Skip to content
Console
Menu

Queues

Workflows

Getting Started

Authentication

KV Store

AI errors and retries

Codes, idempotency and limits for the AI API

Every failure returns one JSON envelope. The official error object stays first — type, code, message and the offending param — and typed fields ride beside it, so a client can decide what to do without parsing prose.

JSON
{
  "code": "invalid_api_key",
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid API key.",
    "param": null,
    "type": "invalid_api_key"
  },
  "summary": "Invalid API key.",
  "retryable": false,
  "retry_after_seconds": null,
  "next_action": null
}
  • error.code is the stable identifier. Branch on it, never on the message.
  • error.param names the field to fix when the failure is about your request.
  • summary is a short sentence safe to log; retryable says whether an identical retry can succeed after the advertised delay.
  • next_action states what to change; retry_after_seconds is the delay to honour. A rate-limit denial also sends the Retry-After header.
  • Responses carry an x-request-id header. Quote it in support requests.

#Status codes

StatusMeaning
400The request is invalid, violates the public protocol, or exceeds the model's context window.
401The bearer key is missing, malformed or revoked.
404The endpoint does not exist, the model is not in the catalogue, or the stored object is not visible to your key.
409An identical idempotent request is still in progress, or its outcome is unresolved.
413The request body exceeds the bounded input limit.
422The request cannot be honoured under public rules with the meaning you asked for, or an Idempotency-Key was reused with a different body.
429Rate, concurrency or spend limits refused the call, or the serving provider throttled after failover.
501A documented facade or hosted tool type is not implemented yet — an explicit not-ready, never a silent downgrade.
503Required capacity, catalogue, credentials or the model route is temporarily unavailable.

#Codes you will meet

CodeStatusWhat to do
invalid_api_key401The key must start with sylphx_sk_, be unrevoked, and be sent as Authorization: Bearer ….
invalid_request400Fix the field named in error.param.
model_not_found404Copy an id from the catalogue; an id we do not sell is never substituted.
context_length_exceeded400Shorten the input, or compact the conversation and continue from the compacted window.
invalid_idempotency_key400 / 413Send a non-empty key of at most 256 bytes; a UUID is the intended shape.
idempotency_in_progress409Wait, then resend the identical body with the same key.
idempotency_key_reuse422Use a new key for a new request body.
idempotency_outcome_unknown409The original execution is unresolved and will not be repeated. Reconcile with a GET or an idempotency replay; do not resend under a new key.
rate_limit_exceeded429Reduce rate or in-flight requests, then retry after retry_after_seconds.
model_unavailable503Retry later or choose another model. A route or capacity failure, never a request-shape problem.
file_not_found400Re-upload the file and use the new file id.
response_not_cancellable400Responses are not background jobs. Retrieve the response instead.

#Retries are safe by key

POST /responses is the call to protect. A UUID Idempotency-Key binds one key to one exact request body.

Shell
curl https://api.sylphx.ai/v1/responses \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 00000000-0000-4000-8000-000000000000" \
  -d '{"model": "openai/gpt-5.5", "input": "Summarise this incident report in three bullets."}'
  • Identical retry after completion: 200 with the original response and the header idempotency-replayed: true. No new generation.
  • Same key, different body: 422 idempotency_key_reuse. The key is spent.
  • Same key while the first attempt runs: 409 idempotency_in_progress.
  • A completed response can be replayed for seven days from completion.
  • If an execution outcome is unknown, the API says so (idempotency_outcome_unknown, or response_commit_outcome_unknown with the original response reference) instead of pretending the call failed.

Retry only when it is safe

Retry an identical body with the same key when retryable is true, or on a network failure before you read a response. Never reuse a key for a changed body, and never ignore retry_after_seconds.

#Rate limits

Limits are per key and enforced before a request reaches a model. Free-tier keys share one envelope across every model: 300 burst requests per minute, 180 sustained requests per minute and 32 requests in flight. A response carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset (Unix seconds). A denial is 429 rate_limit_exceeded with Retry-After; concurrency denials tell you to reduce in-flight requests, rate denials to slow down. If the serving provider throttles after failover you receive the same typed 429 with the provider's retry hint.

#A retry helper

TypeScript
import { randomUUID } from 'node:crypto'

async function createResponse(body: unknown, key = randomUUID()) {
  for (let i = 0; i < 5; i += 1) {
    const response = await fetch('https://api.sylphx.ai/v1/responses', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.SYLPHX_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': key,
      },
      body: JSON.stringify(body),
    })
    if (response.ok) return response.json()
    const payload = await response.json()
    if (payload.retryable !== true) throw new Error(`${payload.code}: ${payload.summary}`)
    const wait = payload.retry_after_seconds ?? 2 ** i
    await new Promise((resolve) => setTimeout(resolve, wait * 1000))
  }
  throw new Error('retries exhausted')
}

#If you need help

Support needs four facts, all in the response you already have: the x-request-id header and the UTC time of the call; the error.code and summary; the model id and whether the call streamed; and the body you sent with the key redacted. Send them through contact.