Skip to content

Errors

Every failure, on REST and on the WebSocket, uses the same envelope.

{
"error": {
"code": "tier_unavailable",
"message": "Tier 'ultra' is not available on this deployment.",
"request_id": "req_01JD4Z2Q8W6M"
}
}
Field Notes
code A stable string. Branch on this.
message Written for a human reading a log. Wording may change, do not parse it.
request_id Quote this when you contact support.

On the WebSocket the same object arrives wrapped in an event, and the socket closes afterwards:

{
"type": "error",
"error": {
"code": "invalid_audio_format",
"message": "Expected float32 PCM. Frame length is not a multiple of 4 bytes.",
"request_id": "req_01JD4Z2Q8W6M"
}
}
Status Meaning
400 The request is malformed, or asks for something that cannot be served.
401 The API key is missing, malformed or revoked.
402 The credit balance is too low to start the request.
403 The key is valid but not permitted to do this.
404 No such path.
413 The uploaded file is larger than the endpoint accepts.
415 The uploaded media type cannot be decoded.
422 A parameter is the right shape but not an accepted value.
429 Rate limited.
500 Something failed on our side.
503 Capacity is unavailable right now.
code Status What to do
invalid_api_key 401 Check the key, or create a new one in the console.
missing_api_key 401 Send Authorization: Bearer utt_live_….
payment_required 402 Add credits. See Pricing and credits.
permission_denied 403 The key cannot use this endpoint.
invalid_request 400 Fix the request. The message says what is wrong.
unsupported_language 422 Use a value from Languages.
unknown_tier 422 Use one of the four tier names.
tier_unavailable 400 This deployment cannot serve that tier. See Availability.
invalid_audio_format 400 Frames must be float32 mono little endian PCM.
unsupported_media_type 415 Batch only. Convert the file.
file_too_large 413 Batch only. Split the file, or stream it.
no_audio_received 400 "eof" arrived before any audio frame did.
rate_limited 429 Back off and retry.
internal_error 500 Retry. If it persists, send us the request_id.
capacity_unavailable 503 Retry with backoff.

utter is prepaid. If the balance is below the minimum hold, the request is refused before any transcription happens, so nothing is transcribed and nothing is debited.

{
"error": {
"code": "payment_required",
"message": "Credit balance is too low to start this request.",
"request_id": "req_01JD4Z2Q8W6M"
}
}

On a WebSocket this arrives as an error event in place of ready, and the socket closes. Retrying will not help until credits are added. See Pricing and credits.

Retry 429, 500 and 503, with exponential backoff and jitter.

Do not retry 400, 401, 402, 403, 413, 415 or 422. The same request will fail the same way. The one case worth special handling is tier_unavailable: retry once on the fallback tier you chose in advance, then give up.

The WebSocket may also close without an error event, from a network drop or an idle timeout. Treat any close before you have seen done as a failure of that session, and handle it the same way:

  • Keep every final you received. Those segments are settled.
  • Discard the last partial. It was never committed.
  • If you reconnect, resume from the end of your last final rather than from the start, so you do not pay twice for audio already transcribed.
import time
import requests
RETRY = {429, 500, 503}
def transcribe(path: str, key: str, attempts: int = 4) -> dict:
for attempt in range(attempts):
with open(path, "rb") as f:
response = requests.post(
"https://api.utter.cc/v1/transcribe",
headers={"Authorization": f"Bearer {key}"},
files={"file": (path, f, "audio/wav")},
timeout=300,
)
if response.status_code == 200:
return response.json()
error = response.json().get("error", {})
if response.status_code not in RETRY or attempt == attempts - 1:
raise RuntimeError(
f"{error.get('code')}: {error.get('message')} "
f"(request_id {error.get('request_id')})"
)
time.sleep(2**attempt)
raise AssertionError("unreachable")