Drex reference

Errors

The error envelope, every error type and message, and the headers that come with them.

Errors returned by the Drex application at https://console.nace.ai are JSON bodies with one shape and an HTTP status that names the error class. The hosting layer can reject an oversized HTTP body with a plain-text 413 Request Entity Too Large before Drex runs, without this JSON envelope. Keep inline images within the 4 MiB request-body limit, including base64 expansion; upload larger images and send their file references. For how to retry, see Errors and retries. The SDKs raise one error class per status; see Python and TypeScript.

Error envelope

{
  "error": {
    "type": "invalid_request_error",
    "message": "questions.a.type: must be \"noul\", \"choice\", or \"score\"",
    "issues": [
      { "path": "questions.a.type", "message": "must be \"noul\", \"choice\", or \"score\"" }
    ]
  },
  "request_id": "req_0123456789abcdef0123456789abcdef"
}
FieldTypePresentMeaning
error.typestringalwaysOne of the six types in the table below. The HTTP status is a function of this field.
error.messagestringalwaysHuman-readable description. Each type's messages are listed below.
error.issuesarrayinvalid_request_error onlyEvery validation problem found, as { "path", "message" } objects. The first issue is also the source of error.message.
request_idstringalwaysreq_ followed by 32 hex characters. Same value as the x-request-id header. Quote it in support requests.

Error types

TypeHTTPWhenRetryable
authentication_error401Missing or invalid Bearer API key.no
insufficient_credit402The account has insufficient available funds.no
payment_required402The account has an unpaid invoice.no
invalid_request_error422The request body failed validation or exceeded model limits.no
rate_limit_error429Account RPM or in-flight concurrency was exceeded, or the free tier's shared capacity is full.yes
internal_error500An unexpected server failure occurred.yes
overloaded529Drex is at capacity, paused, or its rate limiter is down, or the model timed out or is unavailable.yes

Response headers

HeaderResponsesValue
x-request-idallThe request id, identical to request_id in the body.
x-typesafe-request-id/v1/systemone, /v1/modelsThe same request id, under the name TypeSafe SDK clients read. The Nace SDKs read x-request-id.
retry-after429, 529Whole seconds to wait. Drex divides retry-after-ms by 1000 and rounds up.
retry-after-ms429, 529Milliseconds to wait. The exact value the limiter computed.

No /v1 route sends Access-Control-Allow-Origin, so a page on another origin can't call Drex: call it from your server.

Order of checks

POST /v1/systemone runs these checks in order and stops at the first failure.

  1. Kill switch. 529 overloaded.
  2. API key. 401 authentication_error, then 402 payment_required or 402 insufficient_credit.
  3. Rate limits. 429 rate_limit_error, or 529 overloaded when the model pool is full.
  4. Body parsing and validation. 422 invalid_request_error.
  5. Model evaluation. 422 invalid_request_error for a request over the token limit, 529 overloaded when the model service does not answer.
  6. Anything unexpected. 500 internal_error.

A request that reaches step 4 has already used a slot in the rate limiter, so a request that fails validation still counts toward the per-minute limit. See Limits.

GET /v1/models runs steps 1 and 2 only, and step 2 stops after the key check.

authentication_error (401)

One message:

Invalid API key. Pass a nace_sk_ key as `Authorization: Bearer <key>`.

Returned by both endpoints when any of these is true:

  • The Authorization header is missing.
  • The header is not exactly Bearer followed by nace_sk_ and 43 URL-safe characters (A-Z, a-z, 0-9, _, -).
  • The key does not exist.
  • The key was revoked. A revoked key can keep working for up to 5 seconds on an instance that had it cached.

Real response, captured against production with no key:

HTTP/2 401
content-type: application/json
x-request-id: req_bd2dffad68f5f5fa4ce702e9bd516fb4
x-typesafe-request-id: req_bd2dffad68f5f5fa4ce702e9bd516fb4

{"error":{"type":"authentication_error","message":"Invalid API key. Pass a nace_sk_ key as `Authorization: Bearer <key>`."},"request_id":"req_bd2dffad68f5f5fa4ce702e9bd516fb4"}

The dashboard Playground uses the same type with its own message, Sign in to use the playground., when the browser session has ended.

insufficient_credit (402)

One message:

This account has insufficient available funds. Add funds to keep going.

Returned when free or prepaid funds cannot cover the request’s budget, including when other running work has reserved the available balance. Funds are reserved before provider execution. A request whose funding expires or runs out is cancelled free. An account with a saved card and auto-pay on is never refused for lack of funds. GET /v1/models does not check the balance and keeps working. Top up, or add a card and turn on pay as you go, on the dashboard Billing page. See Pricing.

payment_required (402)

One message:

This account has an unpaid invoice. Pay it on the Billing page to keep going.

Returned by POST /v1/systemone, the Playground, and the document routes when a charge to the account's card failed, or an invoice is still unpaid at the end of the day it was issued. The account is paused: requests that spend funds are refused, and reads keep working, including GET /v1/models. The response is not retryable until the invoice is paid. Pay it with the Pay button on the dashboard Billing page. The pause lifts as soon as Stripe reports the invoice as paid. See Pricing.

invalid_request_error (422)

error.message is the first issue, written as path: message, or the bare message when the path is empty. A token-limit refusal is the exception: its message starts Request too long: and names the limit and the model. error.issues lists every problem the validator found in one pass. Paths are dotted: questions.<id>, questions.<id>.criteria.<key>, questions.<id>.criteria.<index>.

Every issue Drex itself produces:

PathMessageWhen
(empty)body must be JSONThe body does not parse as JSON.
(empty)body could not be decoded as zstdThe Content-Encoding is zstd, gzip, or deflate and the body is not valid data in that encoding. The message names the encoding sent.
(empty)Content-Encoding "compress" is not supported. Supported: identity, zstd, gzip, deflate.The Content-Encoding is not one of those. The quoted value is whatever was sent. A Content-Encoding that includes br never reaches Drex: the platform answers 415 Unsupported Media Type with no Drex error body. See Limits.
(empty)Content-Encoding must name one encoding, got "gzip, zstd". Supported: identity, zstd, gzip, deflate.The header lists more than one encoding.
(empty)request body must be at most 4194304 bytesThe body is larger than the limit as sent or after decoding. See Limits.
(empty)body must be a JSON objectThe body parses, but is an array, string, number, boolean, or null.
modelunknown model "<value>"; use followed by public active model ids and aliasesmodel is present and is not a model id, alias or retired id listed in Models. The quoted value is whatever was sent. The suggestions depend on the environment and omit retired ids and models offered only through staff preview. GET /v1/models returns the full catalog available to the key, including authorized previews and retired ids, so the lists can differ.
stateis requiredThe state key is absent. Any JSON value, including null, satisfies it.
questionsmust be an objectquestions is missing or not an object.
questionsmust contain at least one questionquestions is {}.
questionsat most 512 questions allowedMore entries than the limit in Limits.
questionsquestion ids must be non-emptyA key is empty or whitespace only.
questions.<id>must be an objectThe question is not an object.
questions.<id>.typemust be "noul", "choice", or "score"Unknown or missing type. No further checks run on that question.
questions.<id>.type"box" questions are not supported; use "noul", "choice", or "score"The question is a box question, which the AI SDK sends for its box type.
questions.<id>.instructionsmust be a string, or JSON nested at most 8 levels that renders to at most 262144 charactersinstructions is JSON (not a string or null) nested more than 8 levels deep, or rendered longer than 262,144 characters. null reads as omitted; other JSON is rendered to text.
questions.<id>.instructionsJSON instructions across all questions must render to at most 4194304 charactersThe JSON instructions of all questions in the request together render to more than 4,194,304 characters. Questions count in the order they are sent: each one that no longer fits in what the questions before it left gets the issue, and a later one that still fits does not. A body over 4,194,304 bytes is refused first (path ""), so this needs rendered text longer than the JSON it came from, which only nesting does: each level indents its lines by 2 more spaces, and each array item adds - . For example, 20 questions whose instructions are each 14,000 one-character strings in arrays nested 7 deep: about 1.1 MB of JSON, where the first 18 fit and the last 2 each get the issue.
questions.<id>.instructionsnoul needs instructions or criteriaA noul question has neither non-empty instructions (after rendering) nor a non-empty criteria.true or criteria.false.
questions.<id>.criteriamust be an objectA noul question's criteria is not an object.
questions.<id>.criteria.true, .falsemust be a stringA noul criterion is present, not null, and not a string. Other keys under criteria are ignored.
questions.<id>.criteriamust be an object with optionsA choice question's criteria is missing or not an object.
questions.<id>.criteriachoice needs at least 1 optionA choice question's criteria is {}.
questions.<id>.criteria.<label>must be a string or nullA choice option description is neither.
questions.<id>.criteriamust be an array of level labelsA score question's criteria is missing or not an array.
questions.<id>.criteriascore needs at least 1 levelA score question's criteria is [].
questions.<id>.criteria.<index>must be a stringA score level label is not a string.
state... or questions...is media (<kind>); <model> reads text onlyThe field holds an image, audio, video or PDF: a base64 data URL, a raw base64 file, or an OpenAI, Anthropic or Gemini media content part. <kind> names which, for example image/png data URL, and <model> is the version that would have answered. On a model that reads images the message ends place supported images in the top-level images array instead. At most 10 per field. See State.
images..., assetsThe image checks in Image errors.
(empty)request body must be at most 4194304 bytesEvery check above passed, and state plus questions (plus images when it is nonempty), serialized as JSON, is larger than the limit in UTF-8 bytes. This is the only issue in the array.
stateexceeds the <limit>-token state limitThe state alone is over the model's state limit; the message names the model and its limit from Limits. error.message is Request too long: the state exceeds the <limit>-token state limit of <model>. Shorten the state.
stateexceeds the <limit>-token limitThe state plus the longest question is over the model's row limit; the message names the model and its limit from Limits. error.message is Request too long: the state plus its longest question exceed the <limit>-token limit of <model>. Shorten the state or that question.
(empty)exceeds the <limit>-token whole-request limitThe whole packed request of Drex Max v1.1 or Drex Max Finance exceeds its limit, including the state, images, all questions and model formatting. The message names the model and its limit and suggests shortening the state, using fewer or smaller images, or removing or shortening questions. See Drex Max limits.

Two more sources exist and are rare. If the model service rejects a request that passed every check above, Drex forwards the service's own validation messages as issues, or invalid request when the service's detail cannot be read. If the validator somehow finishes with no issue to report, error.message is Invalid request..

The following model-error response is illustrative: it shows the suffix when Drex 2.0 is the only publicly available active version. The actual public model ids and aliases after use depend on the environment. The other examples are real captures with illustrative request_id values.

A TypeSafe client that still sends its default model:

{
  "error": {
    "type": "invalid_request_error",
    "message": "model: unknown model \"jev-latest\"; use \"drex-v2.0\" or \"drex-latest\"",
    "issues": [
      { "path": "model", "message": "unknown model \"jev-latest\"; use \"drex-v2.0\" or \"drex-latest\"" }
    ]
  },
  "request_id": "req_0123456789abcdef0123456789abcdef"
}

One field, two issues. A noul question's only criterion, criteria.true, was a number, which fails the type check and also leaves the question without instructions or criteria:

{
  "error": {
    "type": "invalid_request_error",
    "message": "questions.a.criteria.true: must be a string",
    "issues": [
      { "path": "questions.a.criteria.true", "message": "must be a string" },
      { "path": "questions.a.instructions", "message": "noul needs instructions or criteria" }
    ]
  },
  "request_id": "req_0123456789abcdef0123456789abcdef"
}

A short state with a question that puts the row over 262,144 tokens on drex-v2.0, while the JSON body remains under the byte limit:

{
  "error": {
    "type": "invalid_request_error",
    "message": "Request too long: the state plus its longest question exceed the 262,144-token limit of drex-v2.0. Shorten the state or that question.",
    "issues": [
      { "path": "state", "message": "exceeds the 262,144-token limit" }
    ]
  },
  "request_id": "req_0123456789abcdef0123456789abcdef"
}

A state of about 4.3 MB, rejected before the model service sees it:

{
  "error": {
    "type": "invalid_request_error",
    "message": "request body must be at most 4194304 bytes",
    "issues": [
      { "path": "", "message": "request body must be at most 4194304 bytes" }
    ]
  },
  "request_id": "req_0123456789abcdef0123456789abcdef"
}

Image errors

Only Drex Max reads images. For requests that reach Drex, these checks run with the others above, before its serialized-body check. The hosting layer may refuse an oversized HTTP body first with 413, so the 422 image errors below are not guaranteed for oversized inline images. The Drex Max allows options limit applies to every request to a served Drex Max model, with or without images:

PathMessageWhen
imagesmust be an arrayimages is present and is not an array.
imagesat most 8 images allowedMore than 8 entries.
imagesimages require drex-max-v1.1 or drex-max-financeimages is nonempty and the model does not support images.
images.<index>must be a PNG, JPEG or WebP base64 image, data URL, or workspace_file; URLs and paths are not acceptedA string entry is not valid standard base64 or a PNG, JPEG or WebP data URL.
images.<index>image exceeds 16 MiBAn inline image decodes to more than 16 MiB and the HTTP body reaches Drex. A hosted request this large can instead receive the hosting layer's plain-text 413; use uploaded file references for larger images.
images.<index>must be base64 or a workspace_file with workspace_id and file_idAn object entry is not { "type": "workspace_file", "workspace_id", "file_id" }, or an id is not 1 to 128 letters, digits, _ or -.
questions.<id>.criteriaDrex Max allows at most 255 options per question.The model is a served Drex Max model (the message uses its display name) and a choice or score question has more than 255 options or levels, with or without images.
assetsUse the top-level images array; asset references are not supported.A JSON body has an assets field. A multipart upload names its images in assets; see Multipart upload errors.

After those checks pass, Drex reads each image. One it can't use fails the request with a single issue at images.<index> and the message Image could not be read. Use an accessible, valid still PNG/JPEG/WebP, at most 16 MiB and 40 megapixels; all images together must be at most 32 MiB. That covers a corrupt or animated image, another format, an image over a limit, and a workspace_file that you didn't upload without a workspace or that can't be found.

Multipart upload errors

A multipart/form-data request to POST /v1/systemone is read into the same body as a JSON request before any check above runs, so every error above applies to it too, with the paths of the equivalent JSON body (images.<index>, not assets). These checks run first. The first four rows stop the request at once with that one issue, because nothing after them can be checked without a readable request part. Every other row is collected in one pass over the form, so a request with several of those problems gets all of them in error.issues, the assets issues in the order the assets are listed and the <part> issues in the order the parts were sent. A part that an asset names is never also reported as unnamed, whatever else is wrong with that asset.

PathMessageWhen
(empty)body must be multipart/form-dataThe body is not valid multipart, or has no boundary.
(empty)request body must be at most 4194304 bytesThe whole body, image bytes included, is larger than the limit.
requestsend exactly one part named request, holding the JSON bodyThere is no request part, or more than one.
requestmust be a JSON objectThe request part is not a JSON object.
assetsmust be an object with at least one imageassets is present and is not an object, or is {}.
assetsat most 8 images allowedMore than 8 assets.
assetsasset names must be non-emptyAn asset name is "".
imagescannot be sent together with assetsThe request part has both images and assets.
assets.<name>.typemust be "image"The asset's type is anything else.
assets.<name>.partmust name another part of the formpart is missing, not a string, or request.
assets.<name>.partis already used by another assetTwo assets name one part.
assets.<name>.partpart <part> must be one fileThe named part is missing, appears twice, or is not a file.
assets.<name>must be a PNG or JPEG imageThe part's first bytes are not a PNG or JPEG signature, whatever type the part declares.
<part>is not named by any assetA part other than request that no asset names. When assets is missing or invalid, every part other than request is unnamed.

rate_limit_error (429)

Three messages. All carry retry-after and retry-after-ms.

MessageWhenretry-after-ms
Requests per minute exceeded for this account.The account was admitted its full per-minute quota within the last 60 seconds.Time until the oldest counted request leaves the 60-second window, and never less than 1000.
Too many concurrent requests for this account.The account already has its maximum number of requests in flight.1000.
The free tier is at capacity. Retry shortly.Free accounts together already hold the free tier's share of the model pool. Paid accounts never see it.2000.

The quotas per tier are in Limits. The first two limits are per account, so every API key on the account and the dashboard Playground draw from the same counters.

overloaded (529)

Three messages, and three more for Drex Max image requests. All carry retry-after and retry-after-ms. None of them is caused by the request body, so the same request can succeed on retry.

MessageWhenretry-after-ms
Drex is temporarily unavailable. Retry after 30 seconds.The operators have paused the API. Both endpoints return this.30000
Drex is at capacity. Retry shortly.The shared in-flight pool for the request's model fleet is full, or the rate limiter itself could not be reached.2000
Drex is temporarily unavailable. Retry shortly.The model service did not answer within 55 seconds, answered with an error, rejected Drex's own credentials, or reported that it was overloaded.2000
Image storage is temporarily unavailable. Please retry.A workspace_file image could not be looked up because the document service failed or was rate limited.2000
Image download failed or timed out. Please retry., Image preparation timed out. Please retry.Fetching or checking the images took longer than 20 seconds, or a download failed.2000
Drex Max image inference is not configured on this deployment.The request has images and the deployment has no image fleet.1000

The response never says which of these upstream conditions occurred and never includes the model service's status code or body. Drex logs that detail under the request_id.

internal_error (500)

One message:

Something went wrong on our side.

An exception that no other branch handled. Retry once, then send the request_id to support@nace.ai. Nothing is charged for a request that returns 500, or for any other error response.

On this page