{"record":{"id":"8b3b7b572cbebec3","repo":"PaddlePaddle/PaddleOCR","slug":"rate-limit-exceeded-msg","errorCode":null,"errorMessage":"Rate limit exceeded: {msg}","messagePattern":"Rate limit exceeded: (.+?)","errorType":"exception","errorClass":"RateLimitError","httpStatus":429,"severity":"warning","filePath":"paddleocr/_api_client/_core.py","lineNumber":156,"sourceCode":"        )\n    return JobStatus(\n        job_id=job_id,\n        state=state,\n        progress=progress,\n        result=data.get(\"resultUrl\"),\n        error_msg=data.get(\"errorMsg\"),\n    )\n\n\ndef raise_for_status(status_code: int, msg: str) -> None:\n    if 200 <= status_code < 300:\n        return\n    if status_code in (401, 403):\n        raise AuthError(f\"Authentication failed: {msg}\")\n    if status_code == 400:\n        raise InvalidRequestError(f\"Bad request: {msg}\")\n    if status_code == 429:\n        raise RateLimitError(f\"Rate limit exceeded: {msg}\")\n    if status_code in (503, 504):\n        raise ServiceUnavailableError(status_code, f\"Service unavailable: {msg}\")\n    raise APIError(status_code, msg)\n\n\ndef unwrap_api_response(payload: dict, status_code: int) -> dict:\n    if not isinstance(payload, dict):\n        raise ResponseFormatError(\"Response body must be a JSON object.\")\n    code = payload.get(\"code\", 0)\n    if code not in (0, None):\n        raise APIError(status_code, extract_api_message_from_payload(payload) or \"\")\n    data = payload.get(\"data\")\n    if not isinstance(data, dict):\n        raise ResponseFormatError(\"Response JSON must contain object field 'data'.\")\n    return data\n\n\ndef extract_job_id(data: dict) -> str:","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/paddleocr/_api_client/_core.py#L138-L174","documentation":"Raised as RateLimitError by raise_for_status() when the HTTP response status is 429. The service is throttling requests for your API key or account; the embedded message usually states the limit and reset window. Unlike 400, retrying after a delay can succeed.","triggerScenarios":"Burst job creation, tight polling loops on get_job_status, or concurrent workers sharing one key, exceeding the account's requests-per-second or jobs-per-day quota.","commonSituations":"Reduced the poll interval below the allowed rate, scaled out workers without raising quotas, or free-tier daily quota exhausted.","solutions":["Honor Retry-After / the message's reset timing and retry after the window.","Increase the poll interval (and keep exponential backoff) in polling calls.","Throttle or batch job creation; add a client-side rate limiter.","Request a quota increase if sustained throughput is needed."],"exampleFix":"# before\nresult = await client.ocr(file_path=p, timeout=600)  # tight internal polling\n\n# after\nimport asyncio\ntry:\n    result = await client.ocr(file_path=p, timeout=600)\nexcept RateLimitError:\n    await asyncio.sleep(60)\n    result = await client.ocr(file_path=p, timeout=600)","handlingStrategy":"retry","validationCode":"import time\nMIN_POLL_INTERVAL = 2.0\n_last_call = 0.0\n\ndef throttle():\n    global _last_call\n    wait = MIN_POLL_INTERVAL - (time.monotonic() - _last_call)\n    if wait > 0:\n        time.sleep(wait)\n    _last_call = time.monotonic()","typeGuard":null,"tryCatchPattern":"from paddleocr._api_client.errors import RateLimitError\nimport asyncio\n\nasync def with_backoff(fn, *a, attempts=5, **kw):\n    for i in range(attempts):\n        try:\n            return await fn(*a, **kw)\n        except RateLimitError:\n            if i == attempts - 1:\n                raise\n            await asyncio.sleep(2 ** i * 5)","preventionTips":["Keep poll intervals at or above the documented rate limit.","Wrap all calls in exponential backoff for RateLimitError.","Cap concurrent workers per API key."],"tags":["rate-limit","http","throttling","retryable"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}