parsr.

Specialist parser

Receipts to JSON, in any language

Extract merchant, items, taxes, totals, and payment method from photos and PDFs of receipts. Tax-jurisdiction aware. 95%+ field-level accuracy on EU and US printed receipts. Phone photos, scans, HEIC, thermal paper — all in.

parse-receipt.shbash
curl -X POST https://eu-api.tryparsr.dev/v1/parse/receipt \
  -H "Authorization: Bearer $PARSR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "document_url": "https://example.com/cafe-brussels.heic",
    "wait": 30
  }'

Format coverage

6 receipt categories

Languages

40+

Avg latency

~2.1s p50

Field accuracy

95%+ field-level

What we extract

Every field, with confidence and citations

Every field comes back with a confidence score in [0,1] and a normalized bounding box on the source page. Line-item totals and tax breakdown are reconciled against the printed total before the response is returned.

Input

Phone photo of a Brussels café receipt, EUR, 21% Belgian VAT (HEIC, ~150 DPI equivalent)

Anonymized receipts preview
response.jsonjson
{
  "schema_version": "receipt.v1",
  "result": {
    "merchant_name": "Café Belga",
    "merchant_address": "Place Eugène Flagey 18, 1050 Ixelles, Belgium",
    "merchant_vat_id": "BE0456789012",
    "transaction_datetime": "2026-04-22T13:47:00+02:00",
    "currency": "EUR",
    "line_items": [
      {
        "description": "Croque-monsieur",
        "quantity": "1",
        "unit_price": { "amount": "12.50", "currency": "EUR" },
        "line_total": { "amount": "12.50", "currency": "EUR" },
        "tax_rate": "0.12",
        "category": "food",
        "confidence": 0.96,
        "bbox": { "page": 1, "x": 0.08, "y": 0.31, "w": 0.84, "h": 0.022 }
      },
      {
        "description": "Salade chèvre chaud",
        "quantity": "1",
        "unit_price": { "amount": "16.00", "currency": "EUR" },
        "line_total": { "amount": "16.00", "currency": "EUR" },
        "tax_rate": "0.12",
        "category": "food",
        "confidence": 0.95
      },
      {
        "description": "Stella Artois 25cl",
        "quantity": "2",
        "unit_price": { "amount": "4.20", "currency": "EUR" },
        "line_total": { "amount": "8.40", "currency": "EUR" },
        "tax_rate": "0.21",
        "category": "alcohol",
        "confidence": 0.97
      },
      {
        "description": "Café espresso",
        "quantity": "2",
        "unit_price": { "amount": "2.80", "currency": "EUR" },
        "line_total": { "amount": "5.60", "currency": "EUR" },
        "tax_rate": "0.12",
        "category": "beverage",
        "confidence": 0.98
      }
    ],
    "subtotal": { "amount": "37.62", "currency": "EUR" },
    "tax_breakdown": [
      { "rate": "0.12", "taxable_amount": "30.45", "amount": "3.65" },
      { "rate": "0.21", "taxable_amount": "6.94",  "amount": "1.46" }
    ],
    "tip": { "amount": "0.00", "currency": "EUR" },
    "total": { "amount": "42.50", "currency": "EUR" },
    "payment_method": "card_visa",
    "validation": {
      "totals_reconcile": {
        "valid": true,
        "computed_total": "42.50",
        "declared_total": "42.50",
        "diff": "0.00",
        "tolerance": "0.01"
      }
    }
  },
  "field_metadata": {
    "merchant_vat_id": { "confidence": 0.94, "checksum_valid": true },
    "total.amount":    { "confidence": 0.99 }
  }
}
FieldTypeDescriptionConf. typical
merchant_namestringMerchant as printed on the receipt. Returned verbatim — no normalization to a merchant database (vision LLM handles any layout).97%
merchant_addressstringSingle-line address as printed. Country is inferred from address + currency for tax-rate validation.94%
merchant_vat_idstringEU VAT number when printed. Validated against the country-specific format (BE: 10 digits, DE: 9 digits, FR: 11 chars, etc.). Surfaced in field_metadata.checksum_valid.95%
transaction_datetimedatetime (ISO 8601)Date and time of purchase, with timezone inferred from merchant address. Date-only receipts return the date with no time component.96%
currencystring (ISO 4217)Receipt currency. Detected from the printed symbol or code; multi-currency receipts (e.g. duty-free with EUR + USD) return per-line currency.98%
line_items[]array of LineItemEach item: description, quantity, unit_price, line_total, tax_rate, category (food | alcohol | beverage | retail | service | other), confidence, bbox. Receipts without itemization (just a total) return an empty array.95%
subtotalmoney { amount, currency }Pre-tax total of all line items. Computed when the receipt only prints the post-tax total.96%
tax_breakdown[]array of TaxLinePer-rate breakdown: rate, taxable_amount, amount. Most EU receipts print at least two rates (e.g. 12% on food + 21% on alcohol in Belgium).94%
totalmoney { amount, currency }Total amount paid as printed. Drives validation.totals_reconcile.99%
payment_methodstring (enum)card_visa | card_mastercard | card_amex | card_other | cash | bancontact | apple_pay | google_pay | paypal | voucher | unknown.93%
validation.totals_reconcileobjectsubtotal + Σ tax_breakdown.amount + tip = total, within 1-cent tolerance. valid=false flags OCR error, missing line, or tampering.100%

Domain-specific validation

What makes this a specialist

Totals reconciliation

subtotal + Σ tax_breakdown.amount + tip = total, computed and returned per receipt, within a 1-cent tolerance for rounding. valid=false catches OCR errors, missed line items, and tampered receipts before they reach your accounting system.

validation.totals_reconcile.valid

exampleReceipt prints total 42.50 EUR; computed from subtotal 37.62 + taxes 5.11 = 42.73 EUR. diff: 0.23. Expense system surfaces for human review instead of silently posting an off-by-23-cents entry.

Line-item math

For each line item, quantity × unit_price = line_total, within a 1-cent tolerance. Catches OCR errors on smudged unit prices or transposed digits in line totals.

validation.line_items[i].math_valid

exampleLine reads '2 × 4.20 = 8.04' (4 mis-OCR'd as 0). Validator computes 8.40, marks math_valid=false on that line, and lowers field-level confidence so the agent can re-prompt or escalate.

VAT-rate plausibility

Printed tax rates are checked against the merchant's jurisdiction (inferred from address + currency). A Belgian café charging 7% on food is implausible — the validator surfaces, doesn't auto-correct.

validation.vat_rates_plausible

exampleBelgian receipt with line tax_rate 0.07 (Germany's reduced rate). Returned as plausible=false with expected_rates=[0.06, 0.12, 0.21]. Bookkeeping agent re-checks the OCR before posting.

Currency consistency

Per-line currency must match receipt-level currency unless the receipt is explicitly multi-currency (duty-free, dual-pricing border shops). Pure mismatches are flagged, not silently coerced.

validation.currency_consistency.valid

exampleReceipt-level currency EUR but a line reports USD with no FX column. Returned as inconsistency=true so the integration knows not to sum across rows blindly.

Format coverage

Tested across six receipt categories, EU + US

6 categories · 40+ languages · printed and handwritten totals · thermal, laser, and PDF

Restaurants

  • Table service
  • Fast food
  • Café & bar
  • Delivery (Uber Eats, Deliveroo, Wolt)
  • Catering
  • Hotel F&B

Retail

  • Grocery (Carrefour, Albert Heijn, Tesco)
  • Big-box (IKEA, Decathlon)
  • Specialty & boutique
  • Pharmacy
  • Hardware & DIY
  • E-commerce printable receipts

Travel

  • Hotels & B&B
  • Airline boarding & e-tickets
  • Rail (SNCB, DB, SNCF, Trenitalia)
  • Parking & tolls
  • Rideshare (Uber, Bolt, FreeNow)
  • Taxi

Fuel & automotive

  • Gas stations (Shell, BP, Total, Q8)
  • EV charging (Allego, Ionity, Tesla)
  • Car wash
  • Tire & service
  • Roadside assistance

Services

  • Healthcare & pharmacy
  • Professional services (legal, accounting)
  • Coworking day passes
  • Event tickets
  • Repair & maintenance

Subscriptions & digital

  • SaaS invoices (Stripe, Paddle)
  • App store receipts (Apple, Google)
  • Streaming (Netflix, Spotify)
  • Telco bills
  • Cloud usage receipts

Code recipes

From document to JSON in five lines

parse.shbash
curl -X POST https://eu-api.tryparsr.dev/v1/parse/receipt \
  -H "Authorization: Bearer $PARSR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "document_url": "https://example.com/cafe-brussels.heic",
    "wait": 30
  }'
parse_receipt.pypython
import os, httpx

resp = httpx.post(
    "https://eu-api.tryparsr.dev/v1/parse/receipt",
    headers={
        "Authorization": f"Bearer {os.environ['PARSR_API_KEY']}",
        "Idempotency-Key": os.urandom(16).hex(),
    },
    json={
            "document_url": "https://example.com/cafe-brussels.heic",
        "wait": 30,
    },
    timeout=40,
)
result = resp.json()["result"]
reconcile = result["validation"]["totals_reconcile"]
if not reconcile["valid"]:
    raise ValueError(f"Totals don't reconcile — diff {reconcile['diff']}")
for item in result["line_items"]:
    print(item["description"], item["line_total"]["amount"])
print("TOTAL:", result["total"]["amount"], result["currency"])
parseReceipt.tstypescript
const resp = await fetch("https://eu-api.tryparsr.dev/v1/parse/receipt", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PARSR_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    document_url: "https://example.com/cafe-brussels.heic",
    wait: 30,
  }),
});
const { result } = await resp.json();
if (!result.validation.totals_reconcile.valid) {
  throw new Error(
    `totals don't reconcile — diff ${result.validation.totals_reconcile.diff}`,
  );
}
console.log(`${result.merchant_name}: ${result.total.amount} ${result.currency}`);
agent.pypython
from langchain_parsr import ParsrToolkit
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

tools = ParsrToolkit.from_env().get_tools()
agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools)

result = await agent.ainvoke({
    "messages": [(
        "user",
        "Parse this lunch receipt and categorize it for our expense system: "
        "https://example.com/cafe-brussels.heic"
    )]
})
print(result["messages"][-1].content)

Compared

How parsr's receipt parsing compares

VendorPricing per pageEU residencyConfidence + bboxTotals reconciliationMulti-language coverage
parsrfrom €0.022Default (eu-api region-bound key)Per field, per line itemYes — built into response40+ including Polish, Czech, Greek, Turkish
Veryfi$500/mo minimumNo — US-hostedYesNoStrong on Latin scripts, US-focused
Mindee~$0.10 (Pro tier)Pro tier+ only (€179/mo entry)YesNoEN/FR strong; others vary by model version
ReductoCustomGrowth tier (custom)PartialNoGeneral-purpose parser
DocuPipeQuote-basedNoYesNoDashboard-driven, custom configs

New receipt format or edge case? 48 hours.

We don't train models — we curate prompts, schemas, validators, and fixture tests. A new POS layout, a regional VAT quirk, a thermal-paper degradation pattern: send a sample (anonymized fine) and it's live in two business days. Mindee's pre-trained models take months. Email support@ and we'll confirm within 24 hours.

Request a format →

FAQ

Common questions

  • Will phone photos work, or do I need a flatbed scan?

    Phone photos work. The vision LLM tolerates skew, glare, and creases down to roughly 150 DPI equivalent (a typical iPhone photo of an A6 receipt held in hand). Confidence drops on heavy motion blur, deep shadows across the totals line, or paper folded across columns — surfaced through field_metadata.<field>.confidence so your agent escalates only when needed. HEIC, JPEG, PNG, PDF, and multi-page TIFF all in.

  • How well does it handle non-English / non-Latin receipts?

    40+ languages out of the box, including Polish, Czech, Hungarian, Greek, and Turkish alongside the usual EN/FR/DE/ES/IT/NL/PT. Field names and tax-rate plausibility are jurisdiction-aware — a Greek receipt is checked against Greek VAT rates (24% / 13% / 6%), a Czech receipt against Czech rates (21% / 12%). Cyrillic and CJK receipts are supported but accuracy on those scripts is in the 88–92% range rather than 95%+; we surface that honestly via per-field confidence.

  • What if a receipt has multiple currencies on it?

    Duty-free and border-shop receipts often print prices in two currencies (e.g. EUR + CHF, or EUR + GBP). The parser returns receipt-level currency as the dominant one and per-line currency on each line_item where it differs. validation.currency_consistency reports drift instead of silently coercing — your integration can decide whether to sum, convert at the printed rate, or escalate.

  • What about faded thermal-paper receipts?

    Thermal paper that's been in a wallet for a few weeks is the hardest case. parsr extracts what's legible and lowers confidence on the rest — typical accuracy on aged thermal sits in the 80–90% range vs 95%+ on fresh prints. Use field_metadata.<field>.confidence < 0.85 as the recommended escalation threshold; do not treat low-confidence fields as ground truth. We don't claim accuracy we can't deliver on bad inputs.

  • Some of my receipts are just a total — no line items. Does that still work?

    Yes. line_items returns an empty array, and merchant, total, currency, payment_method, and tax_breakdown are populated from whatever the receipt prints. Totals reconciliation simply checks tax_breakdown + tip = total when there's no subtotal to verify against. Common case: parking tickets, vending receipts, and bar tabs that only print the round amount.

  • Does it support iPhone HEIC straight from the camera roll?

    Yes. HEIC, HEIF, JPEG, PNG, WebP, PDF, TIFF — uploaded as multipart/form-data or as a public URL. iOS shares HEIC by default and we don't ask users to convert before upload. EXIF rotation is honored. Live Photos are accepted as the still frame. Max 25MB per file on the standard tier; larger via signed URLs.

200 free pages. No credit card. No sales call.

Drop receipts parsing into your stack in an afternoon. If it doesn't earn its keep, walk away — no lock-in.

Get an API key