Authentication and errors
API keys
Send your key as Authorization: Bearer <key>. Keys start with sk_ followed by 40 letters and digits. One key grants every
endpoint; keys have no scopes.
Create and revoke keys on the API keys page of the dashboard. A revoked key stops working within a minute. A key spends your account's credits, so keep it on servers and out of code you ship to browsers or apps.
Error format
Errors carry a stable code to branch on, a message for people reading logs, and details where the code has any:
{
"error": {
"code": "CONCURRENT_LIMIT_EXCEEDED",
"message": "Concurrent limit exceeded",
"details": { "limit": 1 },
"timestamp": "2026-09-15T12:00:00.000Z"
}
} Validation failures use a different shape, with one entry per invalid field:
{
"success": false,
"error": "Request validation failed",
"details": [{ "field": "prompt", "message": "Prompt cannot be empty" }]
}
Items of a batch fail inside a 200 response instead, with codes VALIDATION_ERROR, RESOURCE_ALREADY_EXISTS or
INSUFFICIENT_CREDITS; see batches.
Error codes
| Status | Code | Meaning |
|---|---|---|
| 400 / 422 | — | The body failed validation: 400 on /v1/monitor/*, 422 on /v1/async/*. details lists each field. |
| 400 | BAD_REQUEST | The request could not be read. |
| 401 | MISSING_API_KEY | No bearer token in the Authorization header. |
| 401 | INVALID_API_KEY_FORMAT | The token is not shaped like an API key. |
| 401 | INVALID_OR_EXPIRED_API_KEY | The key is unknown or revoked. |
| 403 | INSUFFICIENT_CREDITS | Your balance does not cover the request's maximum cost. |
| 404 | RESOURCE_NOT_FOUND | Unknown route or task id, or a monitor endpoint of an engine that is not available yet (as a task, that engine fails validation). |
| 405 | METHOD_NOT_ALLOWED | The path exists, but not with this method. |
| 409 | RESOURCE_CONFLICT | A task with this idempotencyKey already exists. |
| 413 | PAYLOAD_TOO_LARGE | The body is larger than the API accepts. |
| 415 | UNSUPPORTED_MEDIA_TYPE | The body was not sent as application/json. |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests in the current one-second window. |
| 429 | CONCURRENT_LIMIT_EXCEEDED | A synchronous call found every concurrency slot busy; details.limit is your limit. |
| 429 | QUEUE_LIMIT_EXCEEDED | The tasks would overflow your queue; the whole submission is rejected. |
| 500 | INTERNAL_SERVER_ERROR, or the error "Maximum retries exceeded" | The request failed after automatic retries or ran out of time. No credits are charged. |
| 502 | EXTERNAL_SERVICE_ERROR | The engine failed in a way retrying would not fix. No credits are charged. |
Retrying safely
- 429 is always safe to retry: the request was not admitted, so nothing ran or was charged. It carries no
Retry-After; back off with jitter. A rate-limit 429 clears within a second, a concurrency 429 when a running job finishes. - 5xx, timeouts and connection errors are safe to retry for
GETandDELETE, and for task creation when every task carries anidempotencyKey. A repeat of a task that was created answers409 RESOURCE_CONFLICT(in a batch,RESOURCE_ALREADY_EXISTS): the task exists. - Synchronous monitor calls are charged on success and may still be running when a connection drops. Retry them only when the request never reached the server (connection refused, host not resolved). A
500or502from them is final: the server has already retried. - Other 4xx will fail the same way again; fix the request.
The SDKs apply exactly this policy, with 2 retries by default (maxRetries / max_retries), capped exponential backoff with full jitter,
and per-attempt timeouts of 30 seconds, or 330 seconds for synchronous calls (timeoutMs and syncTimeoutMs / timeout and
sync_timeout). Errors they give up on are raised as ApiError:
import { ApiError, Client } from "@answerline/sdk";
try {
await client.chatgpt({ prompt: "Best CRM for small agencies", country: "US" });
} catch (err) {
if (!(err instanceof ApiError)) throw err;
console.error(err.status, err.code, err.meta.requestId, err.body);
}from answerline import ApiError
try:
client.chatgpt("Best CRM for small agencies", "US")
except ApiError as err:
print(err.status, err.code, err.meta.request_id, err.body) Request ids
Every response carries an X-Request-Id header. Send your own (1 to 128 letters, digits, -, _, . or
:) to tie a request to your logs; otherwise one is generated. Quote it when you contact support. The SDKs
expose it as meta.requestId / meta.request_id and include it in ApiError messages.
curl -i "https://api.answerline.dev/v1/credits" \ -H "Authorization: Bearer $API_KEY" \ -H "X-Request-Id: nightly-run-42"
const { data, meta } = await client.request("GET", "/v1/credits");
console.log(meta.requestId);data, meta = client.request("GET", "/v1/credits")
print(meta.request_id)