docs

Errors

Every status code the decision API returns, with the exact response bodies, headers and retry guidance.

Error shapes

System One (/v1/systemone, /v1/models) uses TypeSafe's shape. Most errors have an error_type and a message:

{ "detail": { "error_type": "api_usage_error", "message": "Unknown model: nope" } }

Validation errors (422) carry a list instead, one entry per problem.

/v1/decisions uses the OpenAI error shape and never returns 422:

{ "error": { "message": "...", "type": "invalid_request_error", "param": "questions", "code": null } }

Every response, success or error, carries an x-typesafe-request-id header (x-request-id on /v1/decisions; /v1/models sends both). Quote it when you ask for help. Every error body is Content-Type: application/json.

Reading the bodies

The status codes and causes below are fixed. The message text is meant for people and may be reworded, so match on the status code and error_type, not on the message. The 422 bodies follow FastAPI exactly, including key order.

Summary

CodeMeaningWhat to do
400Bad requestFix the request. Do not retry.
401Missing or invalid keyCheck the key. Do not retry.
403Organisation suspended, call from an IP outside the key's allowlist, key's access level doesn't cover the route, or model not on the key's listWrite to support@sthanika.ai, call from an allowed address, or use a key with the right access. Do not retry.
404 / 405Unknown path, unknown custom model, or wrong methodFix the URL, model or method.
413Body or image over a size limitSend less.
415Unsupported Content-Encoding or Content-TypeUse gzip, zstd or plain JSON.
422Request does not match the schema (System One only)Fix the question. The body lists each problem.
429Rate limit, daily limit, or no credit leftWait for retry-after if present. Without it, a retry will not help soon.
500Internal errorRetry. If it persists, send the request ID to support@sthanika.ai.
503Model temporarily unavailable or switched off, or overloaded (/v1/decisions)Wait for retry-after if present, then retry.
504The model did not answer in timeRetry.
529Overloaded (System One)Wait for retry-after, then retry.

400 Bad request

error_type is max_tokens_exceeded, api_usage_error or invalid_request on System One. /v1/decisions also uses wrong_model_type (generative parameters such as max_tokens or temperature) and unsupported_parameter (stream: true).

State plus the longest question is too long:

{
  "detail": {
    "error_type": "max_tokens_exceeded",
    "message": "State plus the longest question is 40001 tokens, which exceeds the maximum context length of 32768 tokens per question."
  }
}

State plus all questions is too long:

{
  "detail": {
    "error_type": "max_tokens_exceeded",
    "message": "Request is 70000 tokens, which exceeds the maximum context length of 65536 tokens per request."
  }
}

Unknown model:

{ "detail": { "error_type": "api_usage_error", "message": "Unknown model: nope" } }

Images or box without the header (a model that does not accept images gets a 400 that says so):

{
  "detail": {
    "error_type": "api_usage_error",
    "message": "Images are not turned on. Send the header TypeSafe-Danger-Zone-Images: true to use assets or box questions."
  }
}

Request-rule violations all use invalid_request with a message that names the rule:

CauseMessage
Byte-order markRequest body must be UTF-8 JSON without a byte order mark.
Invalid UTF-8Request body must be valid UTF-8.
NaN, Infinity or overflowRequest body must contain only finite numbers (no NaN or Infinity).
Nesting over 64 in a field`state` is nested more than 64 levels deep. (the field name varies)
Whole body nested over 512 levelsRequest body is nested more than 512 levels deep.
Malformed compressed bodyRequest body could not be decompressed with Content-Encoding zstd.

The 400 example for /v1/decisions (same causes, OpenAI shape, param names the field):

{
  "error": {
    "message": "Unknown model: nope",
    "type": "invalid_request_error",
    "param": "model",
    "code": "model_not_found"
  }
}

A per-question context overflow on /v1/decisions does not fail the request: that question comes back as a refusal and the others are answered. See /v1/decisions.

401 Unauthorized

Missing, empty or non-Bearer Authorization header:

{
  "detail": {
    "error_type": "authentication_error",
    "message": "Must supply an API key! Check your request and try again."
  }
}

Unknown, expired or revoked key:

{
  "detail": {
    "error_type": "authentication_error",
    "message": "Cannot authenticate with the server. Please check your API key and try again."
  }
}

On /v1/decisions the same causes return the OpenAI shape with "type": "invalid_request_error" and "code": "invalid_api_key".

403 Forbidden

A key with an IP allowlist, used from another address:

{
  "detail": {
    "error_type": "permission_error",
    "message": "This API key is not allowed to make this request."
  }
}

A key whose access level doesn't cover the route (a Read only key making a decision):

{
  "detail": {
    "error_type": "permission_error",
    "message": "This API key is not allowed to make decisions."
  }
}

On /v1/models the message is This API key is not allowed to list models. On /v1/decisions the OpenAI shape has "type": "permission_error" and "code": "insufficient_scope".

A model that isn't on the key's model list:

{
  "detail": {
    "error_type": "permission_error",
    "message": "This API key cannot use model sieve-auto."
  }
}

On /v1/decisions its code is model_not_allowed and param is "model".

A suspended organisation (every route, every key):

{
  "detail": {
    "error_type": "permission_error",
    "message": "This organization is suspended. Contact support@sthanika.ai."
  }
}

The suspended-organisation response also carries x-should-retry: false, so OpenAI SDKs do not retry it. On /v1/decisions its code is organization_disabled.

404 and 405

A custom model name (adp_... or ft:...) that does not exist, or belongs to another organisation, returns 404 with error_type not_found_error and the message Model not found: <name>.

Any unknown path returns 404 and a known path with the wrong method returns 405:

{ "detail": "Not Found" }
{ "detail": "Method Not Allowed" }

413 Request too large

{
  "detail": {
    "error_type": "request_too_large",
    "message": "Request body is larger than 32 MiB (before or after decompression)."
  }
}

The same status is used for an oversize image. See Limits.

415 Unsupported media type

{
  "detail": {
    "error_type": "unsupported_media_type",
    "message": "Unsupported Content-Encoding: \"br\". Use gzip or zstd."
  }
}

Other messages: Only one Content-Encoding layer is supported., zstd window larger than 8 MiB is not supported., Compressed request body without a Content-Encoding header. and Unsupported Content-Type "text/plain". Use application/json or multipart/form-data.

422 Validation error

System One only. The body is a list in FastAPI's shape. Each entry has type, loc, msg, input and, for some errors, ctx, in that order. loc starts with "body". For a question, loc includes the question type, for example ["body", "questions", "urgency", "score", "criteria"].

A missing field. input is the enclosing object:

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "state"],
      "msg": "Field required",
      "input": { "model": "sieve-latest", "questions": { "billing": { "type": "noul", "instructions": "Is this message about billing?" } } }
    }
  ]
}

A score with no levels:

{
  "detail": [
    {
      "type": "too_short",
      "loc": ["body", "questions", "urgency", "score", "criteria"],
      "msg": "List should have at least 1 item after validation, not 0",
      "input": [],
      "ctx": { "field_type": "List", "min_length": 1, "actual_length": 0 }
    }
  ]
}

An unknown question type:

{
  "detail": [
    {
      "type": "union_tag_invalid",
      "loc": ["body", "questions", "q"],
      "msg": "Input tag 'bool' found using 'type' does not match any of the expected tags: 'noul', 'choice', 'score', 'box'",
      "input": { "type": "bool", "instructions": "x" },
      "ctx": { "discriminator": "'type'", "tag": "bool", "expected_tags": "'noul', 'choice', 'score', 'box'" }
    }
  ]
}

All the messages:

Ruletypemsg
Required field missingmissingField required
Not a stringstring_typeInput should be a valid string
Not an objectdict_typeInput should be a valid dictionary
Not a listlist_typeInput should be a valid list
Body, question or noul criteria is not an objectmodel_attributes_typeInput should be a valid dictionary or object to extract fields from
questions, assets or choice criteria emptytoo_shortDictionary should have at least 1 item after validation, not 0
Score criteria emptytoo_shortList should have at least 1 item after validation, not 0
More than 255 choice optionstoo_longDictionary should have at most 255 items after validation, not 256
More than 10 score levelstoo_longList should have at most 10 items after validation, not 11
No type on a questionunion_tag_not_foundUnable to extract tag using discriminator 'type'
Unknown type (the tag is case-sensitive)union_tag_invalidInput tag '...' found using 'type' does not match any of the expected tags: 'noul', 'choice', 'score', 'box'
Wrong asset formatliteral_errorInput should be 'png', 'jpg' or 'jpeg'
Wrong asset typeliteral_errorInput should be 'image'

Where a field accepts a string, object or list, a wrong value yields three entries, one per accepted shape, with loc ending in str, dict[str,any] and list[any].

429 Too many requests

HTTP/1.1 429 Too Many Requests
retry-after: 2
x-typesafe-request-id: ...
{
  "detail": {
    "error_type": "rate_limit_error",
    "message": "Rate limit exceeded for requests. Retry after the time in the retry-after header."
  }
}

The message names what ran out: requests or input tokens. retry-after is at most 30 seconds. On /v1/decisions the body is {"error": {"type": "rate_limit_error", "code": "rate_limit_exceeded", ...}}.

Other 429s:

Causeerror_type (System One) / code (/v1/decisions)retry-after
Daily input-token limit for the organisation or key and modelrate_limit_error / daily_token_limit_exceededOnly if the reset at 00:00 UTC is within 30 seconds
New organisation ramping up faster than allowedrate_limit_error / acceleration_limit_exceededSame
Prepaid balance too low, or wallet frozeninsufficient_quota / insufficient_quotaNone. Add credit first.
The key's monthly budget is used upinsufficient_quota / key_budget_exceededNone. The X-Sieve-Budget-Resets-At header says when the month resets; an admin can raise or remove the budget.
The organisation's monthly budget (all keys) is used upinsufficient_quota / project_budget_exceededNone. Same header; an admin can change it in Settings → Organisation.

Rate limit headers

Responses can carry x-ratelimit-limit-<unit>, x-ratelimit-remaining-<unit> and x-ratelimit-reset-<unit>, where the unit is requests or input-tokens. The reset value is a duration such as 1s or 250ms. The headers appear only when a limit is set for your organisation.

500, 503 and 504

{ "detail": { "error_type": "internal_error", "message": "Internal server error." } }
{ "detail": { "error_type": "service_unavailable", "message": "The model is temporarily unavailable. Please retry shortly." } }
{ "detail": { "error_type": "timeout_error", "message": "The model did not answer in time. Please retry." } }

A 503 can also mean a model or the service was switched off by an operator. The error_type is service_unavailable and the message names the model, for example Model sieve-31b is temporarily disabled. Please try again later or use another model. These carry x-should-retry: false and no retry-after. When the balance check cannot run, the request is also refused with 503 rather than run unpaid.

529 Overloaded

System One answers an overloaded service with 529 and a retry-after header (integer seconds) plus retry-after-ms:

HTTP/1.1 529
retry-after: 1
{
  "detail": {
    "error_type": "overloaded",
    "message": "The service is temporarily overloaded. Please retry shortly."
  }
}

On /v1/decisions the same condition is 503 with retry-after, because OpenAI clients do not use 529:

{
  "error": {
    "message": "The service is temporarily overloaded. Please retry shortly.",
    "type": "service_unavailable_error",
    "param": null,
    "code": "server_is_overloaded"
  }
}

Retries

  • What the SDKs do. Both TypeSafe SDKs retry 408, 429 and 5xx (so 503 and 529 too) and connection errors, twice, with backoff from 0.5 s doubling to 5 s. They honour retry-after-ms first, then retry-after. The Python SDK stops when the total time would pass 30 s; the JavaScript SDK ignores a retry-after over 60 s. The default timeout per attempt is 10 s.
  • OpenAI SDK (for /v1/decisions) retries 408, 409, 429 and 5xx twice by default and does not wait for a retry-after over 120 s.
  • A 4xx other than 408 or 429 is never retried.
  • Idempotency. Send an Idempotency-Key header on calls you may retry. Requests with the same Idempotency-Key from the same API key, to the same route, within 60 seconds of a successful response get that response again, with the header Sieve-Idempotent-Replayed: true. A retry that arrives while the first call is still running waits for it. Without the header, an identical body from the same key within 60 seconds is treated as the same request, so a retry is not billed twice. Only successful responses are replayed; errors are not.
  • Server time limits: connect 2 s, total 30 s.

Next steps

Last updated