Error Handling

HTTP status codes, error response format, and troubleshooting guide.

Error Handling

The InkScan API uses standard HTTP status codes and returns structured error responses.

Error response format

All errors return a consistent JSON structure:

{
  "error": {
    "code": "invalid_image",
    "message": "The uploaded file is not a supported image format.",
    "status": 400
  }
}

Status codes

Code Meaning When it happens
200 Success Recognition completed
400 Bad Request Invalid parameters, unsupported format, or missing image
401 Unauthorized Missing or invalid API key
402 Payment Required Insufficient credits for this request
413 Payload Too Large Image exceeds 10 MB or PDF exceeds 50 MB
422 Unprocessable Image is valid but recognition failed (e.g. blank page, no text detected)
429 Rate Limited Too many requests. Check Retry-After header
500 Server Error Something went wrong on our side. Retry with exponential backoff

Error codes

Error code Description
invalid_image File is not a supported image format (JPG, PNG, WebP)
image_too_large Image exceeds maximum file size
no_text_detected Image was processed but no handwriting was found
low_quality Image quality too low for reliable recognition
insufficient_credits Not enough credits to process this request
invalid_api_key API key is missing, malformed, or revoked

Rate limits

Rate limits are per API key, regardless of credit balance:

Tier Requests per minute
Default 60

Rate limited responses include a Retry-After header (in seconds).

Retry strategy

For 429 and 5xx errors, use exponential backoff:

import time, base64, requests

def recognize_with_retry(image_path, max_retries=3):
    with open(image_path, "rb") as f:
        image_base64 = base64.b64encode(f.read()).decode()

    for attempt in range(max_retries):
        response = requests.post(
            "https://api.inkscan.app/v1/recognize",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={"image_base64": image_base64},
        )

        if response.status_code == 200:
            return response.json()

        if response.status_code in (429, 500, 502, 503):
            wait = 2 ** attempt
            retry_after = response.headers.get("Retry-After")
            if retry_after:
                wait = int(retry_after)
            time.sleep(wait)
            continue

        # Non-retryable error
        response.raise_for_status()

    raise Exception("Max retries exceeded")