Error Reference
This is the single source of truth for every error the Theta One API can return.
All endpoints — /transcribe, /analyze-native, /pronunciation, /pronunciation-simple — share the codes below.
On failure, the API returns:
- an HTTP status code from the table below, and
- a JSON body containing an error message.
Error code summary
| HTTP | Code string | Retry? | Category | When it happens |
|---|---|---|---|---|
400 | Bad Request | No | Client | Malformed request: invalid JSON in options, missing gold_text, invalid audio, NotAllWordsSpokenError, etc. |
401 | Unauthorized | No | Client | Missing / incorrect / inactive / expired API key. |
402 | PAYMENT_REQUIRED | No | Billing | Out of credits or plan doesn't include the endpoint being called. |
429 | RATE_LIMIT_EXCEEDED | Yes — with backoff | Quota | Per-key RPM exceeded. |
500 | INTERNAL_SERVER_ERROR | Yes — limited retries | Server | Unexpected server-side failure. |
Detailed entries below.
400 — Bad Request
The server could not accept the request as sent. Not retryable until the request is fixed.
The response body is always {"detail": "<message>"}. Branch on the message string:
detail string | Root cause | Fix |
|---|---|---|
"Invalid options format. Must be a valid JSON string." | options / transcribe_options was not valid JSON | Wrap the object with json.dumps(...) |
"Invalid options: <...>" | JSON parsed but failed Pydantic validation | Check option field names and types |
"gold_text is required" | options did not include gold_text (pronunciation endpoints) | Add gold_text to the options JSON |
"Invalid native_speech_components_json format. Must be a valid JSON string." | native_speech_components_json was not valid JSON | Pass json.dumps(speech_components_array) |
"Invalid native_speech_components format" | JSON parsed but shape didn't match the FormattedWord | FormattedPause schema | Use the exact speech_components returned by /analyze-native; don't mutate fields |
"Not all words are spoken." | NotAllWordsSpokenError — student's audio does not cover every word in gold_text | Re-prompt the user to re-record |
"Invalid audio format" | Audio file is missing, empty, or not decodable | Send a real .wav or .mp3 |
Agent tip: Do not auto-retry any 400. For "Not all words are spoken.", re-prompt the user rather than retrying silently.
401 — Unauthorized
The API key was rejected. Response body is {"detail": "<message>"}.
Possible detail strings:
detail | Root cause |
|---|---|
"Invalid API key" | x-api-key header missing, malformed, not prefixed sk-theta-, or not found in the database |
"API key is not active, please check your API key..." | Key exists but has been deactivated or its Expires At has passed |
Fix: Verify the key in the API Console. If you suspect a leak, deactivate it immediately and issue a new key.
Do not auto-retry 401 — you will just waste quota.
402 — PAYMENT_REQUIRED
Billing-related refusal. Two distinct root causes share this code — branch on the detail string:
detail | Root cause | Fix |
|---|---|---|
"Insufficient credits, please check your credit balance" | Prepaid credit balance is zero (or would go negative) | Top up, or move to an Enterprise postpaid contract |
"This feature requires a Starter plan or higher..." | Pronunciation endpoint called from a Free-plan account | Upgrade the plan at console.thetaone.co/billing |
Pronunciation endpoints (/analyze-native, /pronunciation, /pronunciation-simple) are allowed only on starter, pro, enterprise plan tiers.
Agent tip: Don't retry 402. Surface the billing reason to the user and link them to Pricing or Prepaid Credits.
429 — RATE_LIMIT_EXCEEDED
You exceeded the per-API-key RPM quota. Response body: {"detail": "Rate limit exceeded. Please try again later."}.
| Plan | RPM |
|---|---|
| Free | 30 |
| Starter | 100 |
| Pro | 500 |
| Enterprise | Unlimited (by contract) |
The limit resets within a short time window, so a few seconds of backoff is usually enough to recover.
Recommended reaction: Exponential backoff with jitter. Full guidance: Rate Limits & Retries.
500 — INTERNAL_SERVER_ERROR
Server-side failure, not caused by your request. Response body: {"detail": "Internal server error"}.
Recommended reaction:
- Retry up to 2–3 times with exponential backoff.
- If the error persists, email support@thetaone.co with:
- the timestamp (UTC) of the failing request,
- the API key you used (the
sk-theta-prefix is enough — never send the full key outside of HTTPS support), - a minimal reproduction if possible.
Related statuses you may occasionally see:
413 Payload Too Large— the upload is too big. Shrink the file.504 Gateway Timeout— upload + processing did not complete within the request timeout. Shorten the audio or check network throughput.
Named errors
| Name | Endpoint(s) | Detail string | Meaning |
|---|---|---|---|
NotAllWordsSpokenError | /analyze-native, /pronunciation, /pronunciation-simple | "Not all words are spoken." | The audio does not cover every word in gold_text. Ask the speaker to re-record the full sentence. |
Minimal error-handling recipe (Python)
import time
import requests
def post_with_retry(url, *, headers, files=None, data=None, max_retries=3):
backoff = 1.0
for attempt in range(max_retries):
r = requests.post(url, headers=headers, files=files, data=data, timeout=30)
if r.status_code == 200:
return r.json()
# Non-retryable client errors — fail fast.
if r.status_code in (400, 401, 402):
raise RuntimeError(f"{r.status_code} {r.text}")
# Retryable.
if r.status_code in (429, 500, 502, 503, 504):
if attempt == max_retries - 1:
raise RuntimeError(f"{r.status_code} after {max_retries} attempts: {r.text}")
time.sleep(backoff)
backoff *= 2
continue
# Anything else — don't retry silently.
raise RuntimeError(f"Unexpected {r.status_code}: {r.text}")