API reference · Updated 16 August 2026
DocuParse API Documentation
Extract structured JSON from receipts and invoices with a single HTTP request.
Quickstart
Get started in three steps:
- 01Sign up — free account at DocuParse API.
- 02Go to Dashboard → API Keysand create a new key. Copy it immediately — it's shown only once.
- 03Send your first request using the example below.
# 1. Submit the document
curl -X POST https://docuparseapi.com/api/v1/extract \
-H "Authorization: Bearer $DOCUPARSE_API_KEY" \
-F "file=@receipt.pdf"
# → {"success": true, "document_id": "<id>", "status": "queued"}
# 2. Fetch the result (repeat until "status" is "completed" or "failed")
curl https://docuparseapi.com/api/v1/documents/<id> \
-H "Authorization: Bearer $DOCUPARSE_API_KEY"On Starter and Pro you can skip polling: add a webhook in Dashboard → Settings and DocuParse API will POST the finished result to your endpoint.
Authentication
All API requests must include your API key as a Bearer token in the Authorization header.
Authorization: Bearer dex_your_api_key_hereSecurity warning
Never include your API key in browser/frontend code. API keys must be stored server-side only (environment variables, secrets manager). Treat them like passwords.
POST/api/v1/extract
Accepts a receipt or invoice file and queues it for async processing. Use GET /api/v1/documents/[id] to poll for the result.
Request
| Parameter | Type | Required | Description |
|---|---|---|---|
| file | File (form-data) | Yes | PDF, JPG, PNG or CSV. Up to 20 pages; 11–20 pages count as two documents. |
Headers
| Header | Value |
|---|---|
| Authorization | Bearer <API_KEY> |
| Content-Type | multipart/form-data (set automatically by HTTP clients) |
Request examples
All examples below use server-side code only. Never call this endpoint from browser JavaScript with a secret API key.
cURL
# 1. Submit the document
curl -X POST https://docuparseapi.com/api/v1/extract \
-H "Authorization: Bearer $DOCUPARSE_API_KEY" \
-F "file=@receipt.pdf"
# → {"success": true, "document_id": "<id>", "status": "queued"}
# 2. Fetch the result (repeat until "status" is "completed" or "failed")
curl https://docuparseapi.com/api/v1/documents/<id> \
-H "Authorization: Bearer $DOCUPARSE_API_KEY"Node.js (using built-in fetch)
const API = "https://docuparseapi.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.DOCUPARSE_API_KEY}` };
async function parseDocument(fileBuffer, fileName) {
// 1. Submit
const form = new FormData();
form.append("file", new Blob([fileBuffer]), fileName);
const submit = await fetch(`${API}/extract`, { method: "POST", headers, body: form }).then((r) => r.json());
if (!submit.success) throw new Error(`[${submit.error.code}] ${submit.error.message}`);
// 2. Fetch the result
for (let i = 0; i < 40; i++) {
const doc = await fetch(`${API}/documents/${submit.document_id}`, { headers }).then((r) => r.json());
if (doc.status === "completed") return doc.data;
if (doc.status === "failed") throw new Error(`Extraction failed: ${submit.document_id}`);
await new Promise((r) => setTimeout(r, 1500));
}
throw new Error(`Timed out: ${submit.document_id}`);
}
const data = await parseDocument(require("fs").readFileSync("receipt.pdf"), "receipt.pdf");
console.log(data.merchant, data.total, data.currency);Next.js API Route (server-side)
// app/api/documents/parse/route.ts — keeps your API key on the server
import { NextRequest, NextResponse } from "next/server";
const API = "https://docuparseapi.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.DOCUPARSE_API_KEY}` };
export async function POST(request: NextRequest) {
const incoming = await request.formData();
const upstream = new FormData();
upstream.append("file", incoming.get("file") as File);
const submit = await fetch(`${API}/extract`, { method: "POST", headers, body: upstream }).then((r) => r.json());
if (!submit.success) return NextResponse.json(submit, { status: 400 });
// Return the id now; the client polls GET /api/documents/[id] (which calls
// GET ${API}/documents/{id}), or use a webhook on Starter and Pro.
return NextResponse.json({ document_id: submit.document_id, status: submit.status });
}Python (requests)
import os, time, requests
API = "https://docuparseapi.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['DOCUPARSE_API_KEY']}"}
def parse_document(path: str) -> dict:
# 1. Submit
with open(path, "rb") as f:
submit = requests.post(f"{API}/extract", headers=HEADERS,
files={"file": (os.path.basename(path), f)}, timeout=30).json()
if not submit.get("success"):
raise RuntimeError(f"[{submit['error']['code']}] {submit['error']['message']}")
# 2. Fetch the result
for _ in range(40):
doc = requests.get(f"{API}/documents/{submit['document_id']}",
headers=HEADERS, timeout=30).json()
if doc["status"] == "completed":
return doc["data"]
if doc["status"] == "failed":
raise RuntimeError(f"Extraction failed: {submit['document_id']}")
time.sleep(1.5)
raise TimeoutError(submit["document_id"])
data = parse_document("receipt.pdf")
print(data["merchant"], data["total"], data["currency"])Response format
- queued· after POST
- processing· being read
- completed | failed· fetch it
Queue response (200)
{
"success": true,
"document_id": "doc_clx7abc123",
"status": "queued",
"message": "Document queued for extraction.",
"page_count": 12,
"quota_units": 2
}page_count is the number of pages read from the file (null when a PDF couldn't be read yet) and quota_units is how many documents the upload counts as: 1 for 1–10 pages, 2 for 11–20 pages. Limits per document: up to 20 pages. 1–10 pages count as one document, 11–20 pages as two.
Poll result endpoint
curl -X GET "https://docuparseapi.com/api/v1/documents/doc_clx7abc123" \
-H "Authorization: Bearer dex_your_api_key"Completed response (200)
{
"success": true,
"document_id": "<id>",
"status": "completed",
"data": {
"document_type": "invoice",
"document_number": "31293737",
"invoice_id": "31293737",
"receipt_id": null,
"date": "2020-12-15",
"due_date": null,
"currency": "USD",
"merchant": "Nunez Inc",
"merchant_address": "6551 Janet Gardens, Michaelberg, KS 90952",
"merchant_email": null,
"customer": "Alvarez Inc",
"subtotal": "371.51",
"tax": "37.15",
"tax_rate": "10.00",
"discount": null,
"shipping": null,
"total": "408.66",
"payment_method": null,
"notes": null,
"line_items": [
{
"description": "Women's black cocktail dress, size 4",
"quantity": 3,
"price": "19.99",
"line_total": "59.97",
"vat_rate": "10.00"
},
{
"description": "Christmas Dress Grace Karin Sleeveless Christmas dress Red Black size Large",
"quantity": 1,
"price": "9.99",
"line_total": "9.99",
"vat_rate": "10.00"
},
{
"description": "Windsor womens black and Pink Bodycon Sleeveless Dress, Womens Size Small",
"quantity": 1,
"price": "15.83",
"line_total": "15.83",
"vat_rate": "10.00"
},
{
"description": "Lumiere Large Multicolored Orange Short Dress NWT",
"quantity": 4,
"price": "4.99",
"line_total": "19.96",
"vat_rate": "10.00"
},
{
"description": "Xhilaration Womens L Burgandy Boho Embroidered Festival Mini Dress NEW",
"quantity": 4,
"price": "14.95",
"line_total": "59.80",
"vat_rate": "10.00"
},
{
"description": "Johnny Was Medium Linen Wrap Dress Dessert Rose Embroiderd Bohemian",
"quantity": 2,
"price": "89.99",
"line_total": "179.98",
"vat_rate": "10.00"
},
{
"description": "Pre-Owned Women's Nah Nah Dress Size 8 Black Sheer Formal Sexy",
"quantity": 2,
"price": "12.99",
"line_total": "25.98",
"vat_rate": "10.00"
}
]
}
}confidence is the worker quality score from 0–100 (or null), and needs_review indicates whether the extraction should be manually reviewed. Document types are receipt, invoice, purchase_order, expense_report, or unknown; line items can include a nullable vat_rate.
Failed response (422)
{
"success": false,
"document_id": "doc_clx7abc123",
"status": "failed",
"error": {
"code": "EXTRACTION_FAILED",
"message": "Document extraction failed."
}
}Error codes
| Code | HTTP status | Description |
|---|---|---|
| MISSING_API_KEY | 401 | Authorization header with Bearer token is required. |
| INVALID_API_KEY | 401 | The provided API key is invalid or does not exist. |
| REVOKED_API_KEY | 401 | The provided API key has been revoked. |
| LIMIT_EXCEEDED | 429 | Monthly document limit reached. Your Free plan includes 20 documents per month. Existing documents remain available. Upgrade to process more documents. |
| UNSUPPORTED_FILE_TYPE | 422 | File type not supported. Accepted types: PDF, JPG, JPEG, PNG, CSV. |
| FILE_TOO_LARGE | 422 | The file is too large · Compress the image or split the PDF |
| FILE_TOO_LARGE_AUTHENTICATED | 422 | The file is too large · Compress the image or split the PDF |
| TOO_MANY_PAGES | 422 | More than 20 pages · Split the PDF into parts of up to 20 pages |
| NO_FILE_PROVIDED | 422 | No file was provided. Include a file field in the multipart/form-data body. |
| EXTRACTION_FAILED | 500 | Document extraction failed. Please try again or contact support. |
| INTERNAL_ERROR | 500 | An unexpected internal error occurred. |
| DOCUMENT_NOT_FOUND | 404 | Document not found. |
| RESULT_NOT_READY | 202 | Extraction is still in progress. Poll again shortly. |
| BATCH_NOT_FOUND | 404 | Batch not found. |
| NO_FILES_PROVIDED | 422 | No files were provided. Include at least one file field in the multipart/form-data body. |
Rate limits
Rate limits apply per API key. Document limits are counted monthly and reset at the start of each calendar month.
| Plan | Monthly documents | When exceeded |
|---|---|---|
| Free | 20 | Requests return LIMIT_EXCEEDED (429) |
| Starter | 1,000 | Requests return LIMIT_EXCEEDED (429) |
| Pro | 5,000 | Requests return LIMIT_EXCEEDED (429) |
If you need a higher limit for a specific use case, contact support.
Best practices
- 01
Store keys server-side only
Never include your API key in browser JavaScript, mobile app source code, or public repositories. Use environment variables.
- 02
Handle errors explicitly
Check the error.code field in failure responses to route errors to the right handling logic. Don't rely solely on HTTP status codes.
- 03
Rotate keys periodically
Create new API keys and revoke old ones regularly. Each key should have a descriptive name (e.g. "production-app", "staging") for easy identification.
- 04
Use the document_id for deduplication
Each successful extraction returns a unique document_id. Store it to avoid re-processing the same file.
- 05
Check structured fields
Use needs_review and confidence to decide which documents get a manual check.
- 06
Photograph documents clearly
Photograph the whole document flat and in focus, and crop away the background.
Webhooks
Webhooks are available on the Starter and Pro plans. Add your HTTPS endpoint in Dashboard → Settings. When a document finishes, DocuParse API sends a POST request with the result. On Free, fetch results with GET /api/v1/documents/{document_id}.
Two events: document.completed for each finished document, and batch.completed once when a batch upload finishes (batch webhooks, Starter and Pro). For a batch, the batch.completed summary is sent first, then a document.completed for each document.
{
"event": "document.completed",
"document_id": "<id>",
"workspace_id": "<id>",
"status": "completed",
"document_type": "invoice",
"created_at": "<timestamp>",
"data": { "...": "same object as GET /api/v1/documents/{id} → data" }
}{
"event": "batch.completed",
"operation_id": "<id>",
"workspace_id": "<id>",
"status": "completed",
"document_count": 3,
"succeeded": 2,
"failed": 1,
"documents": [
{ "document_id": "<id>", "filename": "inv-1.pdf", "status": "completed",
"document_type": "invoice", "merchant": "…", "total": "…", "currency": "USD",
"extracted_data": { "...": "the extracted fields" } },
{ "document_id": "<id>", "filename": "inv-2.pdf", "status": "failed" }
],
"currency_totals": [{ "currency": "USD", "total": 1320.0 }],
"created_at": "<timestamp>",
"completed_at": "<timestamp>"
}Respond with any 2xx status. Keep your endpoint URL free of secrets in the path or query string.