Developer Platform
Docs chevron_right Developer Platform chevron_right Errors

Errors

Every error response carries a structured envelope with a machine-readable code from a closed catalog, so your client branches on code instead of parsing messages. Each entry also carries a human message, an actionable suggestion, and a docs link straight to the relevant row below.

The envelope

Requests authenticated with a scoped efa_ API key receive the envelope as the entire response body, with the canonical HTTP status from the catalog below. Requests authenticated with the account-wide legacy token keep their original status codes and body fields exactly as they always were — the envelope is added under a top-level error key, so existing integrations never break.

# Scoped key (efa_...) — the envelope is the body
{
  "code": "VALIDATION_ERROR",
  "type": "invalid_request_error",
  "message": "The request failed validation.",
  "suggestion": "Fix the fields listed in details and retry the request.",
  "docs": "https://emailflow.ai/docs/api-errors#errors-validation-error",
  "param": "from_email",
  "details": { "from_email": ["The from email must be a valid email address."] }
}

# Legacy token — original body, envelope added under "error"
{
  "from_email": ["The from email must be a valid email address."],
  "error": { "code": "VALIDATION_ERROR", "type": "invalid_request_error", ... }
}

Optional fields appear only when they apply: param names the offending input, details carries field-keyed validation messages or structured context, and retryAfter (on RATE_LIMITED) is the number of seconds to wait, matching the Retry-After header.

The catalog

The full closed catalog — statuses shown are the canonical ones returned to scoped keys:

CodeStatusTypeWhat to do
INVALID_REQUEST 400 invalid_request_error Check the request method, path, and parameters against the API reference.
INVALID_API_KEY 401 authentication_error Create a new key under Account -> API, or check that the key was copied in full.
INSUFFICIENT_PERMISSIONS 403 permission_error Use a key whose scopes cover this endpoint, or create one under Account -> API.
NOT_FOUND 404 invalid_request_error Check the identifier; the resource may have been deleted or may belong to another account.
VALIDATION_ERROR 422 invalid_request_error Fix the fields listed in details and retry the request.
RATE_LIMITED 429 rate_limit_error Slow down and retry after the number of seconds given in retryAfter.
IDEMPOTENCY_CONFLICT 409 invalid_request_error Use a fresh Idempotency-Key for a different request body, or replay the identical body.
CREDITS_EXHAUSTED 402 billing_error Upgrade your plan or wait for the next billing cycle to refresh your credits.
PLAN_LIMIT 402 billing_error Upgrade your plan to raise this limit.
CONFLICT 409 invalid_request_error The resource is in a state that does not allow this operation; fetch it again and check its status.
SERVER_ERROR 500 api_error Retry later; if the problem persists, contact support with the request timestamp.

Validation errors

When input fails validation, scoped keys receive 422 VALIDATION_ERROR with field-keyed messages under details (the first offending field is also named in param). Legacy-token callers keep the original 403 with the bare field-keyed map as the body:

# Legacy token — original validation shape (403)
{
  "from_email": [
    "The from email must be a valid email address."
  ]
}

Handling errors in practice

Branch on code, not on message — messages may be reworded; codes never change (the catalog only grows). The cases worth handling explicitly:

CodeClient behavior
RATE_LIMITEDWait retryAfter seconds, then retry. The TypeScript SDK does this automatically.
IDEMPOTENCY_CONFLICTWith details.reason: "in_flight", the first attempt is still running — wait and retry. Otherwise you reused a key with a different body — pick a fresh key.
CREDITS_EXHAUSTED / PLAN_LIMITNot retryable. Surface to a human: top up credits or raise the quota under plans & quotas.
SERVER_ERRORSafe to retry with the same Idempotency-Key — failed attempts are never stored, so the retry executes normally.