API basics
Errors
What a failed request returns, the codes you will meet most, and one function that reads every error body the API sends.
A request that fails returns an HTTP status of 400 or above and a JSON body. Branch on the status first, then on the code inside the body. The message, when there is one, is written for people and can change; the code is the stable part.
The body
Every error puts a string code in error, often with a message, and sometimes with fields that belong to that code:
{
"error": "invalid_request",
"message": "Invalid assistant payload.",
"issues": [
{ "code": "too_small", "path": ["config", "agent", "instructions"], "message": "Too small: expected string to have >=1 characters" }
]
}{ "error": "insufficient_role", "required": "owner", "actual": "admin" }Schema validation failures usually add issues, listing each field that did not pass with its path. Other invalid_request errors carry only a message, so treat issues as optional.
/** The stable error code from any failed response, plus the rest of the body. */
async function readError(res) {
const body = await res.json().catch(() => null)
const code = body?.error ?? `http_${res.status}`
return { status: res.status, code, message: body?.message, body, requestId: res.headers.get('x-request-id') }
}
const res = await fetch('https://api.myleadsfactory.tech/api/assistants', {
headers: { Authorization: `Bearer ${process.env.MLF_API_KEY}` },
})
if (!res.ok) {
const e = await readError(res)
throw new Error(`${e.status} ${e.code}${e.message ? `: ${e.message}` : ''} (request ${e.requestId})`)
}Codes you will meet
| Status | Code | What it means |
|---|---|---|
| 400 | invalid_request | The body or query failed validation; issues, when present, says which field. Also returned (with only a message) when an org-wide key creates something without project_id. |
| 400 | validation_error | One named input is unusable; message names it. |
| 401 | unauthorized | No Authorization: Bearer header ("Missing or malformed Authorization header."), or the key is unknown or revoked ("Invalid or revoked API key."). |
| 403 | read_only_key | A read-only key tried anything but GET, HEAD or a knowledge search. |
| 403 | insufficient_role | The route needs a role a key does not have. The body also carries required and actual. |
| 403 | project_key_not_allowed | The route needs an org-wide key, and this one is pinned to a project (listing members, for example). Creating a project with a project key answers 403 invalid_request with the message A project-scoped key cannot create projects. |
| 404 | not_found | Nothing with that id in this key's reach. A resource in another project or organization also reads as 404, never 403. |
| 409 | varies | The resource is in a state that refuses the request: conversation_ended, already_taken_over, publication_not_ready and others, listed in each guide. |
| 422 | varies | Well-formed but not acceptable, for example credential_rejected when a provider turns down a credential. |
| 429 | rate_limited | Too many requests this minute. See rate limits. |
| 429 | key_quota_exceeded, voice_busy | Public chat only: a publishable key used a budget its whole site shares. See publishable key budgets. |
| 500 | internal_error | Our fault. The body carries request_id. |
| 502, 504 | vendor_unavailable, vendor_timeout | A provider behind the request (telephony, a voice or model vendor) failed or timed out. |
| 503 | varies | A capability is not configured on this deployment, such as telephony_not_configured or knowledge_not_configured. |
When a provider rejects a request outright, its status is passed through with "error": "vendor_rejected" and the provider's details.
Request ids
Every response carries an x-request-id header. From your server, send your own x-request-id and it is echoed back, so you can tie our logs to yours; otherwise one is generated. A browser on public chat can read the header but not send its own. A 500 also puts it in the body:
{ "error": "internal_error", "request_id": "6f1c2d9e-0a7b-4c3e-9f12-5b8d0e4a7c61" }Include it when you write to us about a failure.
Retry or not
- 429: wait for the
Retry-Afterseconds, then retry. Rate limits has a helper. - 500, 502, 503, 504: retry with exponential backoff (for example 1 s, 2 s, 4 s, then give up). A create that timed out may have succeeded, so read before you create again.
- Any other 4xx: do not retry unchanged. Fix the request, the key or the resource state first.