API Reference
The Deep-OCR API lets you extract structured data from any document with a single HTTP call. Submit a file with a document type to POST /v1/extractions, then poll the returned extraction id until it completes and read the typed JSON result. No configuration, no model training, no SDKs required.
How it works — three steps
- Submit your file to
POST /v1/extractionswith adocument_type. You get202 Acceptedand an extraction id (ext_…) — never the result inline. - Poll
GET /v1/extractions/{id}untilstatusis terminal, honouring theRetry-Afterheader. - Read the result from the
resultfield oncestatusis"completed".
Coming from /v1/ocr?
The older synchronous POST /v1/ocr endpoint is deprecated and will be removed on 31 July 2026. It returned the result inline; the current API submits the document and you poll for the result. Point your integration at POST /v1/extractions — the differences are small:
| /v1/ocr (deprecated) | /v1/extractions |
|---|---|
| 200 + result inline | 202 + { id }, then poll |
| ?document_type= query param (optional) | document_type form field (required) |
| { "detail": "…" } | { "error": { "code", "message" } } |
# Authenticate every request with your Bearer API key (see Authentication below).
# 1 · Submit — returns 202 Accepted with an extraction id
curl -sS -X POST "https://api.deep-ocr.com/v1/extractions" \
-F "file=@invoice.pdf" \
-F "document_type=invoice"
# → { "id": "ext_123", "status": "queued" }
# 2 · Poll until completed (honour the Retry-After header)
curl -sS "https://api.deep-ocr.com/v1/extractions/ext_123"
# → { "status": "completed", "result": { … } }Authentication
All requests require an API key passed as a Bearer token in the Authorization header. You can create and manage keys in the .
Authorization: Bearer deep_ocr_sk_live_xxxxxxxxxxxxxxxxxxxx
Keep your API keys secret. Never commit them to version control. Use environment variables or a secrets manager in production.
Submit a document POST /v1/extractions
Submit a document for extraction. The endpoint accepts a multipart/form-data body and always replies 202 Accepted with an extraction whose id is ext_…. Poll that id until it reaches a terminal status and read the result field — see Poll for the result below.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| file | binary | required | The document file. Accepted formats: PDF, PNG, JPG, JPEG, WebP, TIF, TIFF. Up to ≈100 MB and ≈350 pages per document. |
| document_type | string | required | Extraction schema to use — a required form field. Default to generic for a general schema; pass auto to let the API classify the document first. One of: auto, invoice, receipt, contract, id_document, delivery_note, bank_statement, payslip, purchase_order, handwriting, generic, fulltext. auto and fulltext cost more (see Document Types). |
Code Examples
# Authenticate every request with your Bearer API key (see Authentication above).
# 1 · Submit the document — returns 202 Accepted with an extraction id
curl -sS -X POST "https://api.deep-ocr.com/v1/extractions" \
-F "file=@invoice.pdf" \
-F "document_type=invoice"
# → { "id": "ext_123", "status": "queued" }
# 2 · Poll until the status is terminal (honour the Retry-After header)
curl -sS "https://api.deep-ocr.com/v1/extractions/ext_123"
# → { "status": "completed", "result": { … } }You'll get a 202 with an id — but not the data yet. Next: poll for the result →
Poll for the result GET /v1/extractions/{id}
Every document is processed as a background job. The POST /v1/extractions call always replies 202 Accepted with an extraction whose id is ext_… — never the result inline. Poll that id until it reaches a terminal status and read the result field.
Poll until the status is terminal
# Authenticate every request with your Bearer API key (see Authentication above).
# (You already have the id from the 202 response to Submit a document, above.)
curl -sS "https://api.deep-ocr.com/v1/extractions/ext_123"
# → { "status": "processing" } (+ Retry-After: <seconds>) # keep polling
# → { "status": "completed", "result": { … } } # success → read result
# → { "status": "failed", "error": { … } } # terminal failurePolling contract
- Poll
GET /v1/extractions/{id}with the sameAuthorizationheader. Non-terminal statuses (queued,processing) mean keep polling — wait theRetry-Afterheader (seconds) the server returns. - Success is gated on
status === "completed"— not on HTTP 200. A failed extraction is returned asHTTP 200carryingstatus: "failed", so always check the status before using the body. - On a completed job the extracted data is under
result— the shape shown under The result you get back below.
Parameters, limits & errors
document_typeis a required multipart form field — one of the 12:auto, invoice, receipt, contract, id_document, delivery_note, bank_statement, payslip, purchase_order, handwriting,generic,fulltext. Default togeneric(cheap general schema);autoruns an extra classification pass andfulltextdefaults to ×10 on public plans; overage-enabled custom plans may bill 1:1. Usemetadata.pages_billedas the source of truth.- Limits:up to ≈100 MB and ≈350 pages per document.
- A failed extraction carries
{ "error": { "code", "message" } }wherecodeis one of: payload_too_large, unprocessable, quota_exceeded, too_many_inflight, not_found, too_many_pages, upstream_unavailable, expired, worker_failed.
Retention. A finished extraction stays retrievable (HTTP 200) for about 24 hours after it terminates; once that window elapses the id returns 404 (there is no 410). Re-submit the document if you need it again.
The result you get back
When an extraction completes, the extracted data is carried in its result field. The shape of that result is consistent across all document types:
| Field | Type | Description |
|---|---|---|
| document_type | string | The document_type used for extraction — the value you passed in, or the resolved type when you sent 'auto'. |
| content | object | The extracted data — structure depends on document_type. |
| metadata.pages | number | Number of pages processed. |
| metadata.pages_billed | number | Pages billed against your monthly quota. Equals pages for structured schema modes. Fulltext defaults to 10 billed pages per document page on public plans; overage-enabled custom plans may bill fulltext 1:1. Treat this field as the source of truth. |
| metadata.processing_time | number | End-to-end processing time in seconds. |
Example — Invoice result
{
"document_type": "invoice",
"content": {
"vendor": {
"name": "ACME Solutions GmbH",
"address": "123 Example Street, 10115 Berlin",
"tax_id": "DE123456789",
"email": "billing@acme.de",
"phone": null
},
"customer": {
"name": "Your Company Ltd.",
"address": "42 High Street, 20354 Hamburg",
"customer_number": "CUS-0042"
},
"invoice_number": "RE-2024-0815",
"invoice_date": "2024-03-01",
"due_date": "2024-03-31",
"currency": "EUR",
"line_items": [
{
"description": "Software License Q1/2024",
"quantity": 1,
"unit_price": 490.00,
"total": 490.00,
"tax_rate": 19
}
],
"subtotal": 490.00,
"tax_breakdown": [
{ "rate": 19, "net_amount": 490.00, "tax_amount": 93.10 }
],
"tax_amount": 93.10,
"total": 583.10,
"is_reverse_charge": false,
"payment_terms": "14 days net",
"bank_details": {
"iban": "DE89370400440532013000",
"bic": "COBADEFFXXX",
"bank_name": "Commerzbank"
},
"is_paid": false,
"notes": null,
"items_truncated": false
},
"metadata": {
"pages": 1,
"pages_billed": 1,
"processing_time": 1.84
}
}Fulltext mode — verbatim Markdown
When you want the full readable text of a PDF or scanned document — including tables, headings, and structure — pass document_type=fulltext. The response shape stays the same except that content has exactly one key, markdown, holding the verbatim document text as Markdown. Headings, paragraphs, lists, and tables are preserved. Multi-page documents are joined with a \n\n<!-- page break -->\n\n sentinel so you can split visually if you want.
Quota usage
Fulltext mode consumes more compute than schema modes due to the higher token cost of verbatim transcription. Public plans default to 10 billed pages per document page. Custom overage plans may bill fulltext 1:1; use metadata.pages_billed as the source of truth.
Request
# Authenticate every request with your Bearer API key (see Authentication above).
# 1 · Submit with document_type=fulltext — returns 202 Accepted
curl -sS -X POST "https://api.deep-ocr.com/v1/extractions" \
-F "file=@contract.pdf" \
-F "document_type=fulltext"
# → { "id": "ext_123", "status": "queued" }
# 2 · Poll until completed, then read result.content.markdown
curl -sS "https://api.deep-ocr.com/v1/extractions/ext_123"
# → { "status": "completed", "result": { "content": { "markdown": "# …" } } }Example — Fulltext result
{
"document_type": "fulltext",
"content": {
"markdown": "# Service Agreement\n\n## 1. Scope of Services\n\nThis Agreement governs the cooperation between Party A and Party B for the provision of consulting and implementation services as set out below.\n\n| Item | Description | Quantity |\n|---|---|---|\n| 1 | Consulting services | 10 h |\n| 2 | Implementation | 40 h |\n\n<!-- page break -->\n\n## 2. Fees\n\nThe total fee amounts to EUR 5,000, payable within 14 days of the invoice date."
},
"metadata": {
"pages": 3,
"pages_billed": 30,
"processing_time": 7.42
}
}Document Types
These are the schemas the result.content from step 3 conforms to — pick one as your document_type when you submit. Each document_type uses a pre-defined extraction schema. The returned content object always matches that schema — every field is present, unknown fields are null.
Auto-detect
Not sure which schema fits? Pass document_type=auto and the API classifies the document with a lightweight first pass, then applies the matching schema automatically. The detected type is returned in the document_type field of the result.
The classification pass costs more than a fixed schema — when you already know the type, name it directly (or default to generic) to skip it.
invoicereceiptcontractid_documentdelivery_notebank_statementpayslippurchase_orderhandwritinggenericfulltextField Reference
Monetary values are returned as numbers (e.g. 583.10) so they can be used directly in calculations — the currency is always available separately in the currency field as an ISO 4217 code (e.g. EUR). Dates are ISO 8601 YYYY-MM-DD. Every field not found in the document is returned as null.
Invoiceinvoice
| Field | Type | Description |
|---|---|---|
| vendor.name | string | Company or person issuing the invoice |
| vendor.address | string | Full address of the vendor |
| vendor.tax_id | string | VAT / tax ID of the vendor (e.g. DE123456789) |
| vendor.email | string | Contact email of the vendor |
| vendor.phone | string | Contact phone of the vendor |
| customer.name | string | Name of the invoice recipient |
| customer.address | string | Full address of the recipient |
| customer.customer_number | string | Customer number assigned by the vendor |
| invoice_number | string | Unique invoice identifier |
| invoice_date | date | Date the invoice was issued (ISO 8601) |
| due_date | date | Payment due date |
| performance_date_from | date | Start of the service/delivery period if stated separately from the invoice date |
| performance_date_to | date | End of the service/delivery period |
| purchase_order_number | string | Buyer's PO reference number if quoted on the invoice |
| currency | string | ISO 4217 code (e.g. EUR, USD) |
| line_items[] | array | One entry per line item.{ "description": "Software License Q1/2024", "quantity": 1, "unit_price": 490.00, "total": 490.00, "tax_rate": 19 } |
| subtotal | number | Net total before tax |
| tax_breakdown[] | array | One entry per distinct VAT rate on the invoice.{ "rate": 19, "net_amount": 490.00, "tax_amount": 93.10 } |
| tax_amount | number | Total tax (sum of all tax_breakdown entries) |
| total | number | Gross total including tax |
| is_reverse_charge | bool | true if the recipient is liable for VAT instead of the vendor — common in cross-border EU B2B invoices ('reverse charge') |
| payment_terms | string | Payment conditions as written on the document (e.g. 'Net 30', '2% 10 Net 30') |
| bank_details.iban | string | IBAN for bank transfer |
| bank_details.bic | string | BIC / SWIFT code |
| bank_details.bank_name | string | Name of the bank |
| is_paid | bool | true if stamped/marked paid, false if marked unpaid, null if not indicated |
| items_truncated | bool | true if the invoice had >50 line items (only first 50 extracted) |
Receiptreceipt
| Field | Type | Description |
|---|---|---|
| merchant.name | string | Shop or business name |
| merchant.address | string | Shop address |
| merchant.phone | string | Shop phone number |
| merchant.tax_id | string | VAT / tax ID of the merchant |
| receipt_number | string | Receipt or transaction number |
| date | date | Date of purchase (ISO 8601) |
| time | string | Time of purchase in 24-hour format HH:MM |
| currency | string | ISO 4217 code |
| items[] | array | One entry per purchased item.{ "description": "Oat Milk 1L", "quantity": 2, "unit_price": 1.49, "total": 2.98, "tax_rate": 7 } |
| subtotal | number | Net total before tax |
| tax_breakdown[] | array | One entry per distinct VAT rate on the receipt. German receipts often show both 7 % and 19 %.{ "rate": 7, "net_amount": 2.80, "tax_amount": 0.20 } |
| tax_amount | number | Total tax amount |
| total | number | Amount paid including tax |
| payment_method | string | One of: cash, credit_card, debit_card, mobile_payment, check, gift_card, store_credit, other |
| card_last_four | string | Last 4 digits of the card used as a string (e.g. '4242'), never asterisks |
| cashier | string | Name or ID of the cashier if shown |
| notes | string | Any additional notes or remarks printed on the receipt |
Contractcontract
| Field | Type | Description |
|---|---|---|
| title | string | Title or heading of the contract |
| contract_number | string | Contract reference number if present |
| parties[] | array | One entry per contracting party.{ "role": "service_provider", "name": "ACME Ltd.", "address": "123 Main St, London EC1A 1BB", "representative": "Jane Smith" } |
| parties[].role | string | One of: buyer, seller, service_provider, service_recipient, lessor, lessee, employer, employee, licensor, licensee, other |
| parties[].name | string | Full legal name of the party |
| parties[].address | string | Address of the party |
| parties[].representative | string | Name of the person signing on behalf of the party |
| effective_date | date | Date the contract enters into force |
| expiry_date | date | End date; null for perpetual or indefinite contracts |
| termination_notice_days | number | Required notice period in days to terminate (longest value if multiple apply) |
| governing_law | string | ISO 3166-1 alpha-3 country code (e.g. DEU) or country name as written |
| subject_matter | string | One-sentence summary of what the contract is about (max 30 words) |
| key_obligations | string[] | Up to 5 concrete obligations as short strings (max 15 words each).["Provide IT consulting services 40 hours per week", "Pay monthly fee of €5,000 by the 15th"] |
| payment_terms | string | Payment schedule or conditions |
| contract_value | number | Total monetary value of the contract |
| currency | string | ISO 4217 code |
| signatures[] | array | One entry per signature block.{ "party": "ACME GmbH", "signed_date": "2024-01-15", "signed_by": "Max Müller" } |
| signatures[].party | string | Name of the signing party |
| signatures[].signed_date | date | Date of signature |
| signatures[].signed_by | string | Name of the individual who signed |
| notes | string | Additional observations or remarks about the contract |
ID Documentid_document
| Field | Type | Description |
|---|---|---|
| id_type | string | One of: passport, national_id, id_card, driver_license, residence_permit, travel_document, other |
| issuing_country | string | ISO 3166-1 alpha-3 code (e.g. DEU, AUT, CHE) |
| issuing_authority | string | Authority that issued the document |
| document_number | string | Document number exactly as printed |
| surname | string | Family name |
| given_names | string | Given name(s) |
| date_of_birth | date | Date of birth (ISO 8601) |
| nationality | string | Nationality as printed on the document |
| gender | string | M or F; null for X / Divers / non-binary |
| place_of_birth | string | Place of birth if shown |
| issue_date | date | Date of issue |
| expiry_date | date | Expiry date; null if the document has no expiry |
| mrz_line1 | string | First machine-readable zone line, copied character-for-character (typically 44 chars) |
| mrz_line2 | string | Second machine-readable zone line |
Delivery Notedelivery_note
| Field | Type | Description |
|---|---|---|
| delivery_note_number | string | Delivery note reference number |
| order_number | string | Corresponding purchase or sales order number |
| delivery_date | date | Date of delivery (ISO 8601) |
| sender.name | string | Sending company or person |
| sender.address | string | Full address of the sender |
| sender.contact | string | Contact person or department at the sender |
| recipient.name | string | Receiving company or person |
| recipient.address | string | Delivery address |
| recipient.contact | string | Contact person or department at the recipient |
| carrier | string | Shipping carrier (e.g. DHL, UPS) |
| tracking_number | string | Shipment tracking number, copied exactly as printed |
| items[].description | string | Description of the delivered item |
| items[].article_number | string | Article / SKU number |
| items[].quantity_ordered | number | Originally ordered quantity (null if not shown) |
| items[].quantity_delivered | number | Actually delivered quantity |
| items[].unit | string | Unit of measurement as written (e.g. pcs, Stück, kg) |
| total_packages | number | Total number of packages |
| total_weight_kg | number | Total shipment weight in kg (converted from lbs if needed) |
| is_partial_delivery | bool | true if explicitly marked as a partial shipment, false if marked as complete, null if not stated |
| delivery_sequence | string | Shipment sequence if shown (e.g. '1/3' for first of three) |
| notes | string | Additional remarks or instructions on the delivery note |
Bank Statementbank_statement
| Field | Type | Description |
|---|---|---|
| account_holder | string | Name of the account holder |
| account_number | string | Account number as printed |
| iban | string | IBAN |
| bic | string | BIC / SWIFT code |
| bank_name | string | Name of the bank |
| statement_date | date | Date the statement was generated |
| period_from | date | Start of the statement period |
| period_to | date | End of the statement period |
| currency | string | ISO 4217 code |
| opening_balance | number | Account balance at the start of the period |
| closing_balance | number | Account balance at the end of the period |
| transactions[] | array | One entry per transaction. Statements with more than 100 transactions: only the first 100 are extracted.{ "date": "2024-03-15", "value_date": "2024-03-15", "description": "Direct debit rent March", "reference": "INV-2024-03", "amount": -950.00, "type": "debit", "balance": 2340.12 } |
| transactions[].date | date | Booking date |
| transactions[].value_date | date | Value date — the date the amount actually affects the balance |
| transactions[].description | string | Full transaction description / purpose text |
| transactions[].reference | string | Transaction reference or booking text |
| transactions[].amount | number | Positive for credits (money in), negative for debits (money out) |
| transactions[].type | string | 'credit' if amount is positive, 'debit' if negative |
| transactions[].balance | number | Running account balance after this transaction, if shown |
Payslippayslip
| Field | Type | Description |
|---|---|---|
| employer.name | string | Employer company name |
| employer.address | string | Employer address |
| employer.tax_id | string | Employer tax ID |
| employee.name | string | Employee full name |
| employee.employee_number | string | Internal employee number |
| employee.tax_id | string | Employee tax identification number (TIN) |
| employee.social_security_number | string | Social security / national insurance number |
| employee.iban | string | IBAN of the employee's bank account for salary payment |
| period_from | date | Start of the pay period |
| period_to | date | End of the pay period |
| payment_date | date | Date the salary was / will be paid |
| currency | string | ISO 4217 code |
| earnings[] | array | Each positive pay component (base salary, bonuses, allowances, overtime, etc.).{ "description": "Base salary", "amount": 3500.00 } |
| deductions[] | array | Each amount subtracted (income tax, social security, pension, etc.). Amount is always a positive number.{ "description": "Income tax", "amount": 650.00 } |
| gross_salary | number | Total gross pay before any deductions |
| net_salary | number | Take-home pay after all deductions |
| tax_amount | number | Income tax portion of deductions if shown separately |
| social_security_amount | number | Total social security / national insurance contributions |
| notes | string | Additional remarks on the payslip |
Purchase Orderpurchase_order
| Field | Type | Description |
|---|---|---|
| po_number | string | Purchase order number |
| order_date | date | Date the order was placed |
| delivery_date_requested | date | Requested delivery date |
| buyer.name | string | Company or person placing the order |
| buyer.address | string | Buyer's address |
| buyer.contact | string | Buyer contact person or department |
| buyer.tax_id | string | Buyer's VAT / tax ID |
| supplier.name | string | Company or person receiving the order |
| supplier.address | string | Supplier's address |
| supplier.contact | string | Supplier contact person |
| supplier.tax_id | string | Supplier's VAT / tax ID |
| delivery_address | string | Delivery address if different from buyer address |
| currency | string | ISO 4217 code |
| line_items[] | array | One entry per ordered item.{ "position": 1, "description": "Laptop Dell XPS 15", "article_number": "DEL-XPS-15", "quantity": 2, "unit": "pcs", "unit_price": 1200.00, "total": 2400.00, "tax_rate": 19 } |
| line_items[].position | number | Line item position number (1, 2, 3, …) |
| line_items[].description | string | Description of the ordered item |
| line_items[].article_number | string | Article / SKU number |
| line_items[].quantity | number | Ordered quantity |
| line_items[].unit | string | Unit of measurement as written (e.g. pcs, Stück, kg) |
| line_items[].unit_price | number | Price per unit (net) |
| line_items[].total | number | Line total (net) |
| line_items[].tax_rate | number | VAT rate as a percentage (e.g. 19) |
| subtotal | number | Net total before tax |
| tax_amount | number | Total tax amount |
| total | number | Gross total including tax |
| payment_terms | string | Payment conditions |
| notes | string | Additional remarks or terms printed on the purchase order |
| items_truncated | bool | true if the PO had >50 line items (only first 50 extracted) |
Handwritinghandwriting
| Field | Type | Description |
|---|---|---|
| transcription | string | Full transcribed text. Unclear words marked with [?] (e.g. meetng[?]), single unclear characters as w[?]rd, illegible sections as [illegible – 2 lines] |
| confidence | string | Overall readability: 'high' (>90% clearly readable), 'medium' (50–90%), 'low' (<50%) |
| language | string | ISO 639-1 language code of the primary language (e.g. de, en, fr) |
| notes | string | Observations such as mixed print/cursive, multiple writing styles, or damage |
Genericgeneric
| Field | Type | Description |
|---|---|---|
| document_type | string | Model's best-guess label for the specific document kind (e.g. 'form', 'letter', 'report') — more specific than the top-level 'generic' |
| title | string | Document title or heading |
| date | date | Primary date found on the document |
| content | object | All extracted fields as a nested JSON object — structure adapts to the document. May contain tables (array of row objects), lists (array of strings), and any other relevant fields. |
Fulltext (Markdown)fulltext
| Field | Type | Description |
|---|---|---|
| markdown | string | The verbatim document text as Markdown. Headings, paragraphs, lists, and tables are preserved. Multi-page documents are joined into a single Markdown stream with `\n\n<!-- page break -->\n\n` between pages so you can split visually if you want to. |
Error Codes
There are two kinds of error. Transport errors — an invalid request, a missing key, or an unknown id — come back as the HTTP status codes below. A document that was accepted but could not be processed fails the extraction: the job reaches status: "failed" carrying { "error": { "code", "message" } } with the closed code set listed under Poll for the result (above).
Rate Limits
Rate limits are enforced per API key. Exceeding the limit returns HTTP 429. Limits reset every minute.
| Plan | Requests / minute | Pages / month |
|---|---|---|
| Free Trial | 10 | 100 |
| Starter | 30 | 200 |
| Pro | 60 | 1,000 |
| Business | 120 | 5,000 |
Integrations
Use Deep-OCR with the tools you already know — from no-code automation to AI-assisted development.
Verified n8n node
Deep-OCR is a verified n8n community node, so you can automate document OCR inside any workflow without writing code. On n8n Cloud, add it directly from the node catalog — search for the Deep-OCR node on the canvas and drag it in. On self-hosted n8n, enable Verified Community Nodes in the Admin Panel (and restart the instance) to add it the same way.
You can also install the package manually:
Available on npmjs.com/package/n8n-nodes-deep-ocr
Postman Collection
Download the official Postman collection to explore and test every endpoint without writing any code. Includes pre-configured auth and example responses for all supported document types.
AI-ready — Vibe-code friendly
Building with Claude Code, Cursor, GitHub Copilot, or another AI assistant? We publish a machine-readable API summary at /llms.txt — a concise Markdown file containing everything your AI assistant needs to write a correct integration from scratch.
Paste into your AI assistant to get started instantly:
"Read https://deep-ocr.com/llms.txt and integrate Deep-OCR into my project"
The /llms.txt file follows the emerging llms.txt convention — a lightweight standard for making APIs and documentation easy for AI tools to consume.
Ready to start building?
Create a free account, grab your API key, and make your first request in under 5 minutes.
100 pages free · No credit card required