Oppy API — getting started
Overview
Read and act on buying-signal cards from your own systems, all the way into your CRM. The API covers everything the dashboard does with cards: listing them, generating their narrative text, approving or rejecting them, revealing contacts and pushing them to Attio or HubSpot.
The base URL is:
https://api.oppyone.com/api/public/v1
Every endpoint except /health needs a key.
If you would rather have an AI agent do this work than write a client, see the MCP server — it exposes the same capabilities with the same permissions and costs.
Prerequisites
Before you start, you need:
- An active Oppy account.
- API access enabled for your workspace. This is switched on by Oppy — email
contact@oppyone.com if you are not sure whether yours is. Without it every
request returns
not_found. - Admin permissions in Oppy — creating API keys is admin-only.
- A connected CRM, if you plan to push cards into one. Connecting Attio or HubSpot needs a browser sign-in and cannot be done through this API; see the Attio and HubSpot guides.
Authentication
Create a key in the Oppy dashboard under Workspace settings → API keys. Copy it when it is shown — it is never displayed again.
Send it as either header:
Authorization: Bearer oppy_live_...
X-API-Key: oppy_live_...
A first request, to check everything works:
export OPPY_KEY="oppy_live_..."
export OPPY="https://api.oppyone.com/api/public/v1"
curl -s "$OPPY/workspace" -H "Authorization: Bearer $OPPY_KEY"
{
"id": "11111111-2222-3333-4444-555555555555",
"name": "your-workspace",
"website": "https://example.com",
"industry": "Software Development"
}
Server-to-server only. Browsers are blocked deliberately — calling this from a web page would expose your key to anyone viewing the source.
Permissions (API key scopes)
Each key carries only the scopes you give it, so a reporting integration can hold a key that cannot spend credits or change anything.
| Scope | Grants |
|---|---|
cards:read | List and read cards. |
cards:write | Hydrate, approve, reject, update. |
contacts:read | See revealed contacts and candidates. |
contacts:write | Reveal contacts. Spends credits. |
crm:write | Push cards into your CRM. Writes to your system of record. |
workspace:read | Workspace, users, credit balance. |
crm:write is separate from cards:write on purpose. Approving a card changes
something inside Oppy and can be undone; pushing writes records into your CRM
where your whole team sees them and cleanup is manual.
The card lifecycle
A card starts as a shell and becomes an outreach-ready record in your CRM. Every step is optional — stop wherever it suits you.
- List cards in your inbox.
- Hydrate a card to generate its narrative text — skip if
hydratedis already true. - Approve it. This starts the contact lookup and spends nothing.
- List candidates to see who is available at that company.
- Reveal the one you want. This costs credits, and approves the card if it was still pending.
- Push to your CRM.
Rate limits
Counted per workspace, not per key — extra keys do not buy extra capacity, and the MCP server draws on the same budget.
Every response carries X-RateLimit-Limit, -Remaining and -Reset, so you can
pace yourself instead of waiting to be rejected.
| Requests | Burst | Sustained | Why |
|---|---|---|---|
| Reads | 20 / sec | 120 / min | Database only. |
| Writes | 10 / sec | 60 / min | Approve, reject, patch. |
| Hydrate — pre-generated | 20 / sec | 120 / min | No model call needed. |
| Hydrate — needs generation | 2 / sec | 20 / min | Roughly what the backend can serve. |
| Reveal | 5 / sec | 30 / min | Also bounded by your credit balance. |
| Push to CRM | 5 / sec | 30 / min | Protects your own CRM's rate limit. |
x-ratelimit-limit: 120 # the sustained window
x-ratelimit-remaining: 119
x-ratelimit-reset: 60 # seconds until capacity frees up
# on a 429, the window you actually breached:
retry-after: 1
x-ratelimit-limit: 20
Hydration has two speeds and you do not choose between them — Oppy decides per
card. Watch X-RateLimit-Limit on the response to see which applied: 120 means
the card was pre-generated, 20 means it cost a generation.
Responses and errors
Lists return data alongside pagination. Every non-2xx response — including a
mistyped URL — returns the same envelope:
{
"error": {
"code": "not_found",
"message": "Card not found",
"request_id": "90cadb00-2fdd-4bbf-9f2a-1a5f0c3e8b21"
}
}
Quote the request_id when reporting a problem — it identifies the exact request
in our logs.
| Code | Meaning |
|---|---|
| 400 | No CRM connected, or the card has no revealed contact. |
| 401 | Key missing, unknown, revoked or expired. A revoked key can take up to 60s to stop working. |
| 402 | Monthly reveal credits exhausted. |
| 403 | Key is valid but lacks the required scope. |
| 404 | Not found, or belongs to another workspace — deliberately indistinguishable. |
| 409 | Not possible for this card, e.g. a reveal with no candidate_id. |
| 422 | Invalid parameter, status, assignee email, or limit above 100. |
| 429 | Rate limited. See Retry-After. |
| 502 | Hydration, reveal, or every CRM push failed downstream. Safe to retry. |
A complete run
export OPPY="https://api.oppyone.com/api/public/v1"
export H="Authorization: Bearer $OPPY_KEY"
# 1. the inbox — best cards first
CARD=$(curl -s "$OPPY/cards?card_status=pending&limit=1" -H "$H" | jq -r '.data[0].id')
# 2. narrative text (skip if .hydrated is already true)
curl -s -X POST "$OPPY/cards/$CARD/hydrate" -H "$H" | jq '{hydrated, strategy_text}'
# 3. approve — starts the contact lookup, costs nothing
curl -s -X POST "$OPPY/cards/$CARD/approve" -H "$H" | jq '.card_status'
# 4. who is available (free) — may be empty for ~a minute after approving
CAND=$(curl -s "$OPPY/cards/$CARD/contact-candidates" -H "$H" | jq -r '.[0].id')
# 5. reveal that person — 1 credit, and approves the card if still pending
curl -s -X POST "$OPPY/cards/$CARD/contacts/reveal" -H "$H" \
-H "Content-Type: application/json" \
-d "{\"candidate_id\": \"$CAND\"}" | jq '{contact: .contact.email, credits_used}'
# 6. into the CRM
curl -s -X POST "$OPPY/cards/$CARD/push-to-crm" -H "$H" | jq '.results[] | {provider, status}'
Support & Troubleshooting
- Questions or issues: contact@oppyone.com — include the
request_idfrom the error envelope. - Endpoint-by-endpoint detail is in the API reference.
- An interactive schema browser is available at
/api/public/v1/docs.