Errors
Errors are returned as application/problem+json (RFC 9457). Branch on the code field, never on the human-readable detail string — code is stable, detail is not.
Shape
{
"type": "https://vizvuz.com/docs/errors#quota_exceeded",
"title": "Quota exceeded",
"status": 402,
"code": "quota_exceeded",
"detail": "Monthly character quota exhausted and the overage cap is reached.",
"request_id": "01J8ZC4M9XK3P7QW2NRT5V6YAB"
}
Some errors add fields — rate_limited adds limit, quota_exceeded adds character_limit, overage_cap_minor and period_end. Ignore fields you do not know.
Codes
| Status | code | What happened | What to do |
|---|---|---|---|
| 400 | validation_error | A parameter is missing or the wrong type | Fix the request; the detail names the parameter |
| 400 | invalid_target_lang | Unknown target language | Check /v1/languages?type=target |
| 400 | invalid_source_lang | Unknown source language | Check /v1/languages?type=source |
| 400 | too_many_texts | More than 50 texts in one request | Split the batch |
| 401 | unauthorized | Missing, malformed or unknown key | Check the header format |
| 401 | key_revoked | The key was revoked | Deploy a current key |
| 403 | glossary_limit_reached | Plan glossary limit reached | Delete one or upgrade |
| 404 | not_found | Unknown endpoint or resource | Check the path |
| 404 | glossary_not_found | Unknown glossary, or not yours | Check glossary_id |
| 405 | method_not_allowed | Wrong HTTP verb | See the Allow header |
| 402 | quota_exceeded | Included volume used and overage cap reached | Raise the cap or upgrade |
| 402 | credit_exhausted | Prepaid credit is used up | Top up |
| 400 | text_too_long | More characters than your plan allows per request | Split the text |
| 429 | rate_limited | Too many requests per second | Back off, see Retry-After |
| 503 | engine_unavailable | The translation engine did not answer | Retry with backoff |
| 503 | service_unavailable | A backing service (not the engine) is temporarily unavailable | Retry with backoff |
| 500 | internal_error | A bug on our side | Retry once, then contact support with request_id |
Which errors are worth retrying
| Retry | Codes |
|---|---|
| Yes, with backoff | rate_limited, engine_unavailable, service_unavailable, internal_error |
| No — the request will fail again | every other 4xx |
Failed requests are not billed. A 429 and a 503 cost nothing, and a rolled-back reservation returns the characters to your quota or credit immediately.
A retry loop that behaves
import time, requests
def translate(payload, key, attempts=4):
for attempt in range(attempts):
response = requests.post(
"https://api.vizvuz.com/v1/translate",
headers={"Authorization": f"Bearer {key}"},
json=payload,
timeout=35,
)
if response.status_code < 400:
return response.json()
problem = response.json()
if problem["code"] not in ("rate_limited", "engine_unavailable", "internal_error"):
raise RuntimeError(f"{problem['code']}: {problem['detail']}")
wait = float(response.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
raise RuntimeError("giving up after retries")
Always log the request id
Every response — successful or not — carries X-Request-Id. Log it next to your own correlation id. With it we can find a single request in seconds; without it, investigating a report means guessing.
What we never return
We never surface an error, header or message from the translation engine. Engine failures are logged internally without their payload and reported to you as engine_unavailable. Your text never appears in our logs.
Last updated Sep 1, 2026, 12:00 AM