docs get started
Errors, pagination, and versions
Error codes, cursor pagination, and the version header.
Every error has the same body. Branch on error.code, not on the message. The message is for people and may change.
{
"error": {
"code": "insufficient_credits",
"message": "Not enough credits for this operation.",
"request_id": "req_4f1c9a0b2d7e6a51",
"details": { "required_credits": 12, "balance_credits": 3 }
},
"required_credits": 12,
"balance_credits": 3
}
Every response, success or error, carries X-Request-Id. Quote it when you contact support.
Error codes
| HTTP | Code | What to do |
|---|---|---|
| 400 | invalid_request | Fix the request. details names the field when there is one |
| 400 | unsupported_filter | A filter the person index cannot apply. details.field names it. Remove it or use a supported filter |
| 400 | unsupported_version | RecruitMCP-Version is not a version this server knows. details.supported lists the ones it does |
| 400 | idempotency_key_required | A paid or sending call without ?dry_run=true needs an Idempotency-Key header |
| 401 | unauthorized | Missing, invalid, expired, or revoked key |
| 402 | insufficient_credits | Add credits, then retry. Do not retry on a timer |
| 402 | daily_credit_cap_reached | The key's or the customer's daily cap is spent. details.cap says which, resets_at says when (next UTC midnight) |
| 403 | insufficient_scope | The key lacks details.required_scope |
| 403 | forbidden | The key may not act here: a customer-bound key on an account route, another customer, or a disabled customer |
| 404 | not_found | No such object in this workspace |
| 404 | not_cached | GET /v1/people/{profile_id} reads your cache only. Fetch the person with POST /v1/people:fetch |
| 404 or 409 | suppressed | The person opted out or is on your suppression list. Nothing was charged or sent |
| 405 | method_not_allowed | The path exists, the method does not. Allow lists the methods |
| 409 | conflict | The object's state does not allow this, or a unique value is taken |
| 409 | idempotency_key_reused | Same Idempotency-Key, different body. Use a new key |
| 409 | idempotency_in_progress | The first request with this key is still running. Retry after Retry-After |
| 409 | outreach_cap_reached | A safety cap is reached. next_available_at says when it opens |
| 409 | account_paused | The outreach account is paused. next_available_at says when it resumes, if known |
| 409 | account_not_ready | The outreach account is still connecting, or needs to be connected again |
| 429 | rate_limited | Wait Retry-After seconds, then retry |
| 502 | upstream_error | A data source answered with something unusable. Safe to retry later; nothing was charged |
| 429, 502, or 503 | upstream_busy | A data source is busy or not configured. Retry after Retry-After when present. Not a problem with your key's pace |
| 503 | account_connect_unavailable | Connecting outreach accounts is switched off on this server |
| 500 | internal | Our bug. Retry once, then send us the request id |
The API reference lists the errors each operation can return.
Pagination
Every list returns the same envelope:
{ "object": "list", "data": [], "next_cursor": "MjU", "has_more": true }
Pass next_cursor back as ?cursor= to get the next page. limit is 1 to 100, default 25. A cursor belongs to the list and the filters that produced it; changing the filters starts over. Most lists also take created_after and created_before (ISO 8601).
The SDKs wrap this: paginate() in TypeScript and Python yields every item.
Versions
The API is versioned by date. The current version is 2026-11-01.
- Send
RecruitMCP-Version: 2026-11-01to pin a request. - A key pins the version that was current (or sent) when it was created. A request header overrides the key.
- Every response echoes the version it used in
RecruitMCP-Version.
Additive changes (new fields, new operations, new event types, new error codes on an existing status) ship without a new version. Write clients that ignore unknown fields. Anything that removes or renames a field, or changes its meaning, ships as a new dated version, and the old one keeps working.
Dry runs and idempotency
Every call that spends credits or sends a message takes ?dry_run=true. A dry run is free, changes nothing, and returns what the real call would do: an estimate, a preview of the messages, or the list of who would be skipped and why.
Executing needs an Idempotency-Key header. The same key and body within 24 hours returns the first response and does not charge or send again. A 402 is not stored, so the same key works once you have added credits. See Credits.