Skip to content
Resources

Errors

Every exception trace_sdk raises, what causes it, and how to recover.

Every failure raises a typed exception. All of them inherit from TraceError, so one except TraceError catches everything the SDK can throw.

from trace_sdk import Trace, TraceError

client = Trace()

try:
    completion = client.chat.completions.create(
        messages=[{"role": "user", "content": "Summarise this chapter."}],
    )
except TraceError as error:
    print(error)                 # the message, which always says what to do next
    print(error.status_code)     # 402
    print(error.code)            # "insufficient_credits"

The hierarchy#

TraceError
├── APIConnectionError          the request never reached Trace
│   └── APITimeoutError         Trace did not answer in time
├── APIStatusError              an error status with no more specific class
├── AuthenticationError         401
├── PermissionDeniedError       403
├── BadRequestError             400
├── ModelNotFoundError          404, unknown model id
├── InsufficientCreditsError    402
├── RateLimitError              429
└── ServiceUnavailableError     5xx

Fields on every error#

FieldMeaning
str(error)The message. Multi-line: what happened, then what to do.
error.messageThe same text.
error.status_codeThe HTTP status, or None for connection failures.
error.codeA stable machine-readable code, e.g. "model_not_found".
error.typeThe error family, e.g. "invalid_request_error".
error.request_idTrace's id for the request, when there is one.
error.bodyThe raw error body as a dict.

Match on error.code, not on the message text. Messages are written for humans and will be improved; codes are a contract.

AuthenticationError — 401#

No key, an unknown key, a revoked key, or a credential that is not a Trace key at all.

That Trace API key is not valid or has been revoked. Run `trace login` to get a new one,
or create one at /account/developer.
Run `trace login` to sign in, or set TRACE_API_KEY to a key from your account page.

Fix: run trace login. If you are setting TRACE_API_KEY yourself, check it starts with trace_sk_ and has not been revoked at /account/developer.

Codes: missing_api_key, invalid_api_key.

InsufficientCreditsError — 402#

The reservation for this request is larger than the balance.

except InsufficientCreditsError as error:
    error.balance          # 0.5
    error.estimated_cost   # 4.2

Fix, in order of what to try:

  1. Lower max_tokens. The reservation is your prompt plus the full max_tokens, so a generous cap can refuse a request you could actually afford.
  2. Use a cheaper model — see Models and pricing.
  3. Ask your teacher for a grant.

Nothing is charged for a refused request; no ledger row is written. See Credits and limits.

ModelNotFoundError — 404#

The model id is not on the roster, or you passed a chat model to embeddings.create() (or an embedding model to chat.completions.create()).

except ModelNotFoundError as error:
    print(error.available_models)
    # ['deepseek/deepseek-v4-flash-0731:nitro', 'xiaomi/mimo-v2.5', ...]

The message lists the valid ids, so the fix is in the traceback. Usually it is a typo. Occasionally a model has been switched off temporarily, in which case it is missing from the list rather than misspelled.

Fix: copy an id from error.available_models, or run trace models.

RateLimitError — 429#

More than 30 requests in one minute from one key.

import time

try:
    response = client.chat.completions.create(model=model, messages=messages)
except RateLimitError as error:
    time.sleep(error.retry_after)
    # then retry the request

retry_after is in seconds and is always set. The SDK auto-retries a 429 only on requests that are safe to repeat (GETs); model completions and streaming requests are not retried automatically, so a 429 there surfaces to you directly.

Fix: put a short sleep inside your loop. Catching the error and retrying immediately makes it worse.

BadRequestError — 400#

The request itself is wrong. The message names the specific problem.

CodeCause
max_tokens_too_largeOver the 4000 cap.
unsupported_parametern greater than 1.
missing_messagesmessages was empty.
invalid_messageA message with no role.
invalid_toolA tool without function.name.
missing_inputembeddings.create() was given nothing to embed.
invalid_inputinput was not a string or a list of strings.
too_many_inputsMore than 128 texts to embeddings.create().
request_too_largeBody over 256 KB.

Fix: read the message; it says the limit and the value you sent.

ServiceUnavailableError — 5xx#

The provider behind Trace failed, timed out, or is not configured.

The model provider rejected this request.
This is a problem on Trace's side, not in your code. Try again in a moment.

The SDK retries these automatically. Nothing is charged: the reservation is returned in full.

Fix: wait and try again. If it persists across models, Trace has a problem.

APIConnectionError and APITimeoutError#

The request never reached Trace, or Trace did not answer inside the client timeout. These are network conditions, not API errors — status_code is None.

Could not reach Trace at http://localhost:8000/api/sdk/v1.
Check your internet connection, and check TRACE_BASE_URL if you set it.

Fix: check your connection. If you set TRACE_BASE_URL, check it points at a running Trace. If you are on a school network, a proxy may be blocking it.

For a timeout, raise the client timeout or make the request smaller:

client = Trace(timeout=120.0)

Errors during a stream#

An error can arrive after some text has already been yielded. The iterator raises at that point, so wrap the for loop, not just the call that creates it:

try:
    for chunk in client.chat.completions.create(messages=messages, stream=True):
        print(chunk.text, end="")
except TraceError as error:
    print("\nstream failed:", error)

See Streaming.

Which errors are retried#

The SDK retries 429 and 5xx responses, and connection failures, up to max_retries times (default 2) with no delay between attempts — but only for requests that are safe to repeat. Model completions (POST) and streaming requests are not retried, because a retry could double-charge or duplicate a partially delivered response; handle those yourself as shown above.

It never retries 400, 401, 402, 403, or 404. Repeating a request the account cannot pay for, or one that is simply malformed, only burns the rate limit.

client = Trace(max_retries=0)   # retry nothing