API Reference
Deep-OCR extracts structured JSON from supported document types or full text as Markdown. Processing is asynchronous: submit a file with a document type to POST /v1/extractions, then poll for its status and retrieve the result when processing completes. You need an API key and an HTTP client; no SDK is required.
How it works — three steps
- Submit your file to
POST /v1/extractionswith adocument_type. You get202 Acceptedand an extraction ID (ext_…) — processing continues in the background. - Poll
GET /v1/extractions/{id}untilstatusis completed or failed. Wait between requests as specified by theRetry-Afterheader. - Read the result from the
resultfield oncestatusis"completed".
# 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. Accepted requests return 202 Accepted with an extraction ID beginning with ext_…. Poll that ID until processing completes or fails. For completed extractions, 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, DOCX, DOC, ODT, RTF. The upload limit is approximately 100 MB and the document limit is approximately 350 pages. Word-processing files over ≈50 MB are rejected asynchronously by the converter. Word-processing files are queued immediately and converted to PDF by the async worker; their page count appears after conversion and uses the converted PDF. |
| document_type | string | required | Required extraction type. Use 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 processing completes or fails (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 returns, when the request is accepted, 202 Accepted with an extraction ID beginning with ext_… — processing continues in the background. Poll that ID until processing completes or fails. For completed extractions, read the result field.
Poll until processing completes or fails
# 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 failureCheck the processing status
Poll GET /v1/extractions/{id} with the same API key. While the status is queued or processing, wait the number of seconds specified by Retry-After before requesting an update.
When the status is completed, read the extracted data from result. If it is failed, read error.code and error.message. Both states return HTTP 200, so check the processing status before using the result.
Choose an extraction type
The document_type form field is required. Choose a predefined document type, use auto for classification, or select generic for an adaptive structured result.
Use fulltext for Markdown. This mode counts as 10 billed pages per document page. Read metadata.pages_billed for the billed page count.
File limits
Uploads can contain approximately 350 pages and be up to 100 MB. Word-processing files (DOCX, DOC, ODT, RTF) have a separate conversion limit of approximately 50 MB, checked during background processing.
Retrieve results before they expire
Completed and failed extractions remain available for about one hour. After that, the ID returns HTTP 404. Save the result in your system during this window; otherwise, you will need to submit the document 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 costs 10 billed pages per document page. 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. Fulltext costs 10 billed pages per document page. 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 selects the extraction format. Predefined structured types use a fixed schema; generic adapts to the document and fulltext returns Markdown. For predefined types, the returned content object follows the schema, with unavailable values represented as 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.
If you already know the document type, specify it directly to skip classification.
invoicereceiptcontractid_documentdelivery_notebank_statementpayslippurchase_orderhandwritinggenericfulltextField Reference
Monetary values are returned as numbers (e.g. 583.10). When identified, the currency is provided separately in the currency field as an ISO 4217 code (e.g. EUR). Dates are ISO 8601 YYYY-MM-DD. For predefined structured types, unavailable field values are 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 a machine-readable code and a message describing the failure.
Rate Limits
Document submission limits apply per API key and plan. Exceeding a limit returns HTTP 429. Polling has a separate allowance; follow Retry-After between polls. Additional protective limits may also apply.
| Plan | Submissions / minute |
|---|---|
| Free Trial | 10 |
| Starter | 30 |
| Pro | 60 |
| Business | 120 |
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 in your workflows without writing integration 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
Import the Postman collection and environment to try the API requests. Set your API key in the environment before sending a request.
Integrate with an AI coding assistant
Building with Claude Code, Cursor, GitHub Copilot, or another AI assistant? We publish a machine-readable API summary at /llms.txt — a Markdown reference with endpoints, authentication details, and examples. Use it as context for generated code, then review and test the integration.
Example prompt for your coding assistant:
"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 convention for providing documentation in a format AI tools can read.
Ready to start building?
Create an account, generate an API key in the Developer Center, and follow the quickstart example to submit your first document.
See current pricing for plans and included usage.