Errors
Every error response, on every route, uses one JSON shape, and every
condition the API can report has a stable code. This page is the
registry.
The error envelope
Every error response is the OpenAI error envelope:
{
"error": {
"message": "Model 'traffic-bot' was not found. Available models: court-assistant, jury-helpdesk. Omit the 'model' field to use this account's default.",
"type": "invalid_request_error",
"param": "model",
"code": "model_not_found"
}
}
| Field | Type | Description |
|---|---|---|
message | string | Human-readable explanation. Wording may improve over time; do not branch on it. |
type | string | The error class (below). |
param | string or null | The request field at fault, in bracket form (messages[2].tool_call_id, tools[0], input[3], after), or null when no single field is responsible. |
code | string or null | Stable, machine-readable code from the registry, or null for validation and server errors. |
Program against the HTTP status and code, and use param to point the
user at the field to fix. The X-Request-ID response header identifies the
exact request; quote it when contacting support
(request ids). The official OpenAI SDKs raise
typed exceptions for every status and expose code, param, and type on
them (openai.NotFoundError, openai.RateLimitError, and so on).
Error codes are an open set: new codes may be added within /v1
(Versioning). Handle an unknown code by falling
back on the HTTP status class.
Error types
type | Statuses | Meaning |
|---|---|---|
authentication_error | 401 | The request did not authenticate |
invalid_request_error | 400, 403, 404, 405, 413 | The request cannot be served as sent; fix it before retrying |
rate_limit_error | 429 | A per-account limit or quota is exhausted; retry after Retry-After |
server_error | 500, 503 | The API or a service it depends on failed; retry with backoff |
Handling by status
| Status | Meaning | Handling |
|---|---|---|
400 | Malformed or invalid request | Fix the field named by param; do not retry unchanged |
401 | Key missing, malformed, expired, or revoked | Check the key (Authentication) |
403 | Key lacks the required scope | Use a key with the scope; do not retry with the same key |
404 | No such model, conversation, document, or job on your account, or an unknown path | Check the id or the path; re-list models or documents |
405 | The path exists but not for this method | Use a method from the Allow header |
413 | Request body over the cap | Send less per request |
429 | Rate limited or out of quota | Wait Retry-After seconds, then retry with backoff |
500 | Unexpected failure | Retry with backoff; then contact support with the request id |
503 | The underlying language model or the text embedding service is unavailable | Wait Retry-After seconds and retry |
Error code registry
One section per code, ordered by status. Validation errors carry
code: null and are described first; the 404 and 405
responses for unknown routes are covered at the end.
400: validation errors
Status 400. type invalid_request_error. code null.
The request body failed validation or an endpoint-level check. Only the
first failure is reported; param names the field.
{
"error": {
"message": "Input should be 'system', 'developer', 'user', 'assistant' or 'tool'",
"type": "invalid_request_error",
"param": "messages[0].role",
"code": null
}
}
param forms:
param | Meaning |
|---|---|
temperature, stream, input | A top-level field |
messages[2].tool_call_id, tools[0], input[3], messages[1].tool_calls[0] | An item of an array, zero-based, and a field within it |
stream_options.include_usage, metadata.notes | A nested field, or a key of a map |
null | The body is missing (Field required; POST /v1/conversations instead treats a missing or empty body as {}), is not a JSON object, or is not valid JSON (Invalid JSON in request body., checked before authentication); the request was sent without Content-Type: application/json; or the Content-Length header is not an integer (Invalid Content-Length header., checked on every route before routing and authentication) |
Type and bound failures use generic messages such as Field required,
Input should be a valid string, Input should be a valid boolean,
Input should be a valid integer, got a number with a fractional part,
Input should be less than or equal to 2, and
List should have at most 200 items after validation, not 201. Values are
never coerced from strings (strict typing).
Endpoint checks have specific messages. Chat completions (guide):
param | Message |
|---|---|
n | Only n=1 is supported. |
messages | At least one user message is required. |
messages | The first non-system message must have role 'user'. |
messages | The last message must be a user or tool message. |
messages[i].content | Message content must not be empty. |
messages[i].content | Message content must be at most 100000 characters. |
messages[i].content[j].type | A content part whose type is not text (for example image_url): a type error on that part |
messages[i].content[j].text | Text content parts require a text string. (a text part whose text is missing or not a string) |
messages[i].tool_call_id | A tool message requires tool_call_id. |
messages[i].tool_call_id | tool_call_id does not match a preceding tool call. (the id is not in the tool_calls of the nearest preceding assistant message, that call was already answered, or no assistant tool call precedes the tool message, including a leading one) |
messages[i].tool_calls[j] | A malformed assistant tool call in history (id, type, function.name, or function.arguments missing or of the wrong type) |
tools[i] | The tool's type is not function; function.name is missing or not 1 to 64 letters, digits, underscores, and dashes; function.description is not a string (function.description must be a string.); or function.parameters is not an object (function.parameters must be a JSON Schema object.) |
tool_choice | tool_choice must be 'auto', 'none', 'required', or {"type": "function", "function": {"name": "<tool name>"}}. (the shape is checked even when no tools are sent, after the message rules and the tool definitions) |
response_format | response_format.type must be one of 'text', 'json_object', 'json_schema'.; response_format of type 'json_schema' requires json_schema.name (string) and json_schema.schema (object).; response_format (JSON mode) cannot be combined with tools. |
stop | stop sequences must be non-empty strings. |
model | Multiple models are available for this account; pass 'model' explicitly, or ask eCourtDate to set a default model for your account. (model omitted, null, blank, or default while several bots are enabled and no default is configured; see Models) |
Embeddings (guide):
param | Message |
|---|---|
input | input must not be empty. (an empty string or an empty array) |
input | input must be at most 100000 characters. (a single string) |
input[i] | input strings must not be empty.; input strings must be at most 100000 characters.; or the item is not a string |
input[96] | input may hold at most 96 strings. |
dimensions | This model produces 1024-dimensional embeddings; the 'dimensions' parameter is not supported. (any value other than a JSON integer is a type error on the same field) |
Knowledge base (Ingesting files, Crawling websites, Documents):
param | Message |
|---|---|
file | Too many files: 21 (max 20).; or no file part at all (Field required) |
file[i] | File exceeds the 26214400 byte limit. |
file[i] | File type '.exe' is not allowed. Allowed: .csv, .docx, .eml, .htm, .html, .md, .pdf, .txt, .xlsx. |
file[i] | File content does not match its '.pdf' extension. |
file[i] | File 'notes.txt' is not valid UTF-8 text. |
file[i] | Uploaded file is empty. |
file[i] | Filename exceeds 255 bytes.; Filename contains invalid characters. (a /, \, a control character, or another non-printable character such as a non-breaking space or a zero-width joiner in the name) |
namespace | Does not match ^[a-z0-9][a-z0-9_-]{0,63}$; on POST /v1/ingest/crawl the field is required (Field required); on GET /v1/documents the filter is empty |
seed_urls | List should have at least 1 item after validation, not 0; more than 100 items |
seed_urls[i] | Seed URL '<url>' is not allowed: <reason> (public hosts only) |
max_pages, max_depth, rate_limit_rps | Out of range (limits) |
limit | Out of range on GET /v1/documents (1 to 1000) |
after | Unknown document id in after: the cursor is not a document on your account |
content | String should have at least 1 character; String should have at most 100000 characters; or whitespace-only content on a conversation message |
metadata | Dictionary should have at most 16 items after validation, not 17 on a conversation; a metadata that is not an object |
metadata.<key> | A key over 64 characters (String should have at most 64 characters), a value over 512 characters (String should have at most 512 characters), or a non-string value (Input should be a valid string) |
One 400 names no field. When the underlying language model or the text
embedding service refuses the request as sent, the response is 400 with
param: null, code: null, and the message
The model provider rejected the request.; nothing was stored or charged.
Change the request (typically a tool definition, a JSON schema, or a
parameter combination the model cannot accept) before retrying. Inside a
stream the same rejection arrives as an in-band frame with
type: invalid_request_error (errors inside a stream).
Recovery: fix the field and resend. Nothing was stored or charged.
invalid_api_key
Status 401. type authentication_error. param null.
Headers WWW-Authenticate: Bearer.
The request did not authenticate: the Authorization header is missing or
malformed, the key is unknown, the secret is wrong, the key has expired or
been revoked, or the account is suspended. The body is identical in every
case.
{
"error": {
"message": "Incorrect API key provided.",
"type": "authentication_error",
"param": null,
"code": "invalid_api_key"
}
}
Recovery: send Authorization: Bearer ecd_sk_... with the key
eCourtDate activated for your account. If a working key starts returning
401, it has expired or been revoked; see
Key lifecycle. Do not retry automatically.
insufficient_scope
Status 403. type invalid_request_error. param null.
The key authenticated but lacks the scope the operation requires (chat or
ingest). The message names the missing scope. Checked before the rate
limiter, so the request does not consume budget.
{
"error": {
"message": "This API key does not have the 'chat' scope.",
"type": "invalid_request_error",
"param": null,
"code": "insufficient_scope"
}
}
Recovery: use a key that carries the scope (scope matrix) or ask eCourtDate to add it. Retrying with the same key will fail again.
tool_not_allowed
Status 400. type invalid_request_error. param tools[i].
The request offered a tool that the bot is not allowed to use. Bots carry an
allowlist of tool names configured by eCourtDate; the first offending tool,
in request order, is named, and param carries its index. The check runs
even when tool_choice is none, and the model is not called.
{
"error": {
"message": "Tool 'get_weather' is not allowed for this model.",
"type": "invalid_request_error",
"param": "tools[1]",
"code": "tool_not_allowed"
}
}
Recovery: remove the tool from tools or ask eCourtDate to add it to
the bot's allowlist. See Tools and structured output.
model_not_found
Status 404. type invalid_request_error. param model.
The model value is not an enabled bot slug or alias on your account. A
disabled bot, another account's bot, and a misspelled id all produce the
same response; the lookup is case-sensitive. The message lists the ids you
can use.
{
"error": {
"message": "Model 'Court-Assistant' was not found. Available models: court-assistant, jury-helpdesk. Omit the 'model' field to use this account's default.",
"type": "invalid_request_error",
"param": "model",
"code": "model_not_found"
}
}
The closing sentence of the hint depends on whether omitting model would
work: Omit the 'model' field to use this account's default. when a default
resolves, or Pass one of them. when several bots are enabled and no
default is configured. When no bot is configured yet, the whole hint reads
This account has no models configured; omit the 'model' field to use the default assistant.
Returned by POST /v1/chat/completions, GET /v1/models/{modelId}
(including GET /v1/models/default when several bots are enabled and no
default is configured), POST /v1/conversations, and
POST /v1/conversations/{conversationId}/messages (a conversation bound to
a bot that was later disabled fails here on its next message).
Recovery: call GET /v1/models and use one of the listed ids, or omit
model to use the default. See Models.
not_found
Status 404. type invalid_request_error. param null.
No conversation, document, ingest job, or crawl job with that id exists on your account, or the path itself is unknown. An id that belongs to another account, a deleted resource, and a malformed id all produce this same response, so the API never confirms that an id exists elsewhere.
{
"error": {
"message": "Conversation not found.",
"type": "invalid_request_error",
"param": null,
"code": "not_found"
}
}
Messages: Conversation not found., Document not found.,
Job not found., Crawl job not found.. Returned by
GET/DELETE /v1/conversations/{conversationId},
POST /v1/conversations/{conversationId}/messages,
GET/DELETE /v1/documents/{documentId}, GET /v1/ingest/jobs/{jobId},
and GET /v1/ingest/crawl/{crawlJobId}. A request for a path that does not
exist returns the same code with the message Unknown route.
(unknown routes).
Recovery: check the id against what your own system recorded when the
resource was created, or list documents with GET /v1/documents. A second
DELETE on an already-deleted resource is 404, not 204.
request_too_large
Status 413. type invalid_request_error. param null.
The request body exceeds the cap: 5,242,880 bytes (5 MiB) on every
endpoint except POST /v1/ingest/files, whose cap is 525,336,576 bytes (20
files of 25 MiB plus 1 MiB of multipart overhead). For a body sent with a
Content-Length, the check runs before routing and authentication, so it
fires even without a key. A body sent without Content-Length (chunked) is
capped while it is read, after routing: it is still rejected without a key,
but an unknown path (404) or an unsupported method (405) is reported
first.
{
"error": {
"message": "Request body exceeds the 5242880 byte limit.",
"type": "invalid_request_error",
"param": null,
"code": "request_too_large"
}
}
Recovery: send less per request. For chat, trim history (only the most recent turns matter) or move it to a conversation; for embeddings, batch fewer inputs; for uploads, send fewer files per request. See Limits.
rate_limit_exceeded
Status 429. type rate_limit_error. param null.
Headers Retry-After.
Your account has made more requests this minute than its limit allows, or the underlying language model is itself rate limited. The message tells you which:
| Message | Retry-After |
|---|---|
Rate limit exceeded: 60 requests per minute. | Seconds left in the current minute window |
The model is currently rate limited. Please retry shortly. | 10 |
Rate limiter unavailable. | 5 (this response carries no RateLimit-* headers, because nothing was computed) |
{
"error": {
"message": "Rate limit exceeded: 60 requests per minute.",
"type": "rate_limit_error",
"param": null,
"code": "rate_limit_exceeded"
}
}
The limit is per account, shared by all keys and all /v1 endpoints, and
the RateLimit-* headers on every authenticated response show where you
stand (on the per-minute 429, RateLimit-Remaining is 0 and
RateLimit-Reset equals Retry-After). Rejected requests count toward the
window too, so a tight retry loop makes it worse.
Recovery: wait Retry-After seconds, then retry with
exponential backoff. Inside a stream the same
error arrives as an in-band frame without a header; treat it as
Retry-After: 10.
insufficient_quota
Status 429. type rate_limit_error. param null.
Headers Retry-After.
Your account's daily token quota is used up. The quota counts input and
output tokens across chat completions, conversation messages, and
embeddings, resets at midnight UTC, and, once reached, blocks every /v1
operation, including ones that call no model.
{
"error": {
"message": "Daily token quota exceeded: 200000 tokens per day.",
"type": "rate_limit_error",
"param": null,
"code": "insufficient_quota"
}
}
Retry-After is the number of seconds until the quota resets; the
RateLimit-* headers on this response report RateLimit-Remaining: 0 and
the same number of seconds in RateLimit-Reset.
Recovery: wait for the reset, or contact eCourtDate to raise the quota for your account. Do not retry on a backoff schedule; the reset time is known. See Rate limits.
upstream_unavailable
Status 503. type server_error. param null.
Headers Retry-After: 5.
The underlying language model or the text embedding service timed out or failed. The request was not served, nothing was stored, and no tokens were charged.
{
"error": {
"message": "The model provider is temporarily unavailable. Please retry shortly.",
"type": "server_error",
"param": null,
"code": "upstream_unavailable"
}
}
Recovery: wait Retry-After seconds and retry; the request is safe to
resend. If it persists, contact support with the X-Request-ID.
500: server errors
Status 500. type server_error. code null. param null.
An unexpected failure inside the API. The response always carries an
X-Request-ID, and the body never includes internal detail.
{
"error": {
"message": "Internal server error.",
"type": "server_error",
"param": null,
"code": null
}
}
The message is always Internal server error.. Inside a stream the same
body arrives as an in-band frame.
Recovery: retry once or twice with backoff. A conversation turn that fails this way is not stored, so resending the message is safe. If the error repeats, contact support with the request id.
Unknown routes and methods
A request for a path that does not exist returns 404 with
code: not_found; a request for an existing path with an unsupported method
returns 405 (code: null) with an Allow header listing every supported
method in alphabetical order. Both use the envelope and are evaluated before
authentication.
{
"error": {
"message": "Unknown route.",
"type": "invalid_request_error",
"param": null,
"code": "not_found"
}
}
HTTP/1.1 405 Method Not Allowed
Allow: DELETE, GET
Content-Type: application/json
X-Request-ID: 74cdbccc0ffa4bfc95c8cf3823059a02
{
"error": {
"message": "Method not allowed.",
"type": "invalid_request_error",
"param": null,
"code": null
}
}
Common causes: a trailing slash on the path (use the canonical path; a
trailing slash redirects with 307), GET on /v1/chat/completions,
HEAD on any route, or GET /v1/conversations (there is no list
endpoint).
Errors inside a stream
Once a streamed response has started, the HTTP status is already 200.
A failure after that point arrives as a frame whose JSON is the error
envelope, followed by data: [DONE]:
data: {"error":{"message":"The model is currently rate limited. Please retry shortly.","type":"rate_limit_error","param":null,"code":"rate_limit_exceeded"}}
data: [DONE]
No finish chunk or usage chunk follows, nothing is stored or metered, and
there is no Retry-After header. Check every frame for an error key
before reading choices.
Branch on the frame's type to decide whether to resend:
type | code | Meaning | Retry? |
|---|---|---|---|
rate_limit_error | rate_limit_exceeded | The underlying language model is rate limited | Yes, after about 10 seconds |
server_error | upstream_unavailable | The model timed out or is unavailable | Yes, with backoff |
server_error | null | An unexpected failure (Internal server error.) | Yes, a few times |
invalid_request_error | null | The underlying language model rejected the request as sent (The model provider rejected the request.) | No; change the request |
See Streaming for the frame sequence.
Which errors to retry
| Code | Retry? | How |
|---|---|---|
rate_limit_exceeded | Yes | After Retry-After, with backoff and jitter |
upstream_unavailable | Yes | After Retry-After (5 seconds) |
server_error (500) | Yes, a few times | Exponential backoff |
insufficient_quota | At the reset | Retry-After is the seconds until the quota resets |
invalid_api_key, insufficient_scope | No | Fix the key |
model_not_found, not_found | No | Fix the id |
tool_not_allowed, request_too_large, validation | No | Fix the request |
The official OpenAI SDKs retry 429, 500, and 503 automatically and
honor Retry-After; see Rate limits for tuning
and for a hand-written loop.