PaddlePaddle/PaddleOCR · warning · RateLimitError

Rate limit exceeded: ${text}

Error message

Rate limit exceeded: ${text}

What it means

RateLimitError is thrown when the API answers HTTP 429 — you exceeded the allowed request rate or quota for your key/tier. It extends APIError with statusCode fixed at 429, and the message embeds any server explanation. This error is by design retryable after waiting.

Source

Thrown at api_sdk/typescript/src/internal/http.ts:241

      clearTimeout(timeoutID);
      signal?.removeEventListener("abort", abort);
    }

    if (resp.ok) return resp;

    let text = await resp.text();
    try {
      const payload = JSON.parse(text) as { msg?: string; message?: string; errorMsg?: string };
      text = payload.msg || payload.message || payload.errorMsg || text;
    } catch {
      // Keep raw response text.
    }
    if (resp.status === 401 || resp.status === 403) {
      throw new AuthError(`Authentication failed: ${text}`);
    } else if (resp.status === 400) {
      throw new InvalidRequestError(`Bad request: ${text}`);
    } else if (resp.status === 429) {
      throw new RateLimitError(`Rate limit exceeded: ${text}`);
    } else if (resp.status === 503 || resp.status === 504) {
      throw new ServiceUnavailableError(resp.status, `Service unavailable: ${text}`);
    } else {
      throw new APIError(resp.status, text);
    }
  }
}

function requireJobId(data: SubmitResponse): string {
  if (!data || typeof data.jobId !== "string" || data.jobId.length === 0) {
    throw new ResponseFormatError("Submit response is missing jobId.");
  }
  return data.jobId;
}

View on GitHub (pinned to 2661c7c0ef)

Solutions

  1. Retry with backoff honoring the server's Retry-After guidance — start the next attempt after at least 1-2 seconds and double on repeat 429s
  2. Cap concurrency of parallel submissions (e.g. p-limit with 2-4) and add spacing between polls
  3. Increase the poll interval / maxWaitTime budget so status checks are less frequent
  4. If sustained, request a quota increase or distribute across keys/environments as allowed by your plan

Example fix

// before
const results = await Promise.all(files.map(f => client.extractFile(model, f, {})));

// after
import pLimit from "p-limit";
const limit = pLimit(3);
const results = await Promise.all(files.map(f =>
  limit(() => retryOn429(() => client.extractFile(model, f, {})))
));
Defensive patterns

Strategy: retry

Type guard

function isRateLimitError(e: unknown): e is RateLimitError {
  return e instanceof RateLimitError;
}

Try / catch

async function withRateLimit<T>(fn: () => Promise<T>, maxRetries = 5): Promise<T> {
  for (let attempt = 0; ; attempt++) {
    try { return await fn(); }
    catch (e) {
      if (e instanceof RateLimitError && attempt < maxRetries) {
        await sleep(Math.min(2 ** attempt * 1000, 30_000) + Math.random() * 500);
        continue;
      }
      throw e;
    }
  }
}

Prevention

When it happens

Trigger: Tight poller loops hitting getJobStatus too frequently; submitting many files in a parallel loop; bursty batch status checks; shared keys used by multiple services; free-tier keys with low QPS ceilings.

Common situations: Fan-out uploads without concurrency limits; polling intervals shorter than the API allows; retries after errors amplifying request volume; month/quota exhaustion near billing boundaries; multiple team members sharing one key.

Related errors


AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14). Data as JSON: /api/errors/b3d7c8834dc20163. Report an issue: GitHub.