Errors
The NetraOCR API uses conventional HTTP status codes and returns a JSON body describing what went wrong.
Error shape
Failed requests return a JSON object with a detail field:
{
"detail": "File too large (Max 50MB)"
}
Validation errors (422) return a structured list identifying the offending
fields:
{
"detail": [
{
"loc": ["body", "allowed_ips"],
"msg": "At least one allowed IP address is required.",
"type": "value_error"
}
]
}
Always branch on the HTTP status code first; use detail for logging and
user-facing messages.
Status codes
| Code | Name | Meaning & typical fix |
|---|---|---|
200 | OK | Success. |
201 | Created | Resource created (e.g. an API key). |
202 | Accepted | Async work accepted (bulk upload); poll for results. |
400 | Bad Request | Malformed input — e.g. invalid Base64. Fix the request. |
401 | Unauthorized | Missing/invalid credentials. Check your api_key/api_secret. |
403 | Forbidden | IP not on the key's allow-list, or a plan limit reached. |
402 | Payment Required | Account cannot process the request. Review access in the console and retry. |
404 | Not Found | Unknown job_id, template, or resource — or not owned by you. |
413 | Payload Too Large | File exceeds the size limit (50 MB single / 600 MB bulk). |
422 | Unprocessable Entity | Validation failed; inspect the detail list. |
429 | Too Many Requests | Rate limit exceeded. Back off and retry. See Rate Limits. |
5xx | Server Error | Transient server issue. Retry with backoff; contact support if it persists. |
Handling errors well
- Authentication (
401/403). Verify credentials and confirm the calling host's public IP is on the key's allow-list — useGET /api-keys/my-ipto check. - Payment required (
402). Review account access in the console or estimate bulk work withbulk-analyze. - Rate limits (
429). Implement exponential backoff. See Rate Limits. - Server errors (
5xx). Retry idempotent reads freely. For uploads, check job status before re-submitting to avoid duplicate charges.
Example: robust upload
import time, requests
def upload(path, template_id, **params):
for attempt in range(4):
r = requests.post(
"https://netraocr.shivicx.com/api/v1/ocr/upload",
auth=("ak_live_xxxxxxxx", "sk_live_xxxxxxxx"),
files={"file": open(path, "rb")},
params={"template_id": template_id, **params},
)
if r.status_code == 429 or r.status_code >= 500:
time.sleep(2 ** attempt) # back off and retry
continue
r.raise_for_status() # raise on other 4xx
return r.json()
raise RuntimeError("Upload failed after retries")