Jev (TypeSafe AI) API errors 400, 401, 403, 422, 429 and 529: causes, limits and retries
TypeSafe's API reference lists four error codes. In practice users also see 400 for rule violations, 403 for a missing key, and a Cloudflare HTML 403 when the text you send contains a shell command. This page separates what is documented from what is reported, gives the request limits, and ends with a procedure for choosing what your code does when Jev fails.
Fix the request and do not retry on 400, 401, 403 and 422. Retry 408, 429 and 5xx (including 529) with bounded exponential backoff that honours Retry-After; the official Python SDK already does this (2 retries, 0.5–5 s backoff, 30 s budget, 10 s per attempt). There is no 250 ms TypeSafe default timeout: that figure was one project's own setting. Decide before you ship whether each decision fails closed, fails to review, or fails open.
Status codes: documented vs reported
"Documented" is TypeSafe's API reference or the SDK source at tag v0.7.2. "Reported" is a named user's report, dated, not rerun by the pool.
| Status | What TypeSafe documents | What users report | SDK retries? | What to do |
|---|---|---|---|---|
| 400 Bad Request | Not in the API error table. SDK maps it to TypeSafeBadRequestError. | A Noul with no instructions or criteria; more than 255 options ("Too many choices…"); more than 10 Score levels; an empty question key; an unknown model; a capitalised type. Reported | No | Fix the request. Check limits locally before sending. |
| 401 Unauthorized | "Missing or invalid API key" | An invalid key returns 401 with authentication_error. | No | Check the key and the Authorization: Bearer header. |
| 403 Forbidden (JSON) | Not in the table (docs say a missing key is 401). SDK maps 403 to TypeSafePermissionDeniedError. | A missing or empty key returns 403 "Must supply an API key!" (reports of 21 and 25 Sep). Reported | No | Set TYPESAFE_API_KEY. Treat 401 and JSON 403 both as "key problem". |
| 403 Forbidden (HTML) | Not documented. | A state containing a shell curl … https:// command got a Cloudflare HTML 403 in about 45 ms (23 Sep; still open). Reported | No | Check for Content-Type: text/html and a cf-ray header. Not a key problem: rephrase or escape the text, or use your fallback. |
| 404 Not Found | SDK maps it (TypeSafeNotFoundError). | No specific report. | No | Check the base URL and path /v1/systemone. |
| 408 Request Timeout | Retried by the SDK; documented by OpenRouter and Cloudflare. | — | Yes | Retry with backoff. |
| 422 Unprocessable Entity | "The request body failed validation…" | Schema failures only: state null or missing; wrong JSON type for state; Score criteria sent as a map. Reported | No | Fix the field named in detail[].loc. |
| 429 Too Many Requests | "exceeded your rate limit. Back off and retry" | Two testers saw no 429 on the direct route (145 sequential questions in 81 s; 120 requests a minute for 1–2 minutes, 25 Sep). Vercel's free tier throttled in one test. Reported | Yes | Back off; honour Retry-After and retry-after-ms; put more questions in one request; cap concurrency. |
| 500–599 | Only 529 is listed. SDK sends every 5xx to TypeSafeInternalServerError. | — | Yes | Retry, then fall back. |
| 529 Overloaded | "temporarily overloaded. Retry after a short delay" | Whether 529 occurs in practice, and with which headers, is unverified. Unverified | Yes | Back off; count 429 and 529 separately in monitoring. |
| No status: connection error or timeout | TypeSafeAPIConnectionError, TypeSafeAPITimeoutError | — | Yes | Retry within the budget, then fall back. |
| 200 with an invalid body | TypeSafeAPIResponseValidationError with a field_path | — | No | Treat as a failure: send to your fallback, never to the default action. |
Sources: API reference; SDK errors.py and retry.py at v0.7.2; community reports listed below. Error bodies come in three shapes: a plain string for 400 rule violations, an object with error_type and message for unknown-model 400 and for 401/403, and a list for 422. Parse all three, or log the raw body.
Retry and timeout defaults
- Timeout per attempt
- 10.0 s (SDK
DEFAULT_TIMEOUT) - Retries
- 2 after the first attempt (3 attempts in total)
- Backoff
- 0.5 s, doubling, capped at 5 s, minus up to 25% jitter
- Retried
- 408, 429, 500–599 (includes 529), connection errors, timeouts
- Server hints
Retry-Afterandretry-after-msreplace the backoff- Budget
- 30 s per call including waits; stops before a wait that would exceed it
Documented SDK v0.7.2 source and docs, 28 Sep 2026.
About the "250 ms timeout". Some pages repeat a "250 ms default timeout". It is not TypeSafe's. It is DEFAULT_TIMEOUT_MS = 250 in oh-my-claudecode's own Jev hook. The project measured Jev round trips of 465–605 ms (#4091, Reported). The timeout was raised to 2,000 ms (PR #4092, merged into dev on 22 Sep) and released in v5.6.0 on 1 Oct 2026. Up to v5.5.0 the default is 250 ms. Documented config.ts at v5.5.0, 29 Sep 2026; config.ts L36 at v5.6.0, 3 Oct 2026
A gate timeout shorter than the real round trip makes every call fail. With a fail-open policy, that silently switches the gate off. Set your timeout from latency you measure on your route. How oh-my-claudecode and five other agent guards behave when Jev times out is compared on Claude Code and MCP.
Correction, 29 Sep 2026: this note previously said the default was "raised to 2000 ms on 22 Sep". That was true only of the then-unreleased dev branch. Update, 3 Oct 2026: the 2,000 ms default is now released, in v5.6.0 (1 Oct).
Request limits
| Limit | Value | What happens if exceeded | Label |
|---|---|---|---|
| Choice options | Up to 255 | 256 returns 400 "Too many choices…". One option is accepted and returns confidence 1.0. | Documented limit; Reported status |
| Score levels | "should have at least two"; the API accepts up to 10 | 11 returns 400. The SDK only checks for at least one level. | Documented; Reported status |
| Whole request | 64k tokens (state plus all questions) | Not observed | Documented |
| State plus longest question | 32k tokens | Not observed | Documented |
| Rate (direct) | 100K tokens per second and 80 requests per second for jev-1.13.0; "can change without notice" and "Rate limits are adjusting dynamically"; higher limits on custom and enterprise plans (docs.typesafe.ai/models.md, checked 3 Oct 2026, 05:16 UTC; rechecked unchanged 7 Oct 2026, 05:19 UTC).Change, 3 Oct 2026: the page listed 40 requests per second from 30 Sep until at least 2 Oct, 05:14 UTC; the pool first saw 80 on 3 Oct, 05:16 UTC. The Internet Archive has no capture since 1 Oct and the docs changelog returns HTTP 404, so the change cannot be dated more closely. 80 per second is 4,800 per minute, by the pool's arithmetic. Earlier value: 250,000 tokens/s and 1,200 requests/min, "adjusting dynamically". An Internet Archive capture of 29 Sep, 19:24:32 UTC still shows it, and the pool first saw the new values on 30 Sep, 20:47 UTC, so the change happened between those times. The exact time is unknown; no changelog entry was found. In that change tokens per second went down and requests per minute went up (40 per second is 2,400 per minute, by the pool's arithmetic). | 429 | Documented |
state | Required: string, object or array of text | null returns 422; "", {} and [] are reported as accepted | Documented; Reported |
| Question key | Any string; never sent to the model | "" returns 400 | Reported |
| Input type | Text only | — | Documented |
Correction, 30 Sep 2026: this table said "250,000 tokens/s and 1,200 requests/min" until 30 Sep. TypeSafe's models page now lists 100K tokens per second and 40 requests per second. If your client throttles itself by requests per minute or tokens per second, check it against the new values. Correction, 1 Oct 2026: the change was dated "after 29 Sep, 07:07 UTC" until 1 Oct; archive captures narrow it to between 29 Sep, 19:24 UTC and 30 Sep, 20:47 UTC. Correction, 3 Oct 2026: the table said "40 requests per second" until 3 Oct; the models page now lists 80 requests per second (tokens per second unchanged at 100K).
How gateways differ
| Route | Different behaviour | Label |
|---|---|---|
| OpenRouter | Adds 402 (insufficient credits), 403 for guardrail or moderation blocks, 408, 502 (model down or invalid response) and 503 (no provider meets routing requirements). Retry-After may appear on 429 and 503. | Documented OpenRouter errors |
| Vercel AI Gateway | Free tier: lower per-model limits and 429 rate_limit_exceeded. Paid tier: Vercel does not rate limit; provider 429s pass through. AI SDK maxRetries defaults to 2. | Documented Vercel rate-limit docs |
| Cloudflare Workers AI | Its own codes, for example 3036/429 (daily free allocation used), 3040/429 (out of capacity), 3007/408 (timeout), 3006/413 (request too large). The table is for Workers AI in general, not Jev-specific. | Documented Workers AI errors |
| Bifrost gateway | Reported to replace native error details with a generic error and drop Retry-After, retry-after-ms and x-typesafe-request-id. | Reported bifrost #7599 |
langchain-typesafe classifier | Single attempt, no retry on 429 or 529, only minimum limits checked (feature request of 28 Sep). Consistent with the 0.0.1a3 source read on 29 Sep: no retry code, 30 s timeout. Its agent guard fails closed; see Claude Code and MCP. | Reported LangChain #40867; Documented source 29 Sep |
What should your code do when Jev fails?
What real implementations do, counted across 9 use cases, and an 8-question checklist for your own code: when Jev fails (4 Oct 2026). This page keeps the status codes, retries and limits.
- Classify the failure. Key problem (401, or 403 with a JSON body); request problem (400, 422); edge block (403 with an HTML body); rate or capacity (408, 429, 5xx, 529, timeout, connection error); malformed success (response validation error).
- Retry only the transient class, with a bounded number of attempts inside a total time budget, honouring server wait hints. Never retry key, request or edge-block errors.
- Choose the disposition for each decision before shipping. Fail closed or hold when a wrong "allow" is costly (moderation, payments, destructive tool calls). Fail to review when a human queue exists. Fail open only when the action is cheap to undo, and log that the gate was bypassed.
- Test the no-key path and the outage path separately. "No key configured" is often a different code path from "API error". Testing without a key lists ten branches to drive with a mock, and what each mock cannot prove.
Three audited projects made three different choices. Documented source-path audit of 28 Sep 2026, not re-audited
| Project | On 429 or 5xx | On other errors or no key | Disposition |
|---|---|---|---|
Jevmail (src/lib/classify.ts) | 429: waits (Retry-After or 5 s, up to 30 s) and retries once. 5xx: retries once after 1.5 s. | 403 is a named error; others are thrown. No classifier fallback without a key. | no-decisionNo classification is made; the email stays unclassified and is retried on the next run. No key: fail-closed |
| WordPress Jev Comment Triage | With a configured provider, the comment stays held and is retried; after 3 attempts it is left held for a person. | With no provider configured, WordPress's original decision stands and may publish the comment. | conditionalfail-closed (held) when a provider is configured; fail-open when none is |
QuantDinger ai_decision_filter.py | Timeout 1–30 s (default 8 s). A Jev error, invalid schema or low confidence goes to an LLM fallback, then the entry is allowed. | No key or internal-credit denial: entry allowed. | fallback-LLMthen the trade entry is allowed (fail-open) |
The same projects are compared by failure type on Use cases, counted on when Jev fails and graded on Products and projects. Terms and colours follow the shared legend (green fail-closed or held, red fail-open, blue fallback, amber conditional, gray no-decision). Changed 6 Oct 2026: this column used to colour "Stops" red and "Proceeds" green, and Jevmail's disposition read "Stops" from the 28 Sep audit; the 3 Oct re-read at commit f6f20af found that a Jev error leaves the email unclassified (see email and lead sorting).
Community reports used on this page (9)
| Report | Opened | Status on 28 Sep | What it shows |
|---|---|---|---|
| typesafe-ai/skills #1 | 18 Sep | Open | Rule violations return 400, not 422; unknown model 400; three body shapes |
| typesafe-sdk-js #6 | 18 Sep | Open | Live table: empty Noul 400, null state 422, 11 levels 400, 256 options 400, empty key 400 |
| typesafe-ai/skills #8 | 21 Sep | Open | Missing key 403 vs invalid key 401 |
| oh-my-claudecode #4091 | 22 Sep | Closed (PR #4092, merged into dev) | Capitalised type 400; Score map 422; the project's own 250 ms timeout |
| typesafe-sdk-js #15 | 23 Sep | Open | Cloudflare HTML 403 on curl text in state |
| the-jev-enator #37 | 25 Sep | Open | Error classification including the edge block; retry once on 429 and 529 |
| jumboly/jev-client #9 | 25 Sep | Open | No 429 at 120 requests a minute for 1–2 minutes; 529 unverified |
| maximhq/bifrost #7599 | 26 Sep | Open | Gateway drops error details and headers |
| langchain #40867 | 28 Sep | Open | No local limit checks or retries; says oversized questions return 422, which contradicts the 400 reports |
What was not verified
- No request was sent to the Jev API. Every live status code above is a named third party's report; TypeSafe may have changed behaviour since.
- Whether 529 occurs in practice, which rate-limit headers the direct route sends, and whether limits apply per key, account or organisation.
- The server's status code for an empty Choice is not stated in the report that describes it.
- Vercel's dedicated AI Gateway errors page returned 404; Cloudflare's error table is not Jev-specific.