Early accessRead the docs

outbound.dev

Errors and limits

The one error envelope, every status code and error code, and the rate limits.

Every failure the API returns, from a typo in a field to a fault on our side, uses the same JSON envelope and an appropriate HTTP status code. There is exactly one shape to parse, so your error handling is one code path.

The envelope

Response, 402 Payment Required
{
  "error": {
    "code": "insufficient_balance",
    "message": "Your balance cannot cover this call. Add funds and try again.",
    "param": null,
    "doc_url": "https://outbound.dev/docs/errors",
    "request_id": "req_8f3a1b2c9d4e"
  }
}
FieldTypeDescription
codestringStable, machine-readable identifier. Codes never change meaning, so branch your logic on code.
messagestringHuman-readable explanation, suitable for logs. Messages may be reworded at any time: never match on them.
paramstring or nullThe request field that failed validation; null otherwise.
doc_urlstring or nullLink to this page.
request_idstring or nullUnique identifier for the request, also in the X-Request-Id response header. Quote it when you write to us.

Status codes

The HTTP status tells you the class of failure; the code tells you the specific one.

StatusMeaning
200Found. You pay the listed price.
202Accepted. Poll the job until it finishes.
401The API key is missing, malformed, expired or revoked.
402Your balance, or the key's daily cap, cannot cover the call. The body says how much to add. No work started.
403The key is valid but its scopes do not include this endpoint.
404The endpoint or resource does not exist, or is not visible to your organization.
409A request with the same Idempotency-Key is still running.
422The request failed validation, or reused an Idempotency-Key with a different body. param names the field where it can.
429Too fast. Retry-After says when to try again.
500An unexpected error on our side.
503Your organization is still being set up. Retry-After says when to try again.

Error codes

CodeStatusMeaning
unauthorized401The key is missing, malformed, expired or revoked.
insufficient_balance402Your balance, less what open calls have set aside, cannot cover the call. Auto-recharge, if it is on, tops up.
key_daily_cap_reached402The call would pass the key's daily cap. Caps count per UTC day.
insufficient_scope403The key's scopes do not include this endpoint.
not_found404No such endpoint or resource for your organization.
idempotency_in_progress409A request with this Idempotency-Key is still running.
idempotency_key_reused422This Idempotency-Key was sent before with a different body.
validation_error422A field is missing or invalid; param names it.
rate_limited429A rate limit was reached; wait for Retry-After.
unexpected_error500Something failed on our side. Retry, or write to us with the request_id.
org_provisioning503Your organization is still being set up; wait for Retry-After.

Handling failures

  • Branch on `code`, never on `message`. Codes are stable identifiers; messages are prose we may reword to be clearer. An integration that string-matches messages will break on a copy edit.
  • A `404` does not always mean the resource never existed. The API never confirms that another organization's resources exist, so a 404 on an id you believe you own can also mean it belongs to a different organization.
  • Retry `429`, `500` and `503`; fix everything else. On 429 and 503, wait for the Retry-After header before retrying. Send an Idempotency-Key on every POST, so a retry is never charged twice.
  • On `402`, add funds or raise the key's cap. No work started and nothing was charged. Turn on auto-recharge in the console to top up when the balance runs low.

Rate limits

Every request is checked against two windows: a per-minute limit and a per-day limit. A request must fit both. Limits are counted per organization, so ten keys do not get ten times the limit.

Search starts at 20 requests a minute and 1,000 a day. Every other endpoint has its own per-minute and per-day limits, and the headers below show where you stand on each call. Need more? Write to hello@outbound.dev and we can raise your organization's limits.

Reading the headers

Every response that reaches a limit check, success or 429, carries three headers:

HeaderMeaning
RateLimit-LimitQuota of the binding window
RateLimit-RemainingRequests remaining in the binding window
RateLimit-ResetSeconds until the binding window resets

The binding window is whichever of the two windows has fewer requests remaining: the one that will stop you first.

Handling 429s

When you exceed a limit, the API returns 429 with the error envelope and a Retry-After header giving the seconds to wait. Wait that long before retrying, and back off further if you keep reaching the limit.