troubleshooting · api errors & rate limits · verified 2026-09-27

Jev API errors and rate limits: the complete troubleshooting guide

Every documented Jev API status code and SDK error class with a practical fix — 429 rate limits, 401 auth, 422 payloads, 529 overload, timeout retries — plus why there is no streaming and how to check status without an official status page.

TL;DR

A 429 means you exceeded the rate limit: wait for what the Retry-After header asks for (the SDKs expose it as retry_after_ms / retryAfterMs), then retry with exponential backoff and jitter — the official SDK already does exactly this by default (2 retries, 0.5 s doubling to 5 s, Retry-After honored). Never retry 400/401/403/422 — those are your bug, not the server's mood. A 529 means TypeSafe itself is overloaded: back off or fail over to a secondary endpoint. There is no streaming: Jev returns one complete typed decision per request. And there is no public status page: probe with a minimal request and read the status code.

Status codes and official guidance on this page were verified against docs.typesafe.ai on 2026-09-27: the HTTP API reference, the Python SDK exceptions and retries references, the JavaScript SDK error classes, and the System One concept page. Where the official docs publish nothing — numeric limits, a status page, a streaming mode — we say so and fall back to clearly-labelled general HTTP engineering practice.

The error quick-reference table

Every status the API and SDKs document, one row each: what triggers it, how to fix it, and whether retrying can help at all. The HTTP reference documents 401, 422, 429 and 529 explicitly; the SDK references cover the rest as typed error classes.

StatusSDK class (Python · JS)What it meansHow to fix itRetry?
400 Bad RequestTypeSafeBadRequestError · BadRequestErrorDocumented on the SDK exceptions page as "the request was invalid". The HTTP reference details 422 for body validation; a 400 usually means malformed JSON or a request that broke before validation could run.Read the error body, validate your JSON payload locally (zod / pydantic), and confirm the Content-Type header is application/json.No — fix the request
401 UnauthorizedTypeSafeAuthenticationError · AuthenticationErrorThe official wording: "Missing or invalid API key. Check the Authorization header."Confirm the key exists, is active, and comes from the right environment — and that the call runs server-side. Keys in browser bundles get rotated the hard way.No — fix the credential
403 Permission DeniedTypeSafePermissionDeniedError · PermissionDeniedErrorThe SDK documents this as "access was denied": the key authenticated but may not use this resource — a plan, region or entitlement issue by ordinary REST semantics. The HTTP reference does not detail this status, so treat the response body as the source of truth.Check the key's plan and permissions in your provider dashboard; confirm the model ID your plan actually entitles.No — entitlement problem
404 Not FoundTypeSafeNotFoundError · NotFoundErrorWrong path or wrong method. The evaluation endpoint is POST https://api.typesafe.ai/v1/systemone — a typo like /v1/systemOne or an accidental GET lands here.Compare the URL character by character against the endpoint table on /jev-api. The gateways differ: Vercel AI Gateway and OpenRouter have their own paths.No — fix the URL
422 Unprocessable EntityTypeSafeUnprocessableEntityError · UnprocessableEntityErrorThe official wording: "The request body failed validation — for example a missing required field or a malformed question." The body details the offending field.Every request needs model, state and questions; every question needs type (choice / score / noul), instructions and criteria. Fix the field the body names, then re-run.No — fix the schema
429 Too Many RequestsTypeSafeRateLimitError · RateLimitErrorThe official wording: "You have exceeded your rate limit. Back off and retry after a short delay." The SDK parses the server's requested wait into retry_after_ms / retryAfterMs when a Retry-After header is present.Wait what the header asks (fall back to exponential backoff with jitter when it is absent), cap client-side concurrency, and only then consider raising limits with your provider.Yes — back off first
529 OverloadedTypeSafeInternalServerError family · not a standard statusTypeSafe-specific and documented with this wording: "TypeSafe is temporarily overloaded. Retry after a short delay." It is the one status that is unambiguously the provider's situation, not yours.Back off, and if it persists fail over to a secondary endpoint — the pattern our /jev-api guide wires with a 1500 ms circuit breaker.Yes — or fail over
5xx server errorsTypeSafeInternalServerError · InternalServerErrorDocumented as "the server failed to process the request". The working assumption is transience — until the same endpoint keeps failing while another works.Retry with backoff — the SDK includes all 5xx in its default retryable set. If one endpoint keeps failing while another works, prefer failover over persistence.Yes — SDK default
No HTTP responseTypeSafeAPIConnectionError · APIConnectionErrorThe request failed without receiving a response — DNS, TLS, firewall, or your own egress rules. Says nothing about the API itself.Test the network path from the machine that serves production — a probe from your laptop proves nothing about your servers. The SDK retries connection errors by default.Yes — SDK default
Request timeoutTypeSafeAPITimeoutError · APITimeoutErrorThe request exceeded your configured timeout — the SDK error carries the timeout setting that fired. Decisions are single forward passes measured in tens to low hundreds of milliseconds, so a timeout indicts the budget or the network path, not the model.Raise the timeout toward your p99, or keep it tight and fail over — our /jev-api pattern uses 1500 ms. The SDK retries timeouts by default.Yes — SDK default
Invalid response bodyTypeSafeAPIResponseValidationError · —A 2xx arrived but the body was missing or structurally invalid required data — the Python class exposes field_path, e.g. answers.tone.confidence (the docs' own example). This is an SDK-side validation failure, not an HTTP error.Log the field path and the model ID you sent; pin the model identifier and re-check after provider model updates instead of retrying blind.Investigate — do not loop

The SDK column pairs each Python class with its JavaScript twin. Every HTTP error also carries the x-typesafe-request-id response header (exposed as request_id / requestId on the SDK error objects) — include it when you contact support. Statuses beyond the four documented in the HTTP reference are framed by the SDK references and standard HTTP semantics.

Compare your failing call against working example requests

Rate limiting: what the docs promise, and what engineering adds

The official word is short. The working strategy is not.

What the official docs actually say

  • 429 is documented with this instruction: "You have exceeded your rate limit. Back off and retry after a short delay." 529 adds: "TypeSafe is temporarily overloaded. Retry after a short delay."
  • The API reference says to "retry the request with exponential backoff instead of retrying immediately", and notes that the official client SDKs handle this automatically via their default retry policy.
  • No numeric limits are published — not requests per second, not concurrency, not a daily quota. Anyone quoting an exact Jev RPS number is guessing. Derive your ceiling from the 429s you actually receive and from the Retry-After header the server sends.
  • The SDK treats 429 as retryable by default and honors Retry-After by default (respect_retry_after=True, reading both Retry-After and retry-after-ms).
  • Every HTTP error carries the x-typesafe-request-id header — grab it before you contact support.

What general HTTP engineering adds (labelled: not official Jev behaviour)

  • Back off exponentially with jitter: delay = random(0, min(cap, base × 2^n)). The shape matches the SDK default (0.5 s doubling to 5 s); the jitter matters more as you scale — a thousand clients retrying in the same second re-herd the limit.
  • Honor Retry-After when it appears. It is the server answering "when?" for you; polling faster only resets it.
  • Budget your retries: the SDK default is 2 retries inside a 30-second total budget per call. A raw HTTP client deserves the same kind of stop condition, not an infinite loop.
  • Split 429 into its two causes. Burst pacing is yours — fix it with backoff and a client-side concurrency cap. Exhausted quota or credits is billing — backoff will not help; watch the dashboard and alert before you hit the ceiling.
  • If 429s persist while your traffic is legitimate, shed load: route to a secondary endpoint or a deterministic fallback rule instead of queueing your users behind a wall of retries.
When backoff is not enough: the confidence-gated fallback chain

Retry playbooks: Python and TypeScript

Copy-paste starting points. The first two are raw-HTTP clients that encode the general practice above; the third is the official SDK doing it for you.

Raw HTTP retry in Python (requests)

python · raw http

When to use it

You call POST /v1/systemone with requests or httpx and want the retry logic visible in your own code. It retries 408, 429 and the 5xx family (including 529), honors Retry-After, and never retries a validation or auth error.

python · raw http
# jev_retry.py — raw-HTTP retry for POST /v1/systemone.
# Documented statuses: 401 · 422 · 429 · 529 (docs.typesafe.ai/api.md).
# The retry mechanics below are general engineering practice — the official
# docs publish no numeric limits, so none are assumed here.
import os
import random
import time

import requests

URL = "https://api.typesafe.ai/v1/systemone"
# Mirrors the official SDK's default retryable set ({408, 429, all 5xx});
# 529 is listed explicitly — it is TypeSafe's documented overload status.
RETRYABLE = {408, 429, 500, 502, 503, 504, 529}


def evaluate(state: str, questions: dict, max_attempts: int = 4) -> dict:
    delay = 0.5  # first backoff; doubles per attempt, capped at 5 s (SDK shape)
    resp = None

    for _ in range(max_attempts):
        resp = requests.post(
            URL,
            headers={
                "Authorization": f"Bearer {os.environ['TYPESAFE_API_KEY']}",
                "Content-Type": "application/json",
            },
            json={"model": "jev-latest", "state": state, "questions": questions},
            timeout=5.0,
        )
        if resp.status_code < 400:
            return resp.json()

        if resp.status_code not in RETRYABLE:
            # 400/401/403/404/422: identical bytes in, identical error out.
            resp.raise_for_status()

        # Honor the server's requested wait when it sends one:
        # Retry-After (seconds). The official SDK also parses retry-after-ms.
        wait = resp.headers.get("Retry-After")
        sleep_s = (
            float(wait) if wait else min(delay, 5.0) * random.uniform(0.75, 1.0)
        )
        time.sleep(sleep_s)
        delay *= 2

    resp.raise_for_status()  # budget spent on a retryable status
    raise RuntimeError("retry budget spent")

The retryable set mirrors the official SDK default ({408, 429, all 5xx}) with 529 listed explicitly for readability. The 5-second cap matches the SDK's documented backoff_max.

Timeout + backoff + failover in TypeScript

typescript · fetch

When to use it

The server-side TypeScript path. Each attempt gets a 1500 ms abort budget — the same circuit-breaker number our /jev-api guide uses — retryable statuses go around again, and a spent budget throws so the caller can fail over to a secondary endpoint.

typescript · fetch
// lib/jev-retry.ts — per-attempt timeout, backoff with jitter, Retry-After
// handling, and a throw the caller can fail over on. The 1500 ms abort budget
// is the same circuit-breaker number used by the failover adapter on /jev-api.
const PRIMARY = 'https://api.typesafe.ai/v1/systemone';
// Mirrors the official SDK default ({408, 429, all 5xx}) + the documented 529.
const RETRYABLE = new Set([408, 429, 500, 502, 503, 504, 529]);

type Contract = { state: string; questions: Record<string, unknown> };

export async function evaluateWithRetry(
  contract: Contract,
  {
    attempts = 4,
    timeoutMs = 1500,
  }: { attempts?: number; timeoutMs?: number } = {},
): Promise<unknown> {
  let last: Response | undefined;

  for (let attempt = 0; attempt < attempts; attempt++) {
    if (attempt > 0 && last) {
      // Wait at least what the server asked for: retry-after-ms is the
      // millisecond form the official SDK understands; Retry-After is seconds.
      const msForm = Number(last.headers.get('retry-after-ms'));
      const sForm = Number(last.headers.get('retry-after')) * 1000;
      const asked = Number.isFinite(msForm) ? msForm : sForm;
      // ...then add full jitter on top: random(0, min(cap, base * 2^n)).
      const base = Math.min(500 * 2 ** (attempt - 1), 5000);
      await new Promise((r) =>
        setTimeout(r, Math.max(asked || 0, Math.random() * base)),
      );
    }

    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const res = await fetch(PRIMARY, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`,
        },
        body: JSON.stringify({ model: 'jev-latest', ...contract }),
        signal: controller.signal,
      });
      // 2xx, or a fix-the-request 4xx: hand it back either way.
      if (!RETRYABLE.has(res.status)) return res.json();
      last = res; // 408/429/5xx/529: back off and go around again
    } catch (err) {
      if (attempt === attempts - 1) {
        throw new Error('Jev unreachable after retries');
      }
      // AbortError / network failure: fall through and retry.
    } finally {
      clearTimeout(timer);
    }
  }

  // Budget spent on retryable statuses — surface where it died so the caller
  // can fail over to a secondary endpoint (see the adapter on /jev-api).
  throw new Error(`Jev still failing: HTTP ${last?.status ?? 'no response'}`);
}

Full-jitter delays cap at 5 s, matching the SDK's documented backoff_max. When the response carries retry-after-ms, the wait honors it — the millisecond form the official SDK also parses.

Let the official SDK retry (Python)

python · typesafe-sdk

When to use it

The boring, correct option. The documented defaults already retry 408/429/5xx and connection or timeout errors, with exponential backoff, jitter and Retry-After handling. Tune the policy instead of reimplementing it.

python · typesafe-sdk
# The official Python SDK retries for you. Defaults below are the documented
# ones (docs.typesafe.ai/sdk/python/api/retries.md), shown explicitly so you
# can tune rather than reinvent:
from typesafe_sdk import RetryPolicy, TypeSafeClient

client = TypeSafeClient(
    retry=RetryPolicy(
        max_retries=2,             # default: 2 retries (three attempts total)
        backoff_initial=0.5,       # first backoff, seconds — doubles each attempt
        backoff_max=5.0,           # ceiling for the exponential growth
        backoff_jitter=0.25,       # random fraction subtracted from each delay
        timeout=30.0,              # total budget per SDK call, in seconds
        # default is {408, 429, all 5xx}:
        http_statuses={408, 429, 500, 502, 503, 504},
        respect_retry_after=True,  # honors Retry-After / retry-after-ms headers
    ),
)

# Connection errors and timeouts are retried by default too. When the budget
# is spent on a 429, the SDK re-raises TypeSafeRateLimitError — its documented
# retry_after_ms attribute carries the server's requested wait in
# milliseconds (None when the header is absent):
#
#     import time
#     from typesafe_sdk import TypeSafeRateLimitError
#
#     try:
#         decision = run_evaluation(client, state, questions)
#     except TypeSafeRateLimitError as err:
#         time.sleep((err.retry_after_ms or 5_000) / 1000)
#         decision = run_evaluation(client, state, questions)

Defaults shown explicitly per the official retries reference: max_retries 2, backoff_initial 0.5 s, backoff_max 5 s, jitter 0.25, 30-second budget. After the budget is spent the last error is re-raised — catch TypeSafeRateLimitError and read retry_after_ms.

Retry or not: the classification

The SDK default retryable set is {408, 429, all 5xx} plus connection and timeout errors — everything else is yours to fix. The same split works for raw HTTP.

Retry — after waiting

408 request timeout, 429 rate limit, the whole 5xx family including 529 overload, connection failures, and your own timeouts. These mean "not now", not "never". Wait between attempts — backoff plus jitter — and cap the number of attempts.

Never retry — fix first

400, 401, 403, 404 and 422. The same bytes will produce the same error: a malformed body, a missing or invalid key, an entitlement problem, a wrong path, a schema violation. Read the error body — 422 responses detail the offending field — and fix the request before it goes out again.

Budget and billing

A retry is a billed call: input tokens are charged on every attempt, and Jev has no output-token billing because decisions are not generated text. Cap attempts, keep the wall-clock budget explicit, and alert on your 429 rate instead of absorbing it silently.

New to the API? Start with the getting-started guide

Why there is no streaming

The "Jev streaming" question, answered honestly.

The official docs document no streaming or server-sent-events mode for the evaluation endpoint, and the concept page explains why none is needed: Jev "returns typed decisions and probabilities rather than generated text" and does "not write replies, produce code, or generate explanations". There are no tokens tumbling out one by one, because there are no tokens. You send one request; you get one complete JSON response — typically in tens to low hundreds of milliseconds across the three documented endpoints (80–190 ms per the comparison on /jev-api).

So if you searched for "Jev streaming" hoping to watch an answer form: the answer is already complete before you would have rendered the first chunk. What you can stream is your own workflow — emit "queued → evaluating → decided" events from your handler while the single request is in flight. That is perceived responsiveness, and it is general engineering, not an API feature.

Treat any third-party "streaming Jev" wrapper with suspicion: the model cannot emit a partial decision, so whatever is being streamed is buffered and re-chopped on their side. You are paying latency for theatre.

Endpoints, protocol and the failover adapter, in full

Status and health: checking Jev without a status page

No public status endpoint is documented. Here is the working substitute.

The honest baseline: the official docs publish no /health endpoint and no public status page — we checked the full documentation index on 2026-09-27. The practical substitute is a minimal smoke request and a correct reading of its status code.

The probe below sends the smallest valid payload — a tiny state and one Noul question — and prints only the HTTP status. Run it from the same network path your production code uses: a probe from your laptop tells you about your laptop.

If you consume this site's own relay at /api/jev/evaluate instead, remember its limits are ours, not TypeSafe's: Cloudflare Turnstile verification, a 5-requests-per-10-seconds burst cap, a 32 KB body ceiling, and a daily quota. A 429 from our relay is your site quota, not the upstream rate limit.

For a dashboard, general practice applies: track your 429 rate, p95 latency and 529 frequency as de-facto health metrics, and alert on trends rather than single events.

The smoke test (cURL)
# Minimal smoke test: tiny state, one Noul question, print only the status.
#   200 = healthy · 401 = key problem · 429 = pacing problem
#   529 = provider overloaded · no response = your network path
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "jev-latest",
    "state": "health check probe",
    "questions": {
      "alive": {
        "type": "noul",
        "instructions": "Confirm the evaluation service responds",
        "criteria": {
          "ok": "The service returned any decision",
          "down": "No decision is available"
        }
      }
    }
  }'

Reading the smoke test

200 OK

Healthy. A complete typed decision came back — nothing to fix.

401 Unauthorized

Service reachable, key rejected. Your configuration: missing, rotated or wrong-environment key. Do not retry — fix the credential.

429 Too Many Requests

Service is up and you are over its limit — your pacing or your quota. Back off, then re-run the probe to confirm recovery.

529 Overloaded

Provider-side overload — the official wording is "temporarily overloaded". Wait, then fail over to a secondary endpoint if it persists.

Timeout / no response

Your network path or timeout budget. Check egress, DNS and the abort timer before blaming the API.

It prints only the status code — the decision itself is irrelevant for a health check. Keep the probe under your normal rate: a smoke test that triggers 429s has told you something, but not what you wanted to know.

Probe a real decision in the Playground instead

Jev API errors and rate limits: FAQ

Why do I keep getting 429 from the Jev API?

Because you exceeded the rate limit — the documented instruction is to "back off and retry after a short delay". No numeric limit is published, so the fix is mechanical: read the Retry-After header (the SDKs expose it as retry_after_ms / retryAfterMs), retry with exponential backoff plus jitter, and cap your client-side concurrency. The official SDKs already retry 429 by default (2 retries, honoring Retry-After). If the 429s persist after real backoff, the cause is usually exhausted quota or credits rather than pacing — check your dashboard, because backoff cannot fix billing.

What error codes does the Jev API return?

The HTTP reference documents four: 401 Unauthorized (missing or invalid key), 422 Unprocessable Entity (body failed validation), 429 Too Many Requests (rate limit) and 529 Overloaded (TypeSafe temporarily overloaded) — plus the general 5xx family. The SDKs map these onto typed classes: BadRequestError (400), AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404), UnprocessableEntityError (422), RateLimitError (429), InternalServerError (5xx), plus connection, timeout and response-validation classes. The full table with fixes is above.

How should I retry Jev API timeouts?

Timeouts and connection errors are retryable by default — the SDK retries both. Give every call an explicit timeout budget: the SDK default is a 30-second total budget per call, while our /jev-api failover pattern uses a 1500 ms circuit breaker and switches endpoints. Between attempts use exponential backoff with jitter, and cap the attempt count. Jev decisions are single forward passes measured in tens to low hundreds of milliseconds, so a timeout almost always indicts your budget or your network path, not the model.

Does the Jev API support streaming?

No. The official docs document no streaming or server-sent-events mode, and architecturally none is needed: Jev returns typed decisions and probabilities rather than generated text — it does not write replies, produce code, or generate explanations. One request in, one complete JSON response out. If you want perceived responsiveness, stream your own workflow status ("queued → evaluating → decided") from your handler; that is application engineering, not an API feature. Be suspicious of third-party "streaming Jev" wrappers — the model cannot emit a partial decision.

Is there an official Jev API status page?

None is documented. As of 2026-09-27 the official docs publish no health endpoint and no public status page. The practical substitute: send a minimal valid request from your production network path and read the status — 200 healthy, 401 your key, 429 your pacing or quota, 529 provider overload, no response your network. When you contact support, include the x-typesafe-request-id response header from the failing call; the SDK surfaces it as request_id / requestId on every HTTP error.

What are Jev's actual rate limit numbers?

Not publicly documented — the official references publish no requests-per-second, concurrency or daily-quota figures, and anyone quoting exact numbers is guessing. Derive your own ceiling empirically from the 429 responses and Retry-After headers you receive, and pace your client with a token bucket instead of discovering the limit by hitting it. The documented SDK retry defaults (2 retries, 0.5 s doubling to 5 s, jitter 0.25) describe the retry shape, not the limit itself.