When audio transcription fails, many people first blame the model or doubt its recognition ability. In practice, most failures happen at four layers: the file, authentication, the network, and rate limits. The transcription endpoint accepts multipart/form-data uploads, so its request path is longer than a pure text endpoint, and the diagnostic order should be longer too: check the HTTP status code and error type first, then the request itself, and only then adjust the audio and prompts.

This article separates 401, 400 format errors, 413 file-too-large, 429 rate limits, timeouts, and empty or garbled output, giving a diagnostic path and a reusable piece of handling code. File limits, model names, and error formats may change between versions; the official documentation and on-site documentation govern, and prices follow the model pricing page.

Note: OpenAI and Whisper are trademarks of their respective owners. This article is a technical integration guide only. Interface limits, supported formats, and model names may change; refer to the official documentation and on-site documentation.

1. Establish a diagnostic order first

Symptom Common status codes Frequent cause Direction
Authentication failure 401, 403 Wrong key, missing Bearer, no audio endpoint permission Fix credentials; do not retry
Unsupported format 400 Codec not in the supported list, extension does not match actual encoding Transcode and re-upload
File too large 413 or 400 Exceeds the per-file size limit Compress or split
Too many requests 429 Per-minute request, concurrency, or quota exceeded Back off, queue, split keys
Server error 500, 502, 503 Upstream failure or overload Back off, then retry a limited number of times
Timeout No response or connection dropped Large file, slow upload, long processing Chunk, adjust timeouts
Empty transcript 200 Silence, low volume, language misdetection, encoding anomaly Inspect the audio, not retries

If a request-id appears in the response headers or error body, keep it. When asking a provider to investigate, one request ID plus a minimal reproducible audio sample is worth more than ten screenshots.

2. 401: check headers first, then the key

A 401 means authentication failed; it usually has nothing to do with balance or model capability. Work through this order:

  1. Is the auth header Authorization: Bearer <key>, with neither Bearer nor the space missing?
  2. Are there spaces, line breaks, or quotes around the key? Copying from a chat window is the usual culprit.
  3. Did the environment variable actually load? Print only a prefix to confirm; never write the full key to logs.
  4. Has the key been revoked, does it belong to the current project, and does the account have audio endpoint permission?
  5. Is the base URL https://ai.zhongshangqihang.com/v1, and is the path /v1/audio/transcriptions?
  6. If you are using a gateway, confirm that this entry point exposes audio transcription. Do not assume that because text endpoints work, audio endpoints must work too.

Another common mistake is putting the file into a JSON body. The transcription endpoint requires multipart/form-data; on the command line use curl -F "file=@meeting.mp3", not -d. A wrong body format sometimes shows up as 400, and sometimes as 401 because no valid credentials were read.

3. 400 and 413: format and size problems

The most common 400 is an unsupported format. The formats listed in the official documentation include mp3, mp4, mpeg, mpga, m4a, wav, and webm; the complete list and encoding requirements follow the current documentation. Renaming a file is not transcoding: rename .amr to .mp3 and the server still reads the original codec and returns the same error.

A 413 usually means the file exceeds the per-file size limit. The commonly cited official limit is 25 MB; the documentation governs. There are two ways to handle it:

  • Compress: convert to 16 kHz mono; speech does not need a high bitrate. Use ffmpeg -i input.mp3 -ac 1 -ar 16000 -b:a 64k output.mp3.
  • Split: cut into 10-minute chunks, transcribe each, then merge. Choose cut points near silence and leave a small overlap between chunks so words are not split in half.

Do not force long audio through compression alone. The larger the file, the slower the upload, the higher the timeout probability, and the more expensive the re-upload after failure. Parallel chunking is more reliable, but control concurrency or 413 turns into 429.

4. 429: rate limiting is not the same as insufficient balance

A 429 means the request exceeded a rate, concurrency, or quota limit. Two 429s can carry different error types: one may mean the frequency limit was exceeded, another may mean quota is exhausted. Read the error type first, then decide whether to wait, reduce concurrency, or add credit.

Common triggers include opening too many concurrent batch tasks, sharing one key across several services, retrying immediately after failure and creating a retry storm, long audio holding connections open too long, and low per-minute quotas on free or entry-level accounts.

Handle them in this order: check for Retry-After in the response, then retry with exponential backoff plus jitter; move batch jobs into a queue and cap the number of requests in flight; split keys by project so they do not crowd each other; set retry caps and alerts instead of looping indefinitely. Transcription is billed by audio duration, and resubmitting can lead to duplicate charges, so confirm whether the previous attempt already succeeded before retrying.

5. Timeouts: distinguish slow upload from slow processing

A timeout can occur at three stages: connection establishment, file upload, and server-side processing. If even small files time out, check the network, proxy, and base URL first; if large files consistently time out, prioritize chunking and compression.

Set client timeouts explicitly; do not rely on defaults. Transcription time depends on audio duration, file size, and queueing, so set a generous but finite ceiling, for example two to three minutes, and use task records to distinguish "submitted but not returned" from "never sent." Be careful with automatic retries after a timeout: if the server actually finished processing, a retry produces a second charge and duplicate results.

6. 200 but empty text or garbled output

These two cases produce no HTTP error, and the diagnostic direction is completely different.

Empty transcripts commonly come from: an entirely silent or very quiet recording; a language parameter that does not match the audio; an abnormal channel where the main channel has no sound; or a file that uploads but decodes incorrectly. Run a volume check first, pass language=en explicitly, then convert to 16 kHz mono and try again.

Garbled text or many wrong characters commonly come from language misdetection, heavy dialect or accent, background music mixed with speech, multiple people talking over each other, or a microphone placed too far away. Pass names, product names, and technical terms through the prompt field, and apply a glossary substitution after transcription. If the audio itself is unusable, changing models will not save it; go back to recording and preprocessing instead.

7. Retries and chunking: reusable code

Retries apply only to transient errors. For 401, 400, and 413, changing the request is the only meaningful fix; blind retries just amplify the problem.

import os
import random
import time
from openai import (
    OpenAI,
    APIConnectionError,
    APIStatusError,
    APITimeoutError,
    RateLimitError,
)

client = OpenAI(
    api_key=os.environ["WHISPER_API_KEY"],
    base_url="https://ai.zhongshangqihang.com/v1",
    timeout=180,
)

def transcribe(path: str, model: str = "whisper-1", retries: int = 4) -> str:
    for attempt in range(retries):
        try:
            with open(path, "rb") as f:
                result = client.audio.transcriptions.create(
                    model=model,
                    file=f,
                    language="en",
                    response_format="verbose_json",
                )
            text = (result.text or "").strip()
            if not text:
                raise ValueError("Empty transcript: check volume, language, and channel")
            return text
        except RateLimitError:
            wait = min(2 ** attempt, 30) + random.random()
            print(f"429, retrying in {wait:.1f}s")
            time.sleep(wait)
        except (APITimeoutError, APIConnectionError):
            time.sleep(min(2 ** attempt, 30) + random.random())
        except APIStatusError as exc:
            if exc.status_code >= 500:
                time.sleep(min(2 ** attempt, 30) + random.random())
            else:
                raise  # 400/401/413 need a request change, not a retry
    raise RuntimeError("Retry limit exhausted")

print(transcribe("meeting.mp3"))

Model names follow the on-site documentation; whisper-1 is only a common example. For audio over the size limit, split it with ffmpeg first, call this function on each chunk, and remember to add each chunk's start time back when merging.

8. Logging and support checklist

  1. Record the time, timezone, HTTP status code, and error type.
  2. Record the request-id and whether the request hit the official entry point or a gateway entry point.
  3. Record the file name, actual codec, duration, size, sample rate, and channel count.
  4. Record key parameters such as model, language, and response_format.
  5. Record the retry count, each wait time, and the final result.
  6. Record whether the key is shared, the concurrency at the time, and the batch size.
  7. Prepare a 10-30 second minimal reproducible audio sample that can be shared publicly, with personal information removed.

When submitting to a provider, never send a full key, and never send customer recordings without permission. A minimal sample that reproduces reliably is far more useful than a description that says "it sometimes errors."

FAQ

Both 413 and 400 are file problems; how do I tell them apart?

A 413 generally means the size exceeds the limit, and the fix is compression or chunking; a 400 is more often a format, parameter, or encoding problem, and the fix is transcoding and checking fields. Read the error type and message, not just the status code.

What if 429 never recovers?

First determine whether the error type is a frequency limit or exhausted quota. For frequency, lower concurrency, queue work, and split keys; for quota, add credit or change entry points. Do not keep increasing retries during a sustained 429, or recovery will only take longer.

Should I retry immediately after a timeout?

First judge whether the request may already have completed server-side. Transcription may already be billed, and a direct retry can produce duplicate results and duplicate charges. Use task records or idempotent design to confirm state before resending.

Does an empty transcript mean the model is broken?

Usually not. Check volume, silent segments, the language parameter, channels, and actual encoding first. Testing the same audio through a different entry point quickly separates an audio problem from a service problem.

How do I distinguish a gateway error from an official error?

Keep the request-id and error type, and send the same request to both entry points. If the official entry point works and the gateway entry point fails, the problem is likely gateway configuration or forwarding; if both fail, check the key, network, and audio file.

Next step

Start by signing up for the ZSQH AI Gateway, then use a one-minute audio clip to rehearse a 401, a 413, a 429, and a timeout once each before wiring up production monitoring. Model availability and prices follow the model pricing page.

Related reading: