Guides
Errors
The error envelope, every Tokentide error code, upstream errors and when to retry.
Tokentide’s own errors use the OpenAI-compatible envelope, so the OpenAI SDKs (and anything built on them) raise them
as normal API errors with a code:
{
"error": {
"message": "Low Tide is closed right now. It is forecast to open at 19:40 UTC.",
"type": "tokentide_error",
"code": "lowtide_closed",
"param": null,
"request_id": "req_01JDV6Q7Z8R2M3N4P5Q6R7S8T9"
}
}typeis alwaystokentide_errorfor errors Tokentide generates. Upstream errors that Tokentide passes through keep Venice’s body (see Upstream errors).codeis stable; match on it, not onmessage.paramnames the offending field or header when there is one.request_idequals theX-Request-Idheader. Quote it to support.Retry-After(seconds) is set whenever waiting will help.
Error codes
Every code, its HTTP status and what to do. Link to a row with its code, for example
/docs/errors#lowtide_closed.
| Code | HTTP | When | Retry? | What to do |
|---|---|---|---|---|
| invalid_request | 400 | Tokentide validation failed: a bad header value, query or slot request, a field over a model limit (for example max_completion_tokens), TTS input over 4,096 characters, image variants outside 1–4, an invalid X-Tokentide-Upload | No | Fix the request; param names the field |
| lowtide_requires_non_streaming | 400 | X-Tokentide-Lane: lowtide with stream: true (or TTS with streaming: true) | No | Send stream: false, or use the Standard lane |
| invalid_api_key | 401 | Missing, malformed, unknown, revoked or expired key; a Venice key; a tt_live_ key outside production or a tt_test_ key in production | No | Send Authorization: Bearer tt_live_… with an active key |
| unauthorized | 401 | The credentials are valid but cannot be used for this route | No | Use the credential type the route expects |
| insufficient_balance | 402 | Your balance cannot cover the request’s worst-case cost (reserved at admission, refunded down to the actual charge afterwards), or a Large Payload Lane upload slot was requested with no spendable balance | After topping up | Top up, or lower max_completion_tokens to shrink the reservation |
| spend_limit_reached | 402 | A daily, weekly or monthly spend cap you set on the key or account was reached (or is 0, which blocks all spend), or this request’s worst-case cost is more than the room left under it | After the cap resets (Retry-After; the message names the reset time), or after 60 seconds when only the request was too large | Raise the cap in the dashboard, wait for the next day, week or month, or lower max_completion_tokens |
| model_not_allowed | 403 | The model’s privacy tier exceeds your key’s privacy mode, the model is not offered, or a request on the Low Tide lane names a model excluded from Low Tide | No | Use an allowed model, or a key with privacy mode all for anonymized models; for Low Tide, use the Standard lane or send X-Tokentide-Lowtide-Fallback: standard |
| ip_not_allowed | 403 | The key has an IP allowlist and the request came from another address | No | Call from an allowed address or update the allowlist |
| account_restricted | 403 | The account is suspended, banned or closed | No | Contact support@tokentide.app |
| forbidden | 403 | You are authenticated but may not access this resource | No | Access only your own resources |
| not_found | 404 | Unknown route or resource (including Venice account routes, which are never proxied, and unknown or expired uploads) | No | Check the path or id |
| method_not_allowed | 405 | The route exists but not with this HTTP method | No | Use the documented method (see the Allow header) |
| price_above_limit | 409 | The price would exceed X-Tokentide-Max-Multiplier (or the key’s max multiplier), including a cross-epoch upgrade near midnight UTC | Yes, after Retry-After | Wait until the table crosses below your limit, or raise the limit |
| cost_above_limit | 409 | The worst-case charge could exceed X-Tokentide-Max-Cost-USD | No (same request) | Lower max_completion_tokens or raise the limit |
| lowtide_closed | 409 | Low Tide is closed (no spare capacity, congestion, the last 2 minutes before the reset, or paused) | Yes, after Retry-After | Retry at X-Tokentide-Lowtide-Opens-At when present, or send X-Tokentide-Lowtide-Fallback: standard |
| lowtide_cap_reached | 409 | Your hourly Low Tide cap or daily Low Tide allowance is used up | Hourly cap: after Retry-After | Use the Standard lane (or the fallback header); the allowance grows with Standard usage |
| duplicate_request | 409 | The Idempotency-Key was already accepted in the last 24 hours | No | Treat the earlier request as the one that ran; results are never replayed (check GET /v1/account/usage) |
| payload_too_large | 413 | The body exceeds the 4.5 MB direct limit, or an upload exceeds its declared or allowed size | No | Use a public URL input or the Large Payload Lane |
| unsupported_media_type | 415 | The Content-Type or encoding is not accepted on this route | No | Send application/json (or multipart/form-data where documented) |
| rate_limited | 429 | One of our limits: requests per minute per key, concurrent requests, spend velocity, image requests per minute, market stream connections, upload-slot requests, model congestion shedding, or the 4xx circuit breaker | Yes, after Retry-After | Back off with jitter |
| internal_error | 500 | An unexpected error on our side | Yes, with backoff | Retry with an Idempotency-Key; quote the request id if it persists |
| upstream_unavailable | 502 | Venice failed or was unreachable after our retry policy, or a non-streaming response was larger than the 64 MB Tokentide relays | Yes, with backoff | Retry later; check status.tokentide.app. A response over 64 MB fails again: use stream: true or a smaller request |
| capacity_unavailable | 503 | Today’s discounted capacity is sold out (GET /v1/market shows sold_out: true and reopens_at), sales are paused, too many requests that may run past the daily reset are in flight, or the market is briefly unavailable | Yes, after Retry-After | When sold out, Retry-After counts down to the daily reset at 00:00 UTC, when sales reopen. Otherwise retry later |
| epoch_rollover | 503 | The daily reset is in progress: image endpoints and quotes wait for the new day | Yes, after Retry-After | Retry after Retry-After (usually under a minute) |
| service_unavailable | 503 | A feature is temporarily unavailable, for example the Large Payload Lane not enabled | Yes, later | Retry later |
| upstream_timeout | 504 | Venice did not answer in time (mostly long non-streaming requests) | Not blindly | Use stream: true for long generations. The request may have run upstream: if Venice had already answered, it is charged as reserved (never more) and trued up to what Venice billed when the response carries its id |
Upstream errors
Validation errors from Venice are passed through with their status and body, because clients expect Venice
semantics there: 400, 404, 413, 415 and 422 (including the OpenAI-style context_length_exceeded). Their
body is Venice’s ({"error": "…"}, sometimes with details and issues, or {"error": {…}} for context-length
errors), not the Tokentide envelope.
Some upstream errors are never passed through, because they concern our supply, not your request:
Images flagged by Venice as content violations come back as 200 with x-venice-is-content-violation: true and are
not charged. 422 content-policy rejections are normally not charged either.
Errors inside a stream
Once a stream has started, the HTTP status is already 200. If the upstream fails after that, the error arrives
in-band, passed through untouched, followed by the terminator:
data: {"error":{"message":"…","type":"server_error","code":"model_overloaded","param":null,"retry_after":30}}
data: [DONE]Treat an in-band error as terminal. Tokentide never retries a request whose stream has started. If a stream ends without its final usage chunk (an in-band error, a dropped connection or your own disconnect), the request is settled provisionally at its worst-case cost, never more than was reserved for it, and trued up against Venice’s own ledger for that request: anything Venice did not bill is credited back automatically.
The same applies to a non-streaming response that Venice answered but that could not be delivered to you (the
connection was cut, the request ran out of time, or the body is larger than the 64 MB Tokentide relays without
streaming). Venice has already billed it, so it is settled provisionally at its worst-case cost, never more than was
reserved, and trued up against Venice’s ledger when the response carries Venice’s request id (chat completions and
responses). Such a request keeps its Idempotency-Key: a retry with the same key answers 409 duplicate_request.
Embeddings batches whose vectors would exceed 64 MB are refused up front with 400 invalid_request
(param: "encoding_format"): send "encoding_format": "base64", a lower dimensions (on models that support it)
or a smaller batch.
Retry rules
- Never retry unchanged:
400,401,403,404,405,413,415,422. Repeating them cannot succeed, and repeated client errors on one model trip a protective breaker: more than 10 upstream4xxin 30 seconds from one customer answers429 rate_limitedfor 30 seconds (this protects the shared upstream error budget). - Retry after
Retry-After:409 price_above_limit,lowtide_closed,lowtide_cap_reached,429,503. - Retry with exponential backoff and jitter (0.5 s, 1 s, 2 s, 4 s … capped at 30 s, 3–5 attempts):
500,502. 402is about money, not load:insufficient_balanceclears only when your balance grows (top up, or turn on auto top-up);spend_limit_reachedclears when the cap resets (Retry-After) or when you raise it; when only that request was too large for what is left, smaller requests still pass andRetry-Afteris 60 seconds. See Handling 402 errors.- Use
Idempotency-Keyon retries of requests that may have been processed. A duplicate answers409 duplicate_requestinstead of running twice.
Debugging a request
Every response, successful or not, identifies the request:
X-Request-Id(alsorequest_idin the error envelope) is our id for the request. Quote it to support. Once the request has settled, the dashboard’s Request Inspector shows its metadata.X-Client-Request-Idis yours: send a correlation id (1–128 characters of letters, digits and. _ : / + = -, for example a UUID) and it is echoed back. We record only a keyed hash of it, never the id itself, so it can hold your own ids safely. It matters when a call timed out or failed before any response arrived, so you never saw anX-Request-Id: support can still find the request by your id. Other values are ignored, never rejected. The Tokentide SDKs send a random one with every request and print it in their errors.- Public answers our CDN caches (
GET /v1/market,/v1/status,/v1/openapi.json,/v1/image/styles, and/v1/modelswith its/traitsand/compatibility_mappingwithout credentials) never echoX-Client-Request-Id: a cached answer is served to every caller. TheirX-Request-Idcan be the one of the request that filled the cache.
Add X-Tokentide-Debug: 1 to see where the time went and what the upstream answered. Nothing extra is stored. Error
responses carry these headers too, for the stages the request reached before it failed:
When you contact support, include the X-Request-Id (or your X-Client-Request-Id), the time in UTC, the host you
called and the error code. Never send prompts or outputs: metadata is all we need, and all we keep.
Reading errors with the OpenAI SDKs
import openai
try:
client.chat.completions.create(
model="deepseek-v4-1-flash",
messages=[{"role": "user", "content": "…"}],
extra_headers={"X-Tokentide-Lane": "lowtide"},
)
except openai.APIStatusError as e:
print(e.status_code, e.code, e.response.headers.get("retry-after"))
print(e.response.headers.get("x-tokentide-lowtide-opens-at"), e.request_id)The Tokentide SDKs raise a typed error with the code, Retry-After and the parsed X-Tokentide-* headers. See
SDK helpers.