A receipt is unstructured visual data — pixels arranged to look like text. A JSON object with named fields is structured data your application can store, query, display, and process. Converting between those two representations is what a receipt-to-JSON API does.
Here's the complete picture: the request format, the response structure, what every field means, and working code in four languages.
The Request
curl -X POST https://docuparseapi.com/api/v1/extract \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@receipt.jpg"
That's the entire integration surface. One endpoint, one file parameter, one authorization header.
Accepted file types: JPG, PNG, PDF Maximum file size: 10MB Accepted sources: Scanned documents, photos, digital PDFs — the API handles OCR automatically when needed
Get a free API key at docuparseapi.com/signup — 20 documents/month, no credit card.
The Response
{
"success": true,
"document_id": "doc_clx7abc123",
"document_type": "receipt",
"merchant": "Target",
"date": "2026-05-14",
"currency": "USD",
"subtotal": "43.97",
"tax": "3.52",
"total": "47.49",
"tax_rate": "8.00%",
"receipt_id": "4821-0594",
"payment_method": "Mastercard",
"line_items": [
{
"description": "Cotton T-Shirt",
"quantity": 2,
"unit_price": "9.99",
"amount": "19.98"
},
{
"description": "Notebook - 5 pack",
"quantity": 1,
"unit_price": "7.99",
"amount": "7.99"
},
{
"description": "USB-C Cable",
"quantity": 1,
"unit_price": "15.99",
"amount": "15.99"
}
],
"processing_time_ms": 2980
}Field Reference
merchantstring | null✓ YesStore or vendor namedatestring | null✓ YesISO 8601 transaction datetotalstring | null✓ YesFinal charged amountsubtotalstring | nullIf presentPre-tax amounttaxstring | null✓ YesTax amount chargedtax_ratestring | nullIf presentTax percentagecurrencystring | null✓ YesISO 4217 currency codereceipt_idstring | nullIf presentReceipt or transaction numberpayment_methodstring | nullIf presentCard, cash, etc.line_itemsarray✓ YesItems: description, quantity, amountdocument_idstring✓ YesUnique extraction ID for dedup| Field | Type | Description |
|---|---|---|
success |
boolean | Whether extraction succeeded |
document_id |
string | Unique ID for this extraction — use for deduplication |
document_type |
string | "receipt" or "invoice" |
merchant |
string | null | Store or vendor name |
date |
string | null | Transaction date in ISO 8601 (YYYY-MM-DD) |
currency |
string | null | ISO 4217 code: USD, EUR, GBP, etc. |
subtotal |
string | null | Pre-tax amount |
tax |
string | null | Tax amount |
tax_rate |
string | null | Tax percentage |
total |
string | null | Final charged amount |
receipt_id |
string | null | The receipt's own identifier |
payment_method |
string | null | Card type, cash, etc. |
line_items |
array | Individual items — see below |
processing_time_ms |
number | Extraction time in milliseconds |
Why monetary values are strings: Floating-point arithmetic introduces rounding errors in financial calculations. 42.10 stored as a float can become 42.09999999 in some languages. Strings preserve the exact decimal representation from the original document. Convert to Decimal or BigDecimal (not float) for arithmetic.
Missing fields return null, not omitted: If a receipt has no receipt ID, the response contains "receipt_id": null — not a missing key. This means you can safely access any field without checking for key existence first.
Code in Four Languages
Working With the Structured Data
Store in a database
import sqlite3
import json
from decimal import Decimal
def store_receipt(conn: sqlite3.Connection, receipt: dict) -> int:
conn.execute("""
CREATE TABLE IF NOT EXISTS receipts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
document_id TEXT UNIQUE,
merchant TEXT,
date TEXT,
currency TEXT,
total REAL,
tax REAL,
payment_method TEXT,
line_items TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
cursor = conn.execute("""
INSERT OR IGNORE INTO receipts
(document_id, merchant, date, currency, total, tax, payment_method, line_items)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
receipt.get("document_id"),
receipt.get("merchant"),
receipt.get("date"),
receipt.get("currency"),
float(receipt.get("total") or 0),
float(receipt.get("tax") or 0),
receipt.get("payment_method"),
json.dumps(receipt.get("line_items", [])),
))
conn.commit()
return cursor.lastrowidPrevent duplicate submissions
Use document_id — it's unique per extraction:
existing = db.receipts.find_one({"document_id": receipt["document_id"]})
if existing:
return {"status": "duplicate", "existing_id": existing["id"]}Aggregate spending by merchant
receipts = [receipt_to_json(f) for f in receipt_files]
by_merchant = {}
for r in receipts:
m = r.get("merchant") or "Unknown"
by_merchant[m] = by_merchant.get(m, 0) + float(r.get("total") or 0)
for merchant, total in sorted(by_merchant.items(), key=lambda x: -x[1]):
print(f"{merchant}: ${total:.2f}")Error Response Format
When success is false:
{
"success": false,
"error": {
"code": "EXTRACTION_FAILED",
"message": "Document extraction failed for this file."
}
}Error codes:
| Code | Meaning | What to do |
|---|---|---|
MISSING_API_KEY |
No authorization header | Add Authorization: Bearer YOUR_KEY |
INVALID_API_KEY |
Key not found | Check the key, regenerate if needed |
LIMIT_EXCEEDED |
Monthly document limit reached | Upgrade plan or wait for reset |
UNSUPPORTED_FILE_TYPE |
Not PDF, JPG, or PNG | Convert file format before sending |
FILE_TOO_LARGE |
Over 10MB | Compress the file |
EXTRACTION_FAILED |
Document couldn't be parsed | Try a cleaner scan or a different file |