Skip to main content
Insyrge - Zoho CRM & Business Automation Consulting
← Back to All ArticlesAI & Business Automation

OCR API Rate Limits: Queue, Retry, Fail Over

How extraction service limits work, how to queue and pace documents, retry with backoff and know when a second provider pays off.

•Manav Sharma
OCR API Rate Limits: Queue, Retry, Fail Over

Handle OCR API rate limits with a queue between intake and extraction, paced workers, backoff that respects Retry-After and failover only when it pays.

An automated data entry pipeline that works on ten test documents can stall on the first busy morning. Hundreds of scanned invoices arrive at once, the pipeline sends them all to an extraction service, and the service starts rejecting requests because the account has exceeded its allowance. Documents pile up, retries make the problem worse, and someone ends up typing invoices by hand again. OCR API rate limits are the per-second, per-minute or per-day allowances that extraction services enforce. This guide explains how those limits work, how to queue and pace requests, how to retry without making things worse, and when it is worth having a second provider to fail over to.

How OCR API rate limits work

Most document extraction and optical character recognition services limit how many requests an account can make in a period. The details differ by provider, but the patterns are similar:

  • Transactions per second or requests per minute for each operation
  • Limits on concurrent asynchronous jobs
  • Page or file size limits per request
  • Some limits that can be raised on request and some that are fixed

Amazon Textract's quotas page distinguishes set quotas, which cannot be changed, from default quotas, which can be viewed and changed through the Service Quotas console. Google's Document AI quotas page similarly explains that quotas have default values that can typically be adjusted, while system limits are fixed. Check the current figures for your provider and region; they change and differ between operations.

When you exceed a limit, the service rejects the request. Textract's error handling guide says it throttles calls and returns a ProvisionedThroughputExceededException when too many are made in a short period. Many HTTP APIs return status 429 Too Many Requests, and, as MDN describes, may include a Retry-After header saying how long to wait.

Put a queue between intake and extraction

The single most important design choice is not to call the extraction service directly from intake. When an email arrives with forty attachments, or a scanner uploads a batch, each document should go onto a queue. Workers take documents from the queue and send them for extraction at a controlled pace.

A queue turns a burst into a backlog that drains steadily. It also gives you somewhere to see how much work is waiting, and a place to put documents back when extraction fails. Our guide to webhook queues explains the same idea for incoming events, and the design carries over directly to documents.

Pace requests below the limit

With a queue in place, workers can control their own rate. Two simple approaches work well:

  • Fixed concurrency: run a small number of workers, each processing one document at a time, sized so total requests stay under the limit
  • Rate limiter: workers request a token before each call, and tokens refill at a fixed rate below your quota

Leave headroom. If several pipelines share the same account, the limit is shared too, so a reporting job or a second integration can push you over even when your pipeline is behaving. Either give each pipeline its own allowance or coordinate through a shared limiter.

Retry with backoff, not immediately

Some failures are temporary: throttling, timeouts, a brief service error. Retrying immediately after a throttling error usually fails again and adds to the load. Retry with exponential backoff instead: wait a short time, then double the wait after each failure, with a little random jitter so many workers do not retry at the same moment. Respect a Retry-After value when the service provides one.

Textract's guide recommends configuring the AWS SDK's automatic retries and suggests a retry count of 5. Whatever the provider, cap the number of attempts. After the cap, move the document to a separate failed queue with the error recorded, rather than retrying forever. Our guide to webhook dead letter queues covers how to triage and replay items that have exhausted their retries.

Not every error should be retried. A file that is corrupt, password-protected or over the size limit will fail every time. Classify errors into temporary and permanent, and send permanent failures straight to review.

Fail over to a second provider when it is worth it

If extraction is critical and a provider outage or sustained throttling would stop operations, a second provider can take over. This is not free: the two services return results in different formats, may extract fields differently and need separate accounts, credentials and testing.

A practical middle ground:

  • Normalise results into your own format, so downstream code does not care which provider produced them
  • Fail over only on sustained problems, such as repeated throttling after retries or service errors, not on a single failure
  • Record which provider processed each document, so accuracy can be compared
  • Test the secondary path regularly, or it will not work when needed

For many teams, a well-paced queue with backoff is enough, and the second provider is a later improvement.

Worked example: a paced worker with retry and failover

The simplified, untested Python sketch below shows the pattern. A worker takes documents from a queue, waits for a token from a rate limiter, calls the primary provider, backs off on throttling and fails over to a secondary provider after repeated throttling. The provider functions and queue are placeholders.

import random
import time

class Throttled(Exception):
    def __init__(self, retry_after=None):
        self.retry_after = retry_after

class PermanentError(Exception):
    pass

class RateLimiter:
    """Allow roughly `rate` calls per second across this worker."""
    def __init__(self, rate):
        self.interval = 1.0 / rate
        self.next_time = time.monotonic()

    def wait(self):
        now = time.monotonic()
        if now < self.next_time:
            time.sleep(self.next_time - now)
        self.next_time = max(now, self.next_time) + self.interval

def extract_with_retry(doc, provider, limiter, max_attempts=5):
    delay = 1.0
    for attempt in range(1, max_attempts + 1):
        limiter.wait()
        try:
            return provider(doc)
        except Throttled as exc:
            if attempt == max_attempts:
                raise
            wait = exc.retry_after or delay + random.uniform(0, delay / 2)
            time.sleep(wait)
            delay *= 2

def process(doc, primary, secondary, limiter, failed_queue):
    try:
        result = extract_with_retry(doc, primary, limiter)
        source = "primary"
    except Throttled:
        result = extract_with_retry(doc, secondary, limiter)
        source = "secondary"
    except PermanentError as exc:
        failed_queue.append({"doc": doc, "error": str(exc)})
        return None
    return {"doc": doc, "provider": source, "fields": result}

In production, the limiter should be shared across workers, the failed queue should be durable storage, and results should be validated before they reach any business system.

Plan capacity before go-live

Before launch, estimate your busiest hour rather than your daily average. Count how many documents arrived in the heaviest hour of the last few months, how many pages each has on average, and which operations each page needs. Compare that with your quota, leaving room for other users of the same account. If the peak is well above the quota, request an increase early, since quota changes are not always immediate.

Monitor the backlog, not just errors

Three measures tell you whether the pipeline is keeping up: queue depth, the age of the oldest waiting document, and the share of requests throttled. A queue that drains by mid-morning is healthy. One whose oldest item keeps getting older means your pace or quota is too low for the volume, and it is time to request a higher quota, process off-peak or add capacity.

Frequently Asked Questions

What are OCR API rate limits?

They are limits that document extraction services place on how many requests an account can make in a period, such as transactions per second, along with limits on concurrent jobs and file sizes.

What happens when you exceed a rate limit?

The service rejects the request. Amazon Textract, for example, returns a ProvisionedThroughputExceededException, and many HTTP APIs return status 429, sometimes with a Retry-After header.

How should a pipeline retry throttled requests?

With exponential backoff and jitter, respecting any Retry-After value, and with a cap on attempts, after which the document moves to a failed queue for review.

Is a second OCR provider necessary?

Not always. A paced queue with backoff handles most bursts. A second provider helps when extraction is critical and outages or sustained throttling would stop operations.

Keep document extraction moving under load

We design document pipelines that queue, pace and recover from provider limits without manual rework. See our AI automation solutions, or talk to us about your extraction pipeline.

About the Author

Manav Sharma is the founder of Insyrge and a CRM solutions architect. He works across Zoho, Salesforce, HubSpot, Odoo and more, designing the CRM setups, integrations and automations that keep sales and operations teams working from the same data.

Need Help Implementing This in Your Business?

Our certified Zoho consultants and automation experts can help you design and deploy custom workflows tailored to your operations.

Book Free Consultation