DocuParse API Documentation
Extract structured JSON from receipts and invoices with a single HTTP request.
Quickstart
You can be sending requests in under 5 minutes:
- 1. Create a free account at DocuParse.
- 2. Go to Dashboard → API Keys and create a new key. Copy it immediately — it's shown only once.
- 3. Send your first request using the example below.
curl -X POST "https://YOUR_DOMAIN/api/v1/extract" \
-H "Authorization: Bearer dex_your_api_key_here" \
-F "file=@receipt.pdf"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 or CSV. Max 10MB. |
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
curl -X POST "https://YOUR_DOMAIN/api/v1/extract" \
-H "Authorization: Bearer dex_your_api_key" \
-F "file=@receipt.pdf"Node.js (using built-in fetch)
// IMPORTANT: Run this server-side only. Never expose your API key in the browser.
// Store API_KEY in an environment variable.
const fs = require("fs");
const path = require("path");
async function extractDocument(filePath) {
const apiKey = process.env.DOCUEXTRACT_API_KEY; // e.g. "dex_..."
const form = new FormData();
const fileBuffer = fs.readFileSync(filePath);
const filename = path.basename(filePath);
const blob = new Blob([fileBuffer]);
form.append("file", blob, filename);
const res = await fetch("https://YOUR_DOMAIN/api/v1/extract", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
},
body: form,
});
const data = await res.json();
if (!data.success) {
// data.error.code is one of: MISSING_API_KEY, INVALID_API_KEY,
// REVOKED_API_KEY, LIMIT_EXCEEDED, UNSUPPORTED_FILE_TYPE,
// FILE_TOO_LARGE, EXTRACTION_FAILED, INTERNAL_ERROR
throw new Error(`[${data.error.code}] ${data.error.message}`);
}
return data;
}
// Usage
extractDocument("./receipt.pdf")
.then((result) => {
console.log("Document ID:", result.document_id);
console.log("Status:", result.status);
})
.catch(console.error);Next.js API Route (server-side)
// app/api/upload-receipt/route.ts
// This is a Next.js App Router API route that proxies the call to DocuParse.
// The secret API key stays on the server.
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const apiKey = process.env.DOCUEXTRACT_API_KEY;
if (!apiKey) {
return NextResponse.json({ error: "Server misconfiguration." }, { status: 500 });
}
// Forward the multipart form from the client to DocuParse
const formData = await req.formData();
const file = formData.get("file");
if (!file || !(file instanceof File)) {
return NextResponse.json({ error: "No file provided." }, { status: 400 });
}
const upstream = new FormData();
upstream.append("file", file);
const res = await fetch("https://YOUR_DOMAIN/api/v1/extract", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: upstream,
});
const data = await res.json();
if (!data.success) {
return NextResponse.json(
{ error: data.error.message, code: data.error.code },
{ status: res.status }
);
}
return NextResponse.json(data);
}Python (requests)
# IMPORTANT: Run server-side only. Store your key in an environment variable.
import os
import requests
def extract_document(file_path: str) -> dict:
api_key = os.environ["DOCUEXTRACT_API_KEY"] # e.g. "dex_..."
with open(file_path, "rb") as f:
response = requests.post(
"https://YOUR_DOMAIN/api/v1/extract",
headers={"Authorization": f"Bearer {api_key}"},
files={"file": (os.path.basename(file_path), f)},
)
data = response.json()
if not data.get("success"):
# Structured error: data["error"]["code"] and data["error"]["message"]
raise RuntimeError(f"[{data['error']['code']}] {data['error']['message']}")
return data
# Usage
result = extract_document("receipt.pdf")
print("Merchant:", result["data"]["merchant"])
print("Total:", result["data"]["total"])
print("Status:", result["status"])Raw fetch (browser UNSAFE — for demonstration only)
Do not use in production browser code.
This example is for reference only. In production, proxy the request through your own server so the API key is never exposed to the client.
// DEMO ONLY — DO NOT EXPOSE API KEYS IN BROWSER CODE
// Use this pattern only in a backend/server-side context.
const form = new FormData();
form.append("file", fileInput.files[0]);
const res = await fetch("https://YOUR_DOMAIN/api/v1/extract", {
method: "POST",
headers: {
// In real production: call YOUR backend instead, never put the key here
Authorization: "Bearer dex_your_api_key",
},
body: form,
});
const data = await res.json();
if (data.success) {
console.log(data.data);
} else {
console.error(data.error.code, data.error.message);
}Response format
Queue response (200)
{
"success": true,
"document_id": "doc_clx7abc123",
"status": "queued",
"message": "Document queued for extraction."
}Poll result endpoint
curl -X GET "https://YOUR_DOMAIN/api/v1/documents/doc_clx7abc123" -H "Authorization: Bearer dex_your_api_key"Completed response (200)
{
"success": true,
"document_id": "doc_clx7abc123",
"status": "completed",
"data": {
"merchant": "Office Depot",
"currency": "USD",
"subtotal": 42.00,
"tax": 3.50,
"total": 45.50
}
}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 | No Authorization header provided. |
| INVALID_API_KEY | 401 | The key does not exist. |
| REVOKED_API_KEY | 401 | The key was revoked. |
| LIMIT_EXCEEDED | 429 | Monthly document limit reached. |
| UNSUPPORTED_FILE_TYPE | 422 | File is not JPG, PNG, or PDF. |
| FILE_TOO_LARGE | 422 | File exceeds 10MB. |
| NO_FILE_PROVIDED | 422 | No file field in the request. |
| EXTRACTION_FAILED | 500 | Extraction pipeline failed. Retry or contact support. |
| INTERNAL_ERROR | 500 | Unexpected server error. |
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 | 50 | Requests return LIMIT_EXCEEDED (429) |
| Starter | 500 | Overage charged at $0.10/doc |
| Pro | 5,000 | Overage charged at $0.05/doc |
If you need a higher limit for a specific use case, contact support.
Best practices
Store keys server-side only
Never include your API key in browser JavaScript, mobile app source code, or public repositories. Use environment variables.
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.
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.
Use the document_id for deduplication
Each successful extraction returns a unique document_id. Store it to avoid re-processing the same file.
Check confidence scores
The confidence.overall field indicates extraction reliability. Consider flagging results with overall < 0.7 for manual review.
Compress images before uploading
High-resolution images can be compressed without losing relevant text data. Keeping files under 2MB improves processing time.
Webhooks
Webhook delivery will allow you to receive extraction results via HTTP POST to your endpoint instead of polling. You will be able to configure a webhook URL and secret in Dashboard → Settings.
Planned events: extraction.completed extraction.failed limit.warning