Scan receipt (OCR)
Upload a receipt or invoice image and get a structured prefill payload ready to feed into Create Expense.
Overview
Send a photo of a Romanian receipt (bon fiscal), chitanță, or supplier invoice and receive a structured prefill payload describing the document — supplier match, dates, totals, per-rate VAT breakdown, optionally per-line items, and a payment-method hint. Use the response to prefill an expense form. Persist viaPOST /expenses once the user confirms — typically with vat = "mix" + amount_vat_manual for receipts flagged as multi-rate.
Request
Content-Type: multipart/form-data
Response (200)
The response is a normalized prefill payload, not a stored expense. Persist it viaPOST /expenses once the user confirms.
id— CzUid of an existing supplier matched by CUI/name, ornullfor a new auto-created onename,cui— best-effort values from the receiptmatchedExisting—truewhen an existing supplier was found
{ id, name }. Both may be null when no match is found.YYYY-MM-DD. Falls back to today if unreadable.YYYY-MM-DD, or null for receipts paid at the point of sale (bon fiscal). When null, the form should auto-mark the expense as paid with paid_date = date and payment_type derived from paymentMethod (or 1 = Cash as fallback).with_vat = false.vatMode = "mix", this is informational only — use amount_vat_manual instead.vatMode = "mix", send this back as amount_vat_manual when posting the expense."flat" (single rate, send vat = <number>) or "mix" (two or more non-zero rate tranches, send vat = "mix" + amount_vat_manual = vatAmount).amount + vatAmount on the mix path.true when only the gross total was printed (typical bon fiscal). On mix mode this is false because amount is the explicit subtotal.null. Each entry: { rate, net, gross, vatAmount } — any of net / gross may be null.On vatMode = "mix", send this array back via vat_breakdown when posting so reports keep accurate per-rate totals."cash", "card", "transfer", "other", or null (typical for factură furnizor printed before payment). Map to payment_type when posting:cash→1(Cash, “Numerar prin bon fiscal”)card→4(Card)transfer→3(Bank transfer / Ordin de plată)other/null→ caller decides
reference in the create payload).null.name, quantity, unit_price (NET), vat_rate. May be absent depending on document quality and structure — clients that ignore this field still get correct totals via the aggregate fields."Furnizor nou"— supplier auto-created"Mai multe cote TVA"— multi-rate detected, switch UI to mix mode"Liniile au fost ignorate ..."— items omitted; rely on the aggregate fields
Mapping the response to Create Expense
amount_wvat = 147.53, amount_vat = 24.15, amount_total = 171.68 exactly.
Errors
- 400
file_missing— noimagepart in the multipart body - 400
invalid_file_type— unsupported extension (must be jpg/jpeg/png/webp) - 400
invalid_mime_type— content sniff didn’t match an accepted image MIME - 400
file_too_large— file exceeds 10 MB - 502
ocr_unavailable— extraction failed; safe to retry - 503
ocr_not_configured— OCR is not enabled on this deployment
Authorizations
Use your API key (sk_live_xxx or sk_test_xxx)
Body
Receipt image (JPEG / PNG / WebP, ≤ 10 MB).
Response
Extracted prefill payload
Normalized prefill returned by POST /expenses/ocr-extract. Map to a Create Expense body — see the OCR endpoint guide for the full translation table.
Opaque 15-min token for the staged image; pass through to attachments.
Net subtotal (excluding VAT).
Dominant VAT rate.
Total VAT across all rate tranches.
"flat" = single rate; send vat = <number> on POST.
"mix" = two or more non-zero rate tranches; send vat = "mix"
+ amount_vat_manual = vatAmount on POST.
flat, mix Gross total printed on the receipt.
Inferred payment method. Map to payment_type on POST:
cash→1, card→4, transfer→3, other/null→caller decides.
cash, card, transfer, other, null Optional. May be absent depending on document quality and structure. Clients that ignore this field still get correct totals via the aggregate fields.
Pre-localized Romanian strings safe to surface in the form alert.