Loading...
Loading...
Practical code examples using the OpenOCR REST API.
The simplest way to extract text from an image. No SDK install — just hit the REST endpoint with the standard requests library.
import uuid
import requests
API_KEY = "sk-ocr-your-key"
resp = requests.post(
"https://api.open-ocr.com/v1/ocr",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4()), # new UUID per logical request
},
json={
"engine": "mistral-mistral-ocr-4-1",
"input": {"type": "url", "url": "https://example.com/receipt.png"},
},
)
resp.raise_for_status() # an error envelope has no "status" field
result = resp.json()
if result["status"] != "succeeded":
raise RuntimeError(f"OCR ended as {result['status']}: {result.get('provider_error')}")
print(result["extracted_text"])
print(f"Engine: {result['engine']}, Cost: ${result['cost_debited']}")Plain fetch — works in Node, Deno, Bun, or the browser (CORS-allowed for read endpoints; OCR calls should originate server-side to keep your key off the client).
const API_KEY = process.env.OPENOCR_API_KEY!;
const resp = await fetch("https://api.open-ocr.com/v1/ocr", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(), // new UUID per logical request
},
body: JSON.stringify({
engine: "mistral-mistral-ocr-4-1",
input: { type: "url", url: "https://example.com/receipt.png" },
}),
});
const result = await resp.json();
if (result.status !== "succeeded") throw new Error("OCR ended as " + (result.status ?? result.error?.code)); // error envelopes have no status
console.log(result.extracted_text);
console.log(`Engine: ${result.engine}, Cost: $${result.cost_debited}`);Run the same image through multiple engines to compare accuracy, speed, and cost. Useful for finding the best engine for your use case.
import requests, base64, json, uuid
API_KEY = "sk-ocr-your-key"
IMAGE_URL = "https://example.com/document.png"
engines = [
"mistral-mistral-ocr-4-1", # Recommended default: general documents, scans, tables
"google-gemini-3.1-flash-lite", # Cheapest acceptable quality
"local-tesseract", # OpenOCR-hosted, clean printed text
"local-easyocr", # OpenOCR-hosted, multi-language
"local-doctr", # OpenOCR-hosted, layouts
"openocr-ocrmypdf", # OpenOCR-hosted, scanned PDFs
]
for engine in engines:
resp = requests.post(
"https://api.open-ocr.com/v1/ocr",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"engine": engine,
"input": {"type": "url", "url": IMAGE_URL},
},
)
if not resp.ok:
print(f"{engine}: HTTP {resp.status_code}")
continue
r = resp.json()
if r["status"] != "succeeded":
print(f"{engine}: {r['status']}")
continue
text_preview = (r.get("extracted_text") or "")[:80]
latency = r.get("provider_latency_ms", "?")
cost = r.get("cost_debited", 0)
print(f"{engine:<25} {latency:>6}ms ${cost:.4f} {text_preview}...")const apiKey = process.env.OPENOCR_API_KEY!;
const imageUrl = 'https://example.com/doc.png';
const engines = [
'mistral-mistral-ocr-4-1',
'google-gemini-3.1-flash-lite',
'local-tesseract',
'local-easyocr',
'local-doctr',
];
const results = await Promise.all(
engines.map(async (engine) => {
const res = await fetch('https://api.open-ocr.com/v1/ocr', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
engine,
input: { type: 'url', url: imageUrl },
}),
});
const body = await res.json();
return { engine, ...body, ok: body.status === "succeeded" };
})
);
// Print comparison table
results.forEach((r) => {
console.log(`${r.engine}: ${r.provider_latency_ms}ms, $${r.cost_debited}`);
console.log(` → ${r.extracted_text?.substring(0, 100)}...`);
});Handle common error cases: invalid input, insufficient credits, rate limits.
import os
import time
import uuid
import requests
def ocr_with_retry(engine: str, image_url: str, api_key: str, max_retries: int = 3):
"""OCR with automatic retry on rate limits and transient errors."""
# One Idempotency-Key per logical request, reused on every retry, so a retry can never bill twice.
idempotency_key = str(uuid.uuid4())
for attempt in range(max_retries):
resp = requests.post(
"https://api.open-ocr.com/v1/ocr",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
json={
"engine": engine,
"input": {"type": "url", "url": image_url},
},
)
# A completed JSON request returns HTTP 200; accept any 2xx and read the body's
# status field, which is the robust signal.
if resp.ok:
body = resp.json()
if body["status"] == "succeeded":
return body
raise Exception(f"OCR ended as {body['status']}: {body.get('provider_error')}")
if resp.status_code == 429:
# Rate limited — wait and retry
retry_after = int(resp.headers.get("Retry-After", 10))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
if resp.status_code == 402:
raise Exception("Insufficient credits — top up at open-ocr.com/credits")
if resp.status_code == 400:
error = resp.json()
raise Exception(f"Bad request: {error.get('error', 'unknown')}")
if resp.status_code == 409:
raise Exception("Idempotency-Key was used with a different request; use a new key")
if resp.status_code == 503 and resp.json().get("status") == "timeout":
# A timeout replays as the same 503 for this key: do not retry the same key.
raise Exception("OCR timed out; retry with a NEW Idempotency-Key")
if resp.status_code >= 500:
# Server error — retry with backoff
time.sleep(2 ** attempt)
continue
resp.raise_for_status()
raise Exception(f"Failed after {max_retries} retries")
# Usage
api_key = os.environ["OPENOCR_API_KEY"]
result = ocr_with_retry("mistral-mistral-ocr-4-1", "https://example.com/doc.png", api_key)
print(result["extracted_text"])async function ocrWithRetry(
engine: string,
imageUrl: string,
apiKey: string,
maxRetries = 3
) {
// One Idempotency-Key per logical request, reused on every retry, so a retry can never bill twice.
const idempotencyKey = crypto.randomUUID();
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch('https://api.open-ocr.com/v1/ocr', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
engine,
input: { type: 'url', url: imageUrl },
}),
});
// A completed JSON request returns HTTP 200: accept any 2xx and read the body's status, the robust signal.
if (res.ok) {
const body = await res.json();
if (body.status === 'succeeded') return body;
throw new Error(`OCR ended as ${body.status}: ${body.provider_error ?? ''}`);
}
if (res.status === 429) {
const wait = parseInt(res.headers.get('Retry-After') || '10', 10);
console.log(`Rate limited. Waiting ${wait}s...`);
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
if (res.status === 402) throw new Error('Insufficient credits');
if (res.status === 400) {
const err = await res.json();
throw new Error(`Bad request: ${err.error}`);
}
if (res.status === 409) throw new Error('Idempotency-Key was used with a different request; use a new key');
if (res.status === 503 && (await res.clone().json()).status === 'timeout') {
// A timeout replays as the same 503 for this key: do not retry the same key.
throw new Error('OCR timed out; retry with a NEW Idempotency-Key');
}
if (res.status >= 500) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
continue;
}
throw new Error(`Unexpected status: ${res.status}`);
}
throw new Error(`Failed after ${maxRetries} retries`);
}
const apiKey = process.env.OPENOCR_API_KEY!;
const imageUrl = 'https://example.com/doc.png';
const result = await ocrWithRetry('mistral-mistral-ocr-4-1', imageUrl, apiKey);
console.log(result.extracted_text);Process multiple files with concurrency control to stay within rate limits:
import requests, base64, os, uuid
from concurrent.futures import ThreadPoolExecutor, as_completed
API_KEY = "sk-ocr-your-key"
ENGINE = "mistral-mistral-ocr-4-1"
IMAGE_DIR = "./invoices"
def ocr_file(filepath: str) -> dict:
with open(filepath, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
resp = requests.post(
"https://api.open-ocr.com/v1/ocr",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"engine": ENGINE,
"input": {"type": "base64", "data_base64": b64, "mime_type": "image/png"},
},
)
resp.raise_for_status() # an error envelope has no "status" field
body = resp.json()
return {"file": filepath, "ok": body["status"] == "succeeded", **body}
files = [os.path.join(IMAGE_DIR, f) for f in os.listdir(IMAGE_DIR) if f.endswith(".png")]
# Process 5 at a time (well within 60 RPM limit)
with ThreadPoolExecutor(max_workers=5) as pool:
futures = {pool.submit(ocr_file, f): f for f in files}
for future in as_completed(futures):
result = future.result()
print(f'{result["file"]}: {len(result.get("extracted_text", ""))} chars')Each engine has different strengths. Here's a quick guide:
Per-page prices differ by engine; see the pricing page. Use the comparison tool to test engines side-by-side on your own documents.