Errors

Every non-2xx response from /v1 returns Content-Type: application/problem+json per RFC 9457 Problem Details:

{
  "type": "https://developers.exayard.com/concepts/errors#insufficient_scope",
  "title": "Forbidden",
  "status": 403,
  "detail": "Token does not grant scope \"write:projects\".",
  "instance": "/v1/projects",
  "code": "insufficient_scope",
  "param": "write:projects",
  "doc_url": "https://developers.exayard.com/concepts/errors#insufficient_scope",
  "request_id": "req_01H..."
}

Branch on code, not detailcode is stable across minor versions, detail is human-readable and may be edited. For every code listed on this page, type and doc_url point at that code's anchor here, so following the URL from a log line drops you at the right section.

Operation-specific codes

Alongside the codes listed on this page, an operation can answer with a code that describes the specific thing you asked for, such as no_calendar_connected or page_no_content. These name a state of your data rather than a problem with the request. They read as snake_case like every other code and they are just as stable, so you can branch on them.

There are too many to list here, and new ones ship with new operations, so two rules cover them:

A code that is not listed on this page still returns a valid problem response, but its doc_url points at this page rather than at a section of it.

Codes

unauthenticated (401)

Missing or invalid Authorization header. Pass a valid API key (Bearer sk_live_...) or OAuth token. 401 responses also carry a WWW-Authenticate header pointing at the protected-resource metadata for OAuth clients.

insufficient_scope (403)

The token authenticated successfully, but doesn't grant the scope the endpoint requires. The missing scope is surfaced as param on the problem response. Create a new API key with the scope, or use a token that has it. See Scopes.

not_found (404)

Resource not found — or not accessible to this caller. We do not distinguish "doesn't exist" from "exists but you can't see it" because the distinction would leak existence.

forbidden (403)

The caller authenticated, and the token grants the scope, but the specific resource belongs to another organization or is otherwise off limits.

insufficient_credits (402)

The organization does not have enough credits for this operation. Top up, then retry.

subscription_required (402)

The organization has never subscribed, so it has no credits to spend and no way to buy more. Subscribe to a plan (paid plans include monthly AI credits), then retry. This is a different next step from insufficient_credits: topping up is not offered before the first subscription.

upgrade_required (402)

The organization is subscribed, but its current plan does not cover this operation. Upgrade the plan, then retry.

conflict (409)

The request contradicts the current state of the resource, so applying it would overwrite something. Re-read the resource and decide what should win.

invalid_request (400)

Request body failed validation. The offending field (if any) is surfaced as param. Most commonly: missing required fields, wrong types, or values outside an enum.

invalid_idempotency_key (400)

The Idempotency-Key header is malformed. Must be 1–255 URL-safe characters (alphanumeric, underscore, hyphen). See Idempotency.

idempotency_key_reused (409)

The same Idempotency-Key was submitted with a different request body. Pick a new key. This guards against the client mixing up keys and accidentally applying the wrong state.

rate_limited (429)

You've hit the rate limit. Response includes Retry-After (seconds) and the RateLimit / RateLimit-Policy headers. See Rate limits.

internal_error (500)

Unexpected server error. Include request_id in any bug report — it's indexed on our side and gets support to the right request immediately.

transport_error

Emitted by the SDK when the response wasn't JSON (e.g. a CDN error page during an outage). The status field reflects what we actually received.