Error Codes

Standard HTTP status codes plus a fixed reason taxonomy so failures are actionable without log-grepping.

HTTP status codes

CodeTitleWhat it meansFix
400Bad RequestThe request is malformed or missing required parameters.Check that you're sending the audio and localeCode fields correctly.
401UnauthorizedMissing or invalid API key in the Authorization header.Ensure your API key starts with pk_live_ and is sent as Bearer token.
402Account Limit ReachedDeveloper keys only. ASR: this month's free minutes are used and the account balance is empty. TTS: the account balance is empty. Free-tier character caps return 429, not 402.Top up at /billing while signed in.
403ForbiddenAccount suspended, API key revoked, or a tier/entitlement gate refused the request (device key requesting Quality tier on free, or device key submitting a custom-reference voice).Check key status on /keys; upgrade to premium for Quality tier on a device key; use a preset voice for device-key calls.
412Precondition FailedTwo cases. (1) A custom reference clip sent from a signed-in portal session on an account with no active developer API key; preset and saved voices need no key. (2) A custom-reference call whose key has not accepted the custom-voice rights attestation.Create a key from /keys and accept the custom-voice attestation modal on the same page before custom-reference calls.
413Payload Too LargeASR: the audio exceeds your key's size limit (code audio_too_large), or the whole request exceeds the most the endpoint accepts (code payload_too_large). TTS: the text exceeds the configured max length. A very large body can also end in a closed connection before the 413 is read.For ASR, compress or trim the audio (live limit at GET /v1/asr/limits), or send a long recording as a transcription job. For TTS, shorten the text (live limit at GET /v1/tts/limits).
429Rate Limited / Quota ExceededTwo cases. (1) Per-minute/per-hour rate cap on the endpoint. Back off and retry. (2) Device-key character quota for TTS: free-tier monthly cap, or premium daily safety net. Quota state is per-key and resets monthly/daily; pre-flight rejections do not consume quota.For rate caps, back off. For quota caps, upgrade entitlement or wait for the reset window. Live caps at GET /v1/tts/limits.
500Internal Server ErrorAn unexpected error occurred on Phonetico's side.Retry the request. The response body's reason field will narrow it down; if internal_error keeps recurring, contact support with the usageId.
502Bad GatewayThe inference service returned a non-2xx response (e.g., couldn't decode the audio).Check the audio file is a supported format. The detail field carries the upstream error.
503Service UnavailableThe inference service is unreachable from our gateway.Transient; retry. If it persists, check status updates.
504Gateway TimeoutThe inference service didn't respond within the upstream timeout.Try a shorter audio clip or split the audio into chunks. Operator-tunable; live value is at GET /v1/asr/limits.

Failure response shape

Every non-2xx response uses a stable shape so clients can branch on the failure type without parsing prose. The shape differs slightly between ASR and TTS today.

ASR (/v1/asr*)

{
  "error": "ASRServiceError",
  "code": "asr_service_error",
  "reason": "upstream_timeout",
  "detail": "Upstream inference timed out after 120000ms (limit 120000ms).",
  "usageId": "47cdb885-8f03-4079-9c3a-cce2392d8ab9"
}

TTS (/v1/tts*)

{
  "error": "QuotaExceeded",
  "code": "quota_exceeded",
  "message": "Monthly free character limit reached. Upgrade for more."
}

TTS branches on code (the same taxonomy ASR exposes via reason). The shapes will converge in a future release; until then, treat the taxonomy below as the single source of truth and branch on the field your endpoint returns.

reason taxonomy

Treat any unrecognized value as internal_error. New reasons may be added; existing ones won't be removed without a deprecation notice.

reasonHTTPWhat happened
upstream_timeout504Inference exceeded the upstream timeout. Try shorter audio.
upstream_http_error502Inference returned non-2xx. detail carries the upstream message, often an audio-decode problem.
upstream_connection_error503Couldn't reach the inference service. Transient; retry.
upstream_busy503ASR and TTS. Every transcription or synthesis slot stayed in use for as long as a request may wait. Not charged. Retry after the Retry-After header (seconds). Speaker labels on a synchronous call are left out instead when the diarizer stays busy; the transcript is returned.
language_undetermined422ASR with localeCode auto. The language of the audio could not be settled among the languages offered for transcription. Not charged. Name the language with localeCode and resend.
balance_drained_during_call402Account hit its limit between the pre-flight check and the call completing. Visit /billing while signed in for details.
caller_disconnected499TTS. The connection closed before the audio was ready, so the synthesis was stopped. Not charged; no reply reaches the caller, and the reason appears in usage. For long text, use POST /v1/tts/jobs, which needs no open connection.
internal_error500Anything else. Carry the usageId when reporting.
attestation_required412TTS only. The API key has not accepted the custom-voice rights attestation. Visit /keys and accept the modal on the relevant key; future calls from that key go through.
text_too_long413TTS only. The submitted text exceeds the configured max length. Live cap at GET /v1/tts/limits.
voice_not_active404TTS preset-voice path only. The requested voice_id is withdrawn or doesn’t exist. List active voices at GET /v1/tts/voices.
preview_not_offered404GET /v1/tts/voices/:id/preview only. The voice’s language has no preview sentence yet; the voice itself can still be used.
quota_exceeded429TTS device-key only. Per-key monthly character cap reached. Defaults: free 1,500 chars/month, premium 50,000 chars/month. Cap resets at the start of the next calendar month. Live caps at GET /v1/tts/limits.
daily_limit_reached429TTS premium device-key only. Daily safety-net cap reached (default 5,000 chars/day). Resets at midnight UTC.
quality_tier_premium_only403TTS free-tier device-key only. Requested options.qualityTier = "quality"; that tier is gated to premium. Use "fast" or "balanced", or upgrade.
invalid_voice_spec400TTS only. The request body must specify exactly one of voice.preset or voice.reference.
reference_audio_too_large413TTS custom voice. The decoded voice.reference.audio is larger than maxReferenceAudioBytes (GET /v1/tts/limits). A 5 to 15 second mono clip is enough; the model keeps at most 12 s.
speed_out_of_range400TTS only. options.speed is outside speedMin to speedMax (GET /v1/tts/limits). 1.0 is the voice’s own pace; omit speed to use it. Nothing was charged.
reference_audio_too_long400TTS. Either a device key’s voice.reference.audio runs longer than deviceMaxReferenceAudioSeconds (GET /v1/tts/limits), or the voice sample is longer than the model reads (limits.maxReferenceSeconds on GET /v1/models) and could not be cut to fit at a word boundary. The message names the lengths. Nothing was charged.
reference_audio_unreadable400TTS custom voice. voice.reference.audio could not be read as audio. Send WAV, MP3, M4A, OGG or WebM with one speaker.
insufficient_balance402Developer-key only. Before the call, its cost is more than the balance not already held by calls in progress: for ASR, the audio's cost beyond this month's free minutes; for TTS, the text's cost at the chosen quality tier. The message names the amount needed and the amount available. Nothing was charged. Top up at /billing.
key_scope403The key is not allowed to use this product (transcription, transcription jobs or speech synthesis); its products are set on the API Keys page. Nothing was charged.
key_network_not_allowed403The key only accepts requests from its allowed networks and this request came from elsewhere. Add the server's network on the API Keys page or use another key. Nothing was charged.
terms_not_accepted403Developer-key and portal (Transcribe, Speak). Phonetico requires acceptance of the current developer terms before API use, and this account has not accepted the version named. The message and the documents field name each document and its version. Accept them in the portal (the prompt above every page); the next request goes through. Nothing was charged.
key_spend_cap402Developer-key only. The call's cost would take this key past the monthly spend cap set for it on the API Keys page (the calendar month, UTC; what its calls in progress hold counts). The message names the cap, what the key has spent and what the call needs. Nothing was charged. Raise the cap or use another key.
asr_unreadable_audio400ASR batch (POST /v1/asr and /v1/asr/upload). The audio could not be read as a container (WAV, FLAC, OGG/Opus, MP3, WebM), so its length could not be measured. Raw PCM is for streaming only. Nothing was charged.
asr_audio_too_long400ASR. The measured length of the audio is over the per-request limit for this key. Live limit at GET /v1/asr/limits. Nothing was charged.
invalid_developer_key403Developer key has no associated account row (should be impossible under normal signup flow). Contact support with the affected key prefix.

Every failed call is also visible on Usage. The detail page renders the same reason and detail so support tickets can cite a usageId rather than a screenshot.