API referenceError codes

Error codes

Every error from /v1 comes back in one shape, the error envelope, whichever check refused the request. This page describes that shape and lists every code POST /v1/chat/completions and GET /v1/models can return today, with its HTTP status, the situation that causes it, and whether a retry can help. For advice on handling errors inside an application, see Errors.

The error envelope

{
  "error": {
    "type": "insufficient_quota",
    "code": "quota_exhausted",
    "message": "Your monthly plan token allowance is used up. ...",
    "param": null,
    "request_id": "req_6f1c2a9e0b7d4c3a8e5f1b2d",
    "limit": "monthly",
    "resets_at": "2026-11-01T00:00:00.000Z",
    "upgrade_url": "https://syntic.ai/pricing",
    "add_credit_url": "https://syntic.ai/agents/billing#api-topup"
  }
}

The shape is OpenAI-compatible, so client libraries that read error.type, error.code and error.message work unchanged.

FieldAlways presentDescription
error.typeYesThe broad category, fixed by the HTTP status (table below). Decide retry or no retry from this.
error.codeYesA stable snake_case code. Branch detailed handling on this. Handle a code you do not recognise by its type.
error.messageYesA sentence for people. The wording may change; do not parse it.
error.paramYesAlways null today.
error.request_idYesOur id for the request, req_ followed by letters and digits. The same value is in the x-request-id and x-syntic-request-id headers. Quote it to support. See Request IDs.
error.retry_afterOn 429 and 503Seconds to wait, equal to the Retry-After header.
Extra fieldsSome codesListed per code below, for example limit, resets_at, max_bytes or upgrade_url. Links in these fields always point at syntic.ai.

Error responses carry Cache-Control: no-store. Messages never contain text from the model service: no vendor names, hosts, internal model ids or raw error bodies. When the original message cannot be shown, the code’s standard sentence is used instead.

Older deployments answered with a flat body such as {"error": "quota_exhausted", "message": "..."}. If you have to support both, read error.code when error is an object, and error itself when it is a string.

Types

typeHTTP statusMeaningRetry?
invalid_request_error400, 405, 413, 422The request is malformed, too large, on the wrong path or method, or asks for something not allowed.No. Fix the request.
authentication_error401The credential is missing, unknown, revoked or expired.No. Fix the credential or sign in again.
insufficient_quota402The plan allowance and usage credits cannot pay for the request.After resets_at, or after adding credits.
permission_error403The credential is valid but may not make this request.No.
not_found_error404The model does not exist or is not visible to this credential.No.
timeout_error408, 504The answer took too long.Yes, with back-off.
conflict_error409The request clashes with current state.Yes, shortly.
rate_limit_error429Too many requests, or the model is busy.Yes, after Retry-After.
api_error500, 502A failure on Syntic’s side or in the model service.Yes, with back-off.
service_unavailable_error503A check or dependency is briefly unavailable.Yes, after Retry-After.

Any other status from inside the platform is folded into one of these before it reaches you: an unlisted 4xx becomes 400, and an unlisted 5xx becomes 502 (503 for an overloaded service).

Status codes at a glance

This table is generated from the public error catalog in the platform’s error envelope (supabase/functions/api-proxy/error-envelope.ts on develop). type is fixed by the HTTP status; code is the machine-readable reason. stream_error arrives inside a stream that already answered 200. model_not_found and model_not_in_plan are in the catalog on develop but not returned in production yet.

StatustypeCodes in the public catalog
400invalid_request_errorinvalid_request, invalid_json, messages_required, context_length_exceeded, invalid_surface, invalid_capability
401authentication_errorunauthenticated, invalid_api_key, invalid_token, token_revoked, revoked_api_key, expired_api_key
402insufficient_quotaquota_exhausted
403permission_errorforbidden, insufficient_scope, public_key_rejected, origin_not_allowed, account_disabled, org_unavailable, org_membership_required, plan_required, model_not_in_plan
404not_found_errornot_found, model_not_found
405invalid_request_errorendpoint_not_supported, method_not_allowed
408timeout_errorrequest_timeout
409conflict_errorconflict
413invalid_request_errortoo_many_messages, payload_too_large
429rate_limit_errorrate_limited, model_busy, capacity_busy
500api_errorinternal_error, server_misconfigured
502api_errorupstream_error, upstream_unavailable, stream_error
503service_unavailable_errormodels_unavailable, service_unavailable, authorization_unavailable, usage_check_unavailable, plan_check_unavailable, capacity_busy
504timeout_errorupstream_timeout

Not in the public catalog (provisional). The platform also returns model_unavailable (400), zero_retention_unavailable (403), feature_unavailable (403) and model_retention_unverifiable (503) today, and Syntic Desktop sign-ins can see org_required, quota_exceeded, prompt_too_large, concurrent_request and temporarily_unavailable. These keep the envelope and the type for their status, but are not catalog codes yet and may be renamed or folded into one. Handle them by type.

400 Bad Request

codeWhen it happensExtra fieldsRetry?
invalid_jsonThe body could not be parsed as JSON.noneNo.
messages_requiredmessages is missing, not an array, or empty.noneNo.
invalid_requestmodel is present but not a non-empty string; include_unavailable on GET /v1/models is not true or false; or the model refused the request as malformed (for example, broken tool messages).noneNo. Fix the request.
context_length_exceededThe prompt plus max_tokens does not fit in the model’s context window.noneNo. Shorten the conversation or lower max_tokens.
model_unavailableProvisional, not in the public catalog. Your organization uses zero data retention and the chosen model needs standard retention.noneNo. Choose another model.
invalid_surfaceGET /v1/models?surface= is not one of api, cli, terminal, desktop.noneNo.
invalid_capabilityGET /v1/models?capability= names an unknown capability.noneNo.
org_requiredDesktop. The sign-in is not bound to a workspace.noneNo. Sign in to a workspace.

401 Unauthorized

codeWhen it happensRetry?
unauthenticatedNo Authorization: Bearer ... header was sent.No.
invalid_api_keyThe key is unknown or inactive.No.
revoked_api_keyThe key was revoked.No. Create a new key.
expired_api_keyThe key is past its expiry date.No. Create a new key.
invalid_tokenA sign-in token failed verification or has expired.Refresh the token once, then sign in again. See Sign in with Syntic.
token_revokedThe sign-in session behind the token was revoked, for example by Revoke a sign-in.No. Sign in again.

402 Payment Required

quota_exhausted means the request cannot be paid for. The limit field says which cap stopped it:

limitMeaningHow it clears
monthlyThe plan’s monthly token allowance is used up.Monthly reset, a plan upgrade, or turning on usage credits.
weeklyThe plan’s weekly window is used up.Weekly reset, or usage credits.
sessionThe plan’s rolling 5-hour window is used up.When the window moves on, or usage credits.
spend_limitThe organization’s monthly usage-credit spend limit (or a member’s own limit) was reached.An admin raises the limit, or the next month starts.
no_creditsThe plan allowance is used up and the credit balance cannot cover the request.Buy credits.

Extra fields: limit, resets_at (ISO 8601 time, or null), upgrade_url, add_credit_url. When usage cannot be checked at all, the answer is 503 usage_check_unavailable instead (see 5xx). See Handle usage limits.

quota_exceeded (Desktop) means the Free plan’s monthly tokens are used up. Extra field: action_url, the upgrade page.

403 Forbidden

codeWhen it happensExtra fieldsRetry?
public_key_rejectedA pk_live_ publishable key was used. Only secret keys may call /v1.noneNo.
origin_not_allowedThe key has an origin allowlist and the request’s origin is missing or not on it.noneNo.
insufficient_scopeThe credential lacks the user:inference scope.requiredNo.
account_disabledThe account that owns the credential is disabled.noneNo.
org_unavailableThe credential’s organization is not available.noneNo.
org_membership_requiredThe credential’s owner is no longer an active member of its organization.noneNo.
plan_requiredThe organization is on the Free plan (reason: "free_plan") or its billing is inactive (reason: "billing_inactive"). API access needs a paid plan. On Desktop, also a model or feature above the current plan.reason, upgrade_url; Desktop: action_url, required_plan_idNo. Upgrade or fix billing.
model_not_in_planThe named model exists but your plan does not include it. Not live yet — on develop; today chat completions answers with the default model and an x-syntic-model-downgraded header.upgrade_urlNo. Choose a model from GET /v1/models.
zero_retention_unavailableProvisional, not in the public catalog. Your organization requires zero data retention and no zero-retention model service is available.noneNo. Contact support.
feature_unavailableProvisional, not in the public catalog. Chat has been switched off temporarily across the platform.featureYes, later.

More on credential errors is in Authentication.

404 Not Found

model_not_found: model is not a model id that GET /v1/models returns for this credential, or GET /v1/models/{id} names a model that is unknown or that this credential cannot use. Names are exact and case-sensitive. Internal engine ids are treated as unknown names. Do not retry; list the models first.

Not live yet — on develop. Production does not return model_not_found yet: today POST /v1/chat/completions answers an unknown name with the default model instead.

405 Method Not Allowed

codeWhen it happens
method_not_allowed/v1/chat/completions was called with a method other than POST, or /v1/models with one other than GET or HEAD. The Allow header lists the accepted methods.
endpoint_not_supportedA POST reached a /v1 path other than /v1/chat/completions. Nothing was run or billed.

Do not retry; fix the method or the path.

413 Payload Too Large

codeWhen it happensExtra fields
payload_too_largeThe body is over 2,000,000 bytes, or the model service refused the request as too large.max_bytes (when the byte limit was hit)
too_many_messagesmessages holds more than 500 entries.max_messages
prompt_too_largeDesktop. The conversation is too long for the Free plan.action_url

Do not retry unchanged. Trim older turns, summarise long history, or move large documents out of the prompt.

429 Too Many Requests

Every 429 carries a Retry-After header and error.retry_after. Wait at least that long.

codeWhen it happensExtra fields
rate_limitedMore than 600 requests in a minute for one API key (or one signed-in user).retry_after_seconds; headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset-Seconds
model_busyThe model service is limiting requests.none
capacity_busySyntic’s capacity manager turned the request away before it was sent. Nothing was charged. Also comes with the X-Capacity-Busy: 1 header.retry_after_seconds
concurrent_requestDesktop. Another reply is still running for a Free workspace.none

5xx Server errors

StatuscodeWhen it happensRetry?
500internal_errorAn unexpected failure inside Syntic.Yes, with back-off.
500server_misconfiguredThe service is missing configuration.Yes, later.
502upstream_errorThe model service failed or rejected the request for a reason that is not yours.Yes, with back-off.
502upstream_unavailableThe model service could not be reached.Yes, with back-off.
503authorization_unavailableThe credential could not be checked just now.Yes, after Retry-After.
503usage_check_unavailableYour usage could not be checked. Extra fields: limit: "unavailable", resets_at: null. Nothing was charged.Yes, after Retry-After (5 s).
503plan_check_unavailableYour plan could not be checked.Yes, after Retry-After.
503models_unavailableThe model list could not be read.Yes, after Retry-After (5 s).
503model_retention_unverifiableProvisional, not in the public catalog. Zero-retention organizations: the model’s retention could not be confirmed, so the request was not sent. Nothing was charged.Yes, after Retry-After (5 s).
503capacity_busyAs for 429 capacity_busy, when the whole service is at capacity.Yes, after Retry-After.
503temporarily_unavailableDesktop. A Free-plan check could not run.Yes, after Retry-After.
503service_unavailableAny other short outage.Yes, after Retry-After.
504upstream_timeoutThe model took too long to answer.Yes, with back-off.

Errors inside a stream

When a streamed response has already started with 200 OK and then breaks, the last event carries the envelope with code: "stream_error" and type: "api_error", then the stream ends:

data: {"error":{"type":"api_error","code":"stream_error","message":"The response stream was interrupted. Retry the request.","param":null,"request_id":"req_6f1c2a9e0b7d4c3a8e5f1b2d"}}

Text already received may be incomplete. Retry the whole request. See Stream format.

Handling errors

curl -sS https://app.syntic.ai/v1/chat/completions \
  -H "Authorization: Bearer $SYNTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "Amara Deep", "messages": []}' \
  | jq '.error.type, .error.code, .error.request_id'

Retrying safely

  1. Retry only the types rate_limit_error, service_unavailable_error, api_error and timeout_error, plus feature_unavailable after a pause, and a broken stream.
  2. On 429 and 503, wait at least Retry-After seconds.
  3. Otherwise back off exponentially with jitter (about 1 s, 2 s, 4 s, capped near 30 s) and stop after a few attempts.
  4. A 4xx other than 429 fails the same way when sent again unchanged.

Each retried request that produces output is billed on its own.

Codes planned for new endpoints

⚠️
Preview — this feature is being built; the design below is the spec. These codes belong to endpoints that are still being built. They use the same envelope as above.
codeStatusMeaning
beta_required400A preview endpoint was called without its syntic-beta flag.
unknown_beta400The syntic-beta value is not a known flag.
invalid_version400syntic-version is not a published version.
unsupported_parameter400A field is valid but not supported by the chosen model.
invalid_parameter400A field has the wrong type or an out-of-range value.
idempotency_key_reused409The same Idempotency-Key was sent with a different body.
resource_conflict409The change clashes with the resource’s present state.
output_schema_violation422Structured output could not be made to match the schema.
admin_key_required403An inference key was used on an /v1/organizations/* endpoint.
inference_not_allowed403An Admin API key was used to call a model.
ip_not_allowed403The request came from an address outside the key’s IP allowlist.
region_mismatch403The request reached a region the workspace’s data may not be processed in.
policy_denied403Your organization’s inference policy hook refused the request.

Endpoint pages list the codes specific to them.

Was this page helpful?