Skip to main content

Two shapes

/v1 errors are structured, with a stable machine-readable code. Branch on the code, never on the message.
/api errors are flatter. error is always a human-readable string; code appears only where a caller needs to branch.

Status codes

A few /api-specific codes carry the same meaning at 424, naming which capability is missing: search_unavailable, ai_scoring_unavailable, smartlead_not_configured, heyreach_not_configured.

What to do about each

Not a transient failure. The workspace is either unprovisioned for that capability or has it switched off, and retrying changes nothing. Connect the credential, then retry.
Back off until the X-RateLimit-Reset timestamp rather than retrying immediately. If you hit this steadily rather than in bursts, the key needs a higher limit.
The upstream data source failed. Nothing is billed — no cost event is written when the provider call throws. Safe to retry with backoff.
The record is in a state that forbids the action: a search that is already running cannot be expanded, and a campaign that is not awaiting_launch cannot be launched. Read the record first, then act on its actual state.
On /api, a record in another workspace is indistinguishable from one that does not exist. Confirm the session’s active workspace is the one that owns the record.

Retries and idempotency

Billed endpoints accept an idempotency key, either as the Idempotency-Key header or an idempotencyKey body field. Reusing one across a retry means the work is not double-billed.
Send one on every billed call. It costs nothing when there is no retry, and it is the only thing standing between a network blip and a duplicate charge.

Debugging

Quote the request_id from the failing response. It appears in meta.request_id, in the X-Request-Id response header, and on the matching row in GET /v1/usage/costs — which is also how you confirm whether a failed call was ever billed.