Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Handle Venice API errors correctly. Covers the StandardError / DetailedError / ContentViolationError / X402InferencePaymentRequired body shapes, every meaningful status code (400, 401, 402, 403, 415, 422, 429, 500, 503, 504), the 402 PAYMENT-REQUIRED header used by x402 inference, 422 content-policy suggested_prompt retry pattern, 429 rate-limit headers, and an exponential-backoff retry strategy with idempotency.
.claude/skills/sediman-agent-venice-errors/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-17 | ✗→✓ | ▲ Improved | 43% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 74% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 58% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 48% | 0% |
Every Venice endpoint returns one of four error shapes. Knowing which shape you got tells you how to react.
StandardError — simple messageThe default shape for 4xx/5xx. Emitted when there's nothing structured to surface.
json{ "error": "Unauthorized" }
DetailedError — Zod validation failureUsed for some 400 responses on malformed request bodies. When present, details is a Zod format() tree (_errors recursively keyed by field) alongside a flat issues array. Many 400s are plain StandardError without details — always handle both.
json{ "error": "Invalid request", "details": { "_errors": [], "messages": { "_errors": ["Field is required"] } }, "issues": [ { "code": "invalid_type", "path": ["messages"], "message": "Field is required" } ] }
Render details / issues to the user so they can fix the input; don't retry — the request shape is wrong.
ContentViolationError — 422 content policyReturned when a prompt trips content policy. suggested_prompt (a model-provided safe alternative) is currently emitted by the audio generation pipeline (/audio/queue, /audio/retrieve); image and video endpoints return { error: "Content policy violation" } without suggested_prompt.
json{ "error": "Content policy violation", "suggested_prompt": "A cinematic instrumental track inspired by stormy weather and dramatic tension." }
Pattern — when suggested_prompt is present, retry once with prompt = suggested_prompt if the user consents.
X402InferencePaymentRequired — 402 on x402 inference callsReturned only when the caller authenticated with SIWE and has insufficient credit. Discriminated by code: "PAYMENT_REQUIRED".
json{ "error": "Payment required", "code": "PAYMENT_REQUIRED", "message": "Insufficient x402 balance", "suggestedTopUpUsd": 10, "minimumTopUpUsd": 5, "supportedTokens": ["USDC"], "supportedChains": ["base"], "topUpInstructions": { "step1": "POST /api/v1/x402/top-up with no payment header to get payment requirements", "step2": "Sign a USDC transfer authorization using the x402 SDK (createPaymentHeader)", "step3": "POST /api/v1/x402/top-up with the signed X-402-Payment header", "receiverWallet": "<RECEIVER_WALLET_ADDRESS>", "tokenAddress": "<USDC_TOKEN_ADDRESS>", "tokenDecimals": 6, "network": "eip155:8453", "minimumAmountUsd": 5 }, "siwxChallenge": { ... SIWE template ... } }
The PAYMENT-REQUIRED response header carries a base64-encoded x402 v2 paymentRequired object (x402Version, error, resource, accepts[], optional extensions) — it is not the same JSON as the body. Protocol-level clients parse the header; human-facing clients parse the richer body. See venice-x402.
| Status | Body | Meaning | What to do | |---|---|---|---| | 400 Bad Request | DetailedError | Malformed input. Zod details identifies the field. | Fix and re-send. Don't retry. | | 401 Unauthorized | StandardError | Missing / invalid Bearer API key or SIWE. | Rotate credentials. Don't retry. | | 402 Payment Required | Bearer: StandardError with the configured message (e.g. { "error": "Insufficient balance" } — the handler's default path does not attach a code field). SIWE: X402InferencePaymentRequired + PAYMENT-REQUIRED header. | Out of DIEM/USD/wallet credit. | Bearer: top up at venice.ai. SIWE: run the x402 top-up flow. | | 403 Forbidden | StandardError | Valid auth but not entitled. Typical: trial-limited endpoint, beta model, API-key consumption cap hit, SIWE signer ≠ path wallet. | Don't retry. Investigate entitlements. | | 415 Unsupported Media Type | StandardError | Wrong Content-Type (e.g. JSON sent to a multipart endpoint, or vice versa). | Fix headers. Don't retry. | | 422 Unprocessable Entity | ContentViolationError on image/audio/video generation; plain { error } on other routes (e.g. ASR validation errors). | Content policy violation on generation paths; schema-ish validation on others. | On audio generation, optionally retry once with suggested_prompt. On others, fix input. | | 429 Too Many Requests | StandardError | Rate limit cap tripped. Also returned by /crypto/rpc/{network} when credit-per-day or concurrency cap tripped. | Honor X-RateLimit-* headers, back off with jitter. | | 500 Internal Server Error | StandardError | Unexpected failure. | Retry with exponential backoff + idempotency key where supported. | | 503 Service Unavailable | StandardError | Upstream model / service temporarily down. | Retry with backoff. Consider a fallback model. | | 504 Gateway Timeout | StandardError | Upstream slow. Mostly on /chat/completions with huge contexts. | Switch to stream: true or shorter prompts. |
429)Emitted on /crypto/rpc/{network}:
| Header | Meaning | |---|---| | X-RateLimit-Limit | Per-minute request cap for your tier (paid = 100, staff = 1000 on crypto RPC). | | X-RateLimit-Remaining | Requests remaining in the current 60-second window. | | X-RateLimit-Reset | Unix timestamp in seconds when the window resets. |
Additionally, LlmInferenceError model-overloaded conditions set a Retry-After header (seconds) on the 429 — honor it when present.
Inference endpoints (chat, image, audio, video) use a per-API-key tier defined via /api_keys/rate_limits. See venice-api-keys to pre-fetch your caps, and venice-billing for DIEM/USD usage.
402 (x402)| Header | Notes | |---|---| | PAYMENT-REQUIRED | Base64-encoded JSON of the x402 v2 paymentRequired object (x402Version, error, resource, accepts[], optional extensions['sign-in-with-x']). Protocol-level discovery — parse even if you don't parse the JSON body. |
400 — bad input. Fix the request.401 — bad auth. Fix credentials.403 — not entitled. Don't hammer.415 — wrong Content-Type.402 (x402) — run top-up then retry.402 (Bearer) — surface to user; top up at venice.ai.422 with suggested_prompt — one retry with the safer prompt.429 — back off for at least X-RateLimit-Reset - now(). Add jitter.500 / 503 / 504 — exponential backoff (e.g. 0.5s, 1s, 2s, 4s, 8s), capped at ~30s. 3–5 retries max.Idempotency-Key (e.g. on /crypto/rpc/{network}) so retries can't double-bill state-mutating calls.tsasync function callVenice<T>(fn: () => Promise<Response>): Promise<T> { const maxRetries = 5 let delay = 500 for (let attempt = 0; attempt <= maxRetries; attempt++) { const res = await fn() if (res.ok) return res.json() as Promise<T> const body = await res.clone().json().catch(() => ({})) const { status } = res if ([400, 401, 403, 415].includes(status)) { throw Object.assign(new Error(body.error ?? 'Venice error'), { status, body }) } if (status === 402 && body.code === 'PAYMENT_REQUIRED') { await topUpX402(body.suggestedTopUpUsd) continue } if (status === 422) { throw Object.assign(new Error('Content policy'), { status, body }) } if (status === 429) { const retryAfterSec = Number(res.headers.get('retry-after')) const resetSec = Number(res.headers.get('x-ratelimit-reset')) const waitMs = !Number.isNaN(retryAfterSec) && retryAfterSec > 0 ? retryAfterSec * 1000 : !Number.isNaN(resetSec) && resetSec > 0 ? Math.max(resetSec * 1000 - Date.now(), delay) : delay await sleep(waitMs + Math.random() * 250) delay *= 2 continue } if (status >= 500 && attempt < maxRetries) { await sleep(delay + Math.random() * 250) delay *= 2 continue } throw Object.assign(new Error(body.error ?? 'Venice error'), { status, body }) } throw new Error('Exceeded max retries') }
Streaming responses (stream: true on chat, TTS, video-queue progress) deliver mid-stream errors as SSE events:
data: {"error": {"type": "…", "message": "…"}}Treat them as terminal — the underlying connection is closed. The HTTP status is 200 because a successful stream can't be changed mid-flight.
When present on a response, keep the X-Request-ID header. Include it in support tickets — Venice keys diagnostic logs by this ID. /crypto/rpc/* routes set it explicitly; many inference routes also include it, but don't assume it's universal — fall back to your own client-side correlation ID.
402 from /x402/top-up with no X-402-Payment header is the expected discovery response, not an error. See venice-x402.500 on /chat/completions with a huge file upload often means the upstream model chose to abort — reduce max_tokens / image size rather than blindly retrying.429 on /crypto/rpc/{network} may mean the 24-hour credit cap tripped, not the per-minute one. Check customMessage.DetailedError.details is a Zod _errors tree, not a flat map. Walk it recursively.X-Rate-Limit variants — treat any header whose name starts with X-RateLimit as advisory.stream chunk as an error — send-keepalives look like data: [DONE] or empty lines.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-16 | pass→pass | 10,709 | 7,574 | -29% | 1 | 1 | 0% | 2,003 | 4,491 | +124% | 0 | 0 | — |
case-17 | fail→pass | 20,416 | 12,472 | -39% | 1 | 1 | 0% | 3,683 | 5,283 | +43% | 0 | 0 | — |
case-18 | fail→pass | 15,599 | 11,247 | -28% | 1 | 1 | 0% | 2,838 | 4,930 | +74% | 0 | 0 | — |
case-02 | fail→pass | 19,181 | 14,441 | -25% | 1 | 1 | 0% | 3,711 | 5,850 | +58% | 0 | 0 | — |
case-08 | pass→pass | 17,957 | 11,732 | -35% | 1 | 1 | 0% | 3,235 | 5,038 | +56% | 0 | 0 | — |
case-15 | pass→pass | 13,119 | 11,786 | -10% | 1 | 1 | 0% | 2,514 | 5,436 | +116% | 0 | 0 | — |
case-01 | pass→pass | 19,594 | 21,694 | +11% | 1 | 1 | 0% | 3,552 | 7,145 | +101% | 0 | 0 | — |
case-03 | pass→pass | 24,886 | 21,723 | -13% | 1 | 1 | 0% | 4,946 | 7,101 | +44% | 0 | 0 | — |
case-04 | fail→pass | 31,876 | 11,589 | -64% | 1 | 1 | 0% | 3,302 | 5,280 | +60% | 0 | 0 | — |
case-05 | fail→fail | 12,948 | 10,007 | -23% | 1 | 1 | 0% | 2,423 | 4,868 | +101% | 0 | 0 | — |
case-06 | fail→pass | 18,320 | 11,205 | -39% | 1 | 1 | 0% | 3,493 | 5,177 | +48% | 0 | 0 | — |
case-07 | pass→pass | 18,017 | 12,175 | -32% | 1 | 1 | 0% | 3,227 | 5,274 | +63% | 0 | 0 | — |
case-09 | fail→pass | 14,492 | 12,154 | -16% | 1 | 1 | 0% | 2,878 | 5,262 | +83% | 0 | 0 | — |
case-10 | pass→pass | 26,026 | 9,092 | -65% | 1 | 1 | 0% | 2,463 | 4,688 | +90% | 0 | 0 | — |
case-11 | pass→pass | 17,944 | 15,201 | -15% | 1 | 1 | 0% | 3,205 | 5,959 | +86% | 0 | 0 | — |
case-12 | fail→fail | 12,160 | 7,016 | -42% | 1 | 1 | 0% | 2,251 | 4,211 | +87% | 0 | 0 | — |
case-13 | pass→pass | 17,724 | 10,622 | -40% | 1 | 1 | 0% | 3,206 | 4,865 | +52% | 0 | 0 | — |
case-14 | fail→pass | 15,780 | 12,764 | -19% | 1 | 1 | 0% | 2,991 | 5,491 | +84% | 0 | 0 | — |
case-19 | pass→pass | 17,042 | 13,370 | -22% | 1 | 1 | 0% | 3,329 | 5,698 | +71% | 0 | 0 | — |
case-20 | pass→pass | 8,119 | 6,626 | -18% | 1 | 1 | 0% | 1,506 | 4,128 | +174% | 0 | 0 | — |
case-21 | pass→pass | 9,933 | 10,764 | +8% | 1 | 1 | 0% | 1,921 | 4,994 | +160% | 0 | 0 | — |
case-22 | fail→pass | 12,934 | 7,241 | -44% | 1 | 1 | 0% | 2,220 | 4,414 | +99% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.