OCR API failures fall into roughly three groups: the request was never accepted (401, 413, 415), the request was rate-limited or timed out (429, 5xx), or the request succeeded but the result is unusable (empty recognition, garbled text, low confidence). The first two groups can usually be located from the status code; the third is most often misdiagnosed as "the API is broken."

This article gives a troubleshooting order by symptom, image preprocessing methods, retry strategies, and logging fields, followed by error-handling code you can reuse directly.

Note: OCR interfaces, error codes, and quota rules differ significantly between vendors. This article uses common behavior as of October 2026 as examples; the provider's documentation and actual responses govern. OpenAI, Google, Microsoft, Alibaba Cloud, Tencent Cloud, Baidu AI Cloud, Tesseract, PaddleOCR, and other names are trademarks or product names of their respective owners; this article is for technical explanation only.

1. Preserve four pieces of evidence first

Before troubleshooting, do not rush to retry. Save this information first:

  • The HTTP status code and error.code / error.message in the response body;
  • The request ID or x-request-id in the response headers;
  • The original image format, file size, pixel dimensions, and EXIF orientation;
  • The call time, endpoint address, and model name used.
Status code Common meaning What to check first
400 Invalid parameters or image content Base64, URL, field names, whether the image is corrupted
401 Authentication failed Whether the key is missing, expired, or contains spaces
403 No permission or quota restricted Model permissions, project status, account balance
413 Request body or file too large File size, pixel dimensions, PDF page count
415 Unsupported media type Content-Type, file extension, actual encoding
429 Rate limit or concurrency exceeded QPS, concurrency, Retry-After
500/502/503/504 Upstream failure or timeout Retries, degradation, service status page

2. 401 and 403: verify identity first, then permissions

A 401 means the server does not recognize the key. Common causes: the Authorization: Bearer header is missing, the key was copied with extra spaces or a line break, the environment variable is empty, the key has been rotated, or a key from platform A was sent to platform B's address.

Troubleshoot with a minimal request first rather than guessing inside the full business flow. Confirm the value actually sent in the request header, not just what the configuration file says. A 403 usually means the key is valid but lacks permission for that model or endpoint, or the account has hit a quota limit or the project is disabled.

Also note: gateway keys and vendor-native keys usually cannot be mixed. When changing addresses, verify the key, model name, and request path together.

3. 413 and 415: file too large, format incorrect

A 413 does not mean "the image content cannot be recognized"; the request was rejected at the transport layer. Common triggers:

  • A single image exceeds the endpoint's size limit;
  • Pixel dimensions are too large, and memory exceeds the limit after decoding;
  • A PDF has too many pages, or multiple pages were uploaded at once;
  • Base64 encoding inflates size by about one third, pushing a file that was just under the limit over it.

A 415 is usually about format: the endpoint accepts only JPG and PNG but received HEIC, TIFF, or BMP; the extension says JPG while the actual content is WebP; or Content-Type was set to application/json when the endpoint expects multipart/form-data.

Recommended image preprocessing order:

  1. Read the image with an image library and confirm the real format; do not trust the extension.
  2. Resize proportionally with a limit on the long edge, for example down to 2000–3000 pixels before testing.
  3. Correct the EXIF orientation so text is not sideways and recognition does not come back empty.
  4. Convert to a format the endpoint supports and control compression quality so characters do not become blurry.
  5. Split multi-page PDFs and process page by page, or use an endpoint that accepts whole documents.
  6. Record size and dimensions before and after processing so you can trace which step crossed the limit.

Do not compress an image until it is unreadable just to get past a 413. After over-compression, character strokes merge, the endpoint returns success, and the result quality is poor.

4. 429: rate limits, concurrency, and retry pacing

A 429 means "requests are arriving too fast or in too large a volume." Common dimensions include requests per second, requests per minute, concurrency, and quotas measured in tokens or pages. Even when total volume is under the limit, instantaneous concurrency can trigger throttling.

Handling principles:

  • Read Retry-After and wait as instructed when present;
  • Otherwise use exponential backoff with random jitter so all clients do not retry at once;
  • Retry only 429 and 5xx; retrying 401, 413, and 415 is pointless;
  • Set a maximum retry count and total timeout to prevent task pile-up;
  • Put burst traffic into a queue and send it smoothly according to what the server can handle.

More retries are not always better. For endpoints billed per call, whether failed requests are charged depends on the provider's rules, and blind retries can amplify cost.

5. Timeouts and 5xx: distinguish network problems from upstream problems

Separate connection timeouts from read timeouts. A connection timeout usually points to network, DNS, proxy, or firewall issues; a read timeout means the request arrived but processing took too long, which is common with large images, multi-page PDFs, or upstream queuing.

Set a dedicated timeout for OCR requests rather than reusing the short timeout of a normal text endpoint. Synchronous endpoints suit small images; large documents should use asynchronous tasks plus polling, which significantly lowers the timeout rate. For 5xx, retry once or twice, then switch to the fallback; record the request ID and ask the provider to check.

6. Empty results: a successful request does not mean a usable result

"Empty recognition" is the easiest category to misjudge. The endpoint returns 200, but the text is an empty string, contains only punctuation, or all fields are null. Troubleshoot in this order:

  • Does the image actually contain text: open the original image and confirm it is not a blank page or a pure picture.
  • Is the orientation correct: phone photos often carry rotation metadata, and the server may not process EXIF.
  • Is the text too small: small text in a full-page thumbnail often becomes recognizable after cropping and enlarging.
  • Is contrast too low: light gray text, shadows, glare, and bleed-through all reduce recall.
  • Does the language match: using a Chinese model on Russian or English documents may output only scattered characters.
  • Is the layout out of scope: multi-column, vertical text, tables, and stamps covering text are easily missed by traditional OCR.
  • Does the endpoint return only structured fields: some endpoints put full text in another field while the code reads only text.

If the image is clear, correctly oriented, and in the right language but the result is still empty, escalate: change preprocessing parameters, switch models, use multimodal extraction, or route to human review. Do not gamble by "calling it one more time."

Low confidence also needs handling. Set thresholds for fields and send values below the threshold to a human queue; apply format and cross-validation to amounts, document numbers, and dates. Log the confidence distribution so you can tell whether quality is degrading gradually or has always been poor.

7. Reusable error-handling code

The example below assumes the endpoint accepts multipart/form-data; adjust the actual path and field names according to the provider's documentation.

import random
import time
import requests

FATAL = {400, 401, 403, 413, 415}
RETRYABLE = {429, 500, 502, 503, 504}

def ocr_call(image_bytes, api_key, endpoint, max_retries=5):
    last_error = None
    for attempt in range(max_retries):
        try:
            resp = requests.post(
                endpoint,
                headers={"Authorization": f"Bearer {api_key}"},
                files={"file": ("page.png", image_bytes, "image/png")},
                timeout=(5, 90),  # 5 s connect, 90 s read
            )
        except requests.Timeout as exc:
            last_error = exc
        else:
            if resp.status_code in FATAL:
                raise RuntimeError(f"fatal_{resp.status_code}: {resp.text[:300]}")
            if resp.status_code == 200:
                data = resp.json()
                text = (data.get("text") or "").strip()
                if not text:
                    raise ValueError("empty_result: check rotation, crop and format")
                return data
            if resp.status_code not in RETRYABLE:
                raise RuntimeError(f"unexpected_{resp.status_code}")
            last_error = RuntimeError(resp.text[:300])

        retry_after = resp.headers.get("Retry-After") if "resp" in locals() else None
        wait = float(retry_after) if retry_after else min(2 ** attempt + random.random(), 30)
        time.sleep(wait)

    raise RuntimeError(f"retry_exhausted: {last_error}")

This code backs off only for retryable errors, fails immediately on 401, 413, and 415, and treats an "empty result" as a business exception requiring human intervention rather than a network error.

8. What to log and monitor

Record at least these fields: request ID, status code, error code, elapsed time, image dimensions, file size, retry count, recognized character count, average confidence, and whether the case went to human review. Group statistics by endpoint and model so you can tell whether a particular image type is the problem or a particular upstream is unstable.

For monitoring, set three alerts: a sudden rise in error rate, a sudden rise in empty-result rate, and a sudden rise in P95 latency. Watching only average latency misses the long tail, and OCR timeouts often occur in that tail.

9. FAQ

Why does 401 persist after changing the key?

First confirm the value actually sent in the request header, then confirm that the key matches the endpoint address. Gateway keys and vendor-native keys usually cannot be mixed, and environment variables may be cached by an old process.

Why does 413 persist after compression?

Check pixel dimensions, PDF page count, and Base64 inflation. Sometimes the file size is under the limit but the decoded resolution exceeds the endpoint limit, so it must be resized before upload.

Why does 429 still trigger frequently after adding retries?

Check whether all clients retry at the same time and whether concurrency exceeds the limit. Adding jitter, queuing, and a concurrency cap is usually more effective than simply increasing the retry count.

The image looks clear, so why is recognition empty?

Check orientation, language, contrast, and whether the code reads the wrong response field first. A clear but tilted, glare-affected, or language-mismatched image can still return an empty result.

Will retries be billed twice?

It depends on the provider's billing rules and whether the request was already processed. For a timeout that may have succeeded, check the task status or verify with the request ID before deciding whether to retry.

Next steps

Once troubleshooting is stable, consolidate OCR calls into a single client layer with unified timeouts, retries, logging, and degradation, so each business module does not implement its own version. If you need one entry point for several vision models, see the ZSQH AI Gateway; register at /sign-up and check /pricing for models and billing.

Related reading: