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
- 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
- Cap concurrency of parallel submissions (e.g. p-limit with 2-4) and add spacing between polls
- Increase the poll interval / maxWaitTime budget so status checks are less frequent
- 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
- Cap submission concurrency (p-limit 2-4) instead of unbounded Promise.all fan-out
- Space out polls; prefer the SDK poller's built-in backoff over hand-rolled tight loops
- Respect Retry-After guidance embedded in the message and shed load upstream
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
- Service unavailable: ${text}
- Expected a JSON response body.
- PaddleOCR official API request failed.
- Response body is missing data.
- Request timed out after ${timeoutMs}ms
AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14).
Data as JSON: /api/errors/b3d7c8834dc20163.
Report an issue: GitHub.