Menu
Platform
AI
App store purchases
Database
Flags
Jobs and cron
Localization
Monitoring
Notifications
Payments
Queues
Sandboxes
Webhooks
Getting Started
Authentication
KV Store
Deploy & Infrastructure
Reference
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.
{
"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.codeis the stable identifier. Branch on it, never on the message.error.paramnames the field to fix when the failure is about your request.summaryis a short sentence safe to log;retryablesays whether an identical retry can succeed after the advertised delay.next_actionstates what to change;retry_after_secondsis the delay to honour. A rate-limit denial also sends theRetry-Afterheader.- Responses carry an
x-request-idheader. Quote it in support requests.
#Status codes
| Status | Meaning |
|---|---|
400 | The request is invalid, violates the public protocol, or exceeds the model's context window. |
401 | The bearer key is missing, malformed or revoked. |
404 | The endpoint does not exist, the model is not in the catalogue, or the stored object is not visible to your key. |
409 | An identical idempotent request is still in progress, or its outcome is unresolved. |
413 | The request body exceeds the bounded input limit. |
422 | The request cannot be honoured under public rules with the meaning you asked for, or an Idempotency-Key was reused with a different body. |
429 | Rate, concurrency or spend limits refused the call, or the serving provider throttled after failover. |
501 | A documented facade or hosted tool type is not implemented yet — an explicit not-ready, never a silent downgrade. |
503 | Required capacity, catalogue, credentials or the model route is temporarily unavailable. |
#Codes you will meet
| Code | Status | What to do |
|---|---|---|
invalid_api_key | 401 | The key must start with sylphx_sk_, be unrevoked, and be sent as Authorization: Bearer …. |
invalid_request | 400 | Fix the field named in error.param. |
model_not_found | 404 | Copy an id from the catalogue; an id we do not sell is never substituted. |
context_length_exceeded | 400 | Shorten the input, or compact the conversation and continue from the compacted window. |
invalid_idempotency_key | 400 / 413 | Send a non-empty key of at most 256 bytes; a UUID is the intended shape. |
idempotency_in_progress | 409 | Wait, then resend the identical body with the same key. |
idempotency_key_reuse | 422 | Use a new key for a new request body. |
idempotency_outcome_unknown | 409 | The 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_exceeded | 429 | Reduce rate or in-flight requests, then retry after retry_after_seconds. |
model_unavailable | 503 | Retry later or choose another model. A route or capacity failure, never a request-shape problem. |
file_not_found | 400 | Re-upload the file and use the new file id. |
response_not_cancellable | 400 | Responses 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.
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:
200with the original response and the headeridempotency-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, orresponse_commit_outcome_unknownwith 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
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.