// REST API

DocuOCR API Reference

A single REST API to upload documents, run AI extraction, and retrieve structured JSON: fields, line items, and parsed tables, ready to stream into any application or data pipeline.

// 01

Authentication

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.

Creating an API key

  1. 1 Sign in to your DocuOCR dashboard.
  2. 2 Open Settings → API Keys.
  3. 3 Click Create API Key, give it a descriptive name, and confirm.
  4. 4 Copy the key immediately, it is only shown once.

Sending the header

HTTP header
Authorization: Bearer YOUR_API_KEY
// 02

Upload a Document

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.

POST /api/documents/upload

Request multipart/form-data

Field Type Required Description
document file required PDF, PNG, JPG, TIFF or WEBP. Max 50 MB.

Response 200 OK

POST /api/documents/upload → 200
{
  "success": true,
  "message": "File uploaded successfully",
  "data": {
    "file_id": 1234,
    "name": "invoice-q1-2026.pdf"
  }
}
// 03

Run Extraction

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.

POST /api/documents/extract

Request body application/json

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.
extraction_mode string optional One of invoice (invoice data with line items), invoice_summary (header-level invoice data only) or all (auto-detect every table and field as it appears in the document). Defaults to all, the same default the web app uses.

Request body

POST /api/documents/extract
{
  "file_id": 1234,
  "document_type_id": 5678,
  "extraction_mode": "invoice"
}

Response 200

→ 200 OK
{
  "success": true,
  "message": "Extraction initialized successfully",
  "data": {
    "extraction_id": 9876,
    "extraction_hash": "abc123-def456"
  }
}
// 04

Retrieve Extraction Results

Fetch the structured output for an extraction job. The response contains every extracted field with its page number, plus fully-parsed tables with their headers and rows. Poll this endpoint until status reaches a terminal value: a non-terminal status means the job is still running, not that the document is empty.

GET /api/documents/extraction/{hash}

Path parameter

Param Description
hash The extraction_hash returned by POST /extract.

Response 200 OK | invoice example

GET /api/documents/extraction/abc123-def456 → 200
{
  "success": true,
  "data": {
    "extraction_id": 9876,
    "extraction_hash": "abc123-def456-ghi789-jkl012",
    "status": "completed",
    "message": null,
    "pages": {
      "total": 1,
      "completed": 1,
      "pending": 0,
      "error": 0
    },
    "file": {
      "id": 1234,
      "name": "invoice-q1-2026.pdf"
    },
    "document_type": {
      "id": 5678,
      "name": "Invoice"
    },
    "fields": [
      {
        "field_id": 101,
        "content": "Acme Supplies Ltd",
        "value": null,
        "is_table": false,
        "page": 1
      },
      {
        "field_id": 106,
        "content": null,
        "value": null,
        "is_table": true,
        "page": 1,
        "headers": ["Description", "Quantity", "Unit Price", "Amount"],
        "rows": [
          {
            "Description": "Steel brackets",
            "Quantity": "120",
            "Unit Price": "4.50",
            "Amount": "540.00"
          }
        ]
      }
    ]
  }
}

Extraction status values

Status Meaning
pending The job is queued and waiting for a worker. Keep polling.
processing The AI model is reading and extracting fields. Keep polling.
completed Extraction finished successfully. fields are populated.
partial_error Some pages were extracted and some failed. fields are incomplete; see message.
error An unrecoverable error occurred. See message. Retry or contact support.
limit_exceeded Your plan page limit was reached, so extraction stopped. See message.
upgrade_required This document needs a higher plan to finish extracting. See message.
// 05

List Document Types

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.

GET /api/documents/types

Response 200 OK

GET /api/documents/types → 200
{
  "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
    }
  ]
}
// 06

Error Codes

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.

Error response shape

→ 401 Unauthorized
{
  "success": false,
  "error_code": "unauthorized",
  "message": "Invalid or missing API key."
}
// 07

Rate Limits

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

Rate-limit response headers

Response headers on every call
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.

// 08

Code Examples

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.

shell, full extraction workflow
# 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"
extract.php
<?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 (in_array($result['data']['status'], ['pending', 'processing'], true));

if ($result['data']['status'] !== 'completed') {
    throw new RuntimeException($result['data']['status'] . ': ' . $result['data']['message']);
}

foreach ($result['data']['fields'] as $field) {
    echo $field['content'] . ': ' . $field['value'] . "\n";
}
extract.mjs
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 (!['pending', 'processing'].includes(json.data.status)) {
      throw new Error(`${json.data.status}: ${json.data.message}`);
    }
    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);
extract.py
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"] not in ("pending", "processing"):
            raise RuntimeError(f"{data['status']}: {data['message']}")
        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']}  (page={field['page']})")

Ready to wire up document extraction?

Generate your first API key and start pulling structured data from your documents in minutes.