A single REST API to upload documents, run AI extraction, and retrieve structured JSON: fields, line items, and confidence scores, ready to stream into any application or data pipeline.
Every request to the DocuOCR API must carry a valid API key issued from your dashboard. Keys are scoped to your workspace and carry the permissions of the account that created them. Treat them as passwords and never commit them to source control.
Authorization: Bearer YOUR_API_KEY
The first step in any extraction workflow is uploading the source file. DocuOCR accepts PDF, PNG, JPG, TIFF and WEBP formats, including multi-page PDFs and high-resolution scans. The endpoint returns a file_id that you pass to the Extract endpoint.
/api/documents/upload
| Field | Type | Required | Description |
|---|---|---|---|
| document | file | required | PDF, PNG, JPG, TIFF or WEBP. Max 50 MB. |
{
"success": true,
"message": "File uploaded successfully",
"data": {
"file_id": 1234,
"name": "invoice-q1-2026.pdf"
}
}
Kick off an AI extraction job for a previously uploaded file. DocuOCR queues the job asynchronously and returns an extraction_hash immediately. Poll GET /api/documents/extraction/{hash} for the result, or configure a webhook to receive it automatically.
/api/documents/extract
| Field | Type | Required | Description |
|---|---|---|---|
| file_id | integer | required | The file_id returned by the upload endpoint. |
| document_type_id | integer | optional | ID of a custom extraction template. Omit to let the AI auto-detect the document type. |
{
"file_id": 1234,
"document_type_id": 5678
}
{
"success": true,
"message": "Extraction initialized",
"data": {
"extraction_id": 9876,
"extraction_hash": "abc123-def456"
}
}
Fetch the structured output for a completed extraction job. The response contains every extracted field with its value and page number, plus fully-parsed line-item tables where present. Each field carries a confidence score between 0 and 1 so downstream systems can route uncertain fields to human review.
/api/documents/extraction/{hash}
| Param | Description |
|---|---|
| hash | The extraction_hash returned by POST /extract. |
{
"success": true,
"data": {
"extraction_id": 9876,
"extraction_hash": "abc123-def456-ghi789-jkl012",
"status": "completed",
"confidence": 0.987,
"file": {
"id": 1234,
"name": "invoice-q1-2026.pdf"
},
"document_type": {
"id": 5678,
"name": "Invoice"
},
"fields": [
{
"field_id": 101,
"content": "vendor",
"value": "Acme Supplies Ltd",
"confidence": 0.998,
"is_table": false,
"page": 1
},
{
"field_id": 102,
"content": "document_number",
"value": "INV-2026-0892",
"confidence": 0.994,
"is_table": false,
"page": 1
},
{
"field_id": 103,
"content": "issue_date",
"value": "2026-03-14",
"confidence": 0.991,
"is_table": false,
"page": 1
},
{
"field_id": 104,
"content": "currency",
"value": "USD",
"confidence": 1.0,
"is_table": false,
"page": 1
},
{
"field_id": 105,
"content": "total",
"value": "594.00",
"confidence": 0.987,
"is_table": false,
"page": 1
},
{
"field_id": 106,
"content": "line_items",
"is_table": true,
"page": 1,
"rows": [
{
"cells": [
{ "value": "Steel brackets", "is_header": false },
{ "value": "120", "is_header": false },
{ "value": "4.50", "is_header": false },
{ "value": "540.00", "is_header": false }
]
}
]
}
]
}
}
| Status | Meaning |
|---|---|
| pending | The job is queued and waiting for a worker. |
| processing | The AI model is reading and extracting fields. |
| completed | Extraction finished successfully. fields are populated. |
| failed | An unrecoverable error occurred. Retry or contact support. |
Return every extraction template available to your workspace: DocuOCR built-in types and any custom templates your team has configured. Use the returned id as document_type_id in the Extract endpoint to target a specific field schema.
/api/documents/types
{
"success": true,
"data": [
{
"id": 5678,
"name": "Invoice",
"description": "Vendor invoices and bills, all layouts",
"is_public": true,
"fields_count": 12
},
{
"id": 5679,
"name": "Purchase Order",
"description": "Purchase orders with line-item tables",
"is_public": false,
"fields_count": 9
},
{
"id": 5680,
"name": "Bank Statement",
"description": "Transactions, balances and account details",
"is_public": true,
"fields_count": 8
}
]
}
Instead of polling for results, register a webhook URL in your dashboard and DocuOCR will POST the completed extraction payload to your endpoint the moment the job finishes, typically within seconds of the document being processed. Webhooks eliminate polling overhead and are the recommended pattern for high-volume pipelines.
{
"event": "extraction.completed",
"extraction_hash": "abc123-def456-ghi789-jkl012",
"extraction_id": 9876,
"status": "completed",
"confidence": 0.987,
"document_type": "Invoice",
"fields": [
{ "content": "vendor", "value": "Acme Supplies Ltd", "confidence": 0.998 },
{ "content": "document_number", "value": "INV-2026-0892", "confidence": 0.994 },
{ "content": "total", "value": "594.00", "confidence": 0.987 }
]
}
DocuOCR uses standard HTTP status codes. Every error response includes a machine-readable error_code string and a human-readable message to simplify debugging.
| HTTP | error_code | When it happens |
|---|---|---|
| 400 | invalid_request | Missing or malformed required field in the request body. |
| 401 | unauthorized | No API key supplied, or the key is invalid or revoked. |
| 403 | forbidden | Your plan does not include API access, or you hit a quota. |
| 404 | not_found | The file_id or extraction_hash does not exist in your workspace. |
| 415 | unsupported_media | The uploaded file format is not accepted (use PDF, PNG, JPG, TIFF, WEBP). |
| 422 | extraction_failed | The AI could not extract meaningful data from the document. |
| 429 | rate_limit_exceeded | Too many requests. Check Retry-After header for the cooldown window. |
| 500 | server_error | An internal error occurred. Retry with exponential back-off. |
{
"success": false,
"error_code": "unauthorized",
"message": "Invalid or missing API key."
}
Rate limits are applied per API key on a rolling 60-second window. If you exceed the limit the API returns HTTP 429 with a Retry-After header indicating the number of seconds to wait before retrying.
| Plan | Requests / hour | Max file size | Concurrent jobs |
|---|---|---|---|
| Pro | 500 | 50 MB | 10 |
| Enterprise | Unlimited | 200 MB | Configurable |
X-RateLimit-Limit: 500 X-RateLimit-Remaining: 487 X-RateLimit-Reset: 1748987200 Retry-After: 42 // only present on 429
Need higher throughput or custom SLAs? Talk to our team about an Enterprise plan.
Copy-paste snippets that cover the full extraction lifecycle: upload a document, trigger extraction, and retrieve the structured JSON result. Every example uses the same field names and extraction hash so you can follow the flow across languages.
# 1, Upload the document curl -X POST https://docuocr.com/api/documents/upload \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "document=@/path/to/invoice-q1-2026.pdf" # → {"success":true,"data":{"file_id":1234,"name":"invoice-q1-2026.pdf"}} # 2, Start extraction (document_type_id optional) curl -X POST https://docuocr.com/api/documents/extract \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"file_id": 1234, "document_type_id": 5678}' # → {"success":true,"data":{"extraction_id":9876,"extraction_hash":"abc123-def456"}} # 3, Retrieve structured results curl -X GET https://docuocr.com/api/documents/extraction/abc123-def456 \ -H "Authorization: Bearer YOUR_API_KEY"
<?php $apiKey = 'YOUR_API_KEY'; $baseUrl = 'https://docuocr.com/api'; $filePath = '/path/to/invoice-q1-2026.pdf'; // 1, Upload $ch = curl_init("$baseUrl/documents/upload"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"], CURLOPT_POSTFIELDS => ['document' => new CURLFile($filePath)], ]); $upload = json_decode(curl_exec($ch), true); curl_close($ch); if (!$upload['success']) { die("Upload failed: " . $upload['message']); } $fileId = $upload['data']['file_id']; // 2, Extract $ch = curl_init("$baseUrl/documents/extract"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer $apiKey", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ 'file_id' => $fileId, 'document_type_id' => 5678, // optional ]), ]); $extract = json_decode(curl_exec($ch), true); curl_close($ch); if (!$extract['success']) { die("Extraction failed: " . $extract['message']); } $hash = $extract['data']['extraction_hash']; // 3, Poll for results do { sleep(2); $ch = curl_init("$baseUrl/documents/extraction/$hash"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"], ]); $result = json_decode(curl_exec($ch), true); curl_close($ch); } while ($result['data']['status'] === 'processing'); foreach ($result['data']['fields'] as $field) { echo $field['content'] . ': ' . $field['value'] . "\n"; }
const API_KEY = 'YOUR_API_KEY'; const BASE_URL = 'https://docuocr.com/api'; async function uploadDocument(file) { const form = new FormData(); form.append('document', file); const res = await fetch(`${BASE_URL}/documents/upload`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}` }, body: form, }); const json = await res.json(); if (!json.success) throw new Error(json.message); return json.data.file_id; } async function startExtraction(fileId, documentTypeId = null) { const payload = { file_id: fileId }; if (documentTypeId) payload.document_type_id = documentTypeId; const res = await fetch(`${BASE_URL}/documents/extract`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); const json = await res.json(); if (!json.success) throw new Error(json.message); return json.data.extraction_hash; } async function pollResults(hash, intervalMs = 2000) { while (true) { const res = await fetch(`${BASE_URL}/documents/extraction/${hash}`, { headers: { 'Authorization': `Bearer ${API_KEY}` }, }); const json = await res.json(); if (json.data.status === 'completed') return json.data; if (json.data.status === 'failed') throw new Error('Extraction failed'); await new Promise(r => setTimeout(r, intervalMs)); } } // Usage const fileInput = document.querySelector('input[type="file"]'); const fileId = await uploadDocument(fileInput.files[0]); const hash = await startExtraction(fileId, 5678); const data = await pollResults(hash); console.log('Extracted fields:', data.fields);
import time import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://docuocr.com/api" HEADERS = {"Authorization": f"Bearer {API_KEY}"} def upload_document(file_path: str) -> int: with open(file_path, "rb") as f: resp = requests.post( f"{BASE_URL}/documents/upload", headers=HEADERS, files={"document": f}, ) data = resp.json() if not data["success"]: raise RuntimeError(data["message"]) return data["data"]["file_id"] def start_extraction(file_id: int, document_type_id: int | None = None) -> str: payload = {"file_id": file_id} if document_type_id: payload["document_type_id"] = document_type_id resp = requests.post( f"{BASE_URL}/documents/extract", headers={**HEADERS, "Content-Type": "application/json"}, json=payload, ) data = resp.json() if not data["success"]: raise RuntimeError(data["message"]) return data["data"]["extraction_hash"] def poll_results(extraction_hash: str, interval: float = 2.0) -> dict: url = f"{BASE_URL}/documents/extraction/{extraction_hash}" while True: data = requests.get(url, headers=HEADERS).json()["data"] if data["status"] == "completed": return data if data["status"] == "failed": raise RuntimeError("Extraction failed") time.sleep(interval) # Usage file_id = upload_document("/path/to/invoice-q1-2026.pdf") ex_hash = start_extraction(file_id, document_type_id=5678) result = poll_results(ex_hash) for field in result["fields"]: print(f"{field['content']}: {field['value']} (confidence={field['confidence']})")
Generate your first API key and start pulling structured data from your documents in minutes.
From the same family of tools