Skip to main content
POST
/
expenses
/
ocr-extract
Scan receipt (OCR)
curl --request POST \
  --url https://api.contazen.ro/v1/expenses/ocr-extract \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: multipart/form-data' \
  --form image='@example-file'
{
  "tempFileToken": "<string>",
  "supplier": {
    "id": "<string>",
    "name": "<string>",
    "cui": "<string>",
    "matchedExisting": true
  },
  "category": {
    "id": "<string>",
    "name": "<string>"
  },
  "date": "2023-12-25",
  "dueDate": "<string>",
  "currency": "<string>",
  "amount": 123,
  "vatRate": 123,
  "vatAmount": 123,
  "vatMode": "flat",
  "total": 123,
  "vatIncluded": true,
  "vatBreakdown": [
    {
      "rate": 123,
      "net": 123,
      "gross": 123,
      "vatAmount": 123
    }
  ],
  "paymentMethod": "cash",
  "docNumber": "<string>",
  "description": "<string>",
  "items": [
    {
      "name": "<string>",
      "quantity": 123,
      "unit_price": 123,
      "vat_rate": 123
    }
  ],
  "warnings": [
    "<string>"
  ]
}

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 via POST /expenses once the user confirms — typically with vat = "mix" + amount_vat_manual for receipts flagged as multi-rate.

Request

Content-Type: multipart/form-data
image
file
required
Receipt image — JPEG, PNG, or WebP, up to 10 MB. Mobile clients should compress to JPEG before upload (HEIC is rejected).

Response (200)

The response is a normalized prefill payload, not a stored expense. Persist it via POST /expenses once the user confirms.
tempFileToken
string
Opaque token referencing the uploaded image staged on the server. Pass this to the attachment endpoint when posting the expense so the same image gets attached without a re-upload. Expires after 15 minutes.
supplier
object
Resolved supplier match.
  • id — CzUid of an existing supplier matched by CUI/name, or null for a new auto-created one
  • name, cui — best-effort values from the receipt
  • matchedExistingtrue when an existing supplier was found
category
object
Fuzzy-matched category from the firm’s list (system + custom merged): { id, name }. Both may be null when no match is found.
date
string
Issue date in YYYY-MM-DD. Falls back to today if unreadable.
dueDate
string
Due date in 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).
currency
string
3-letter ISO code (RON, EUR, USD).
amount
number
Net subtotal (excluding VAT). On the flat path this is what the user enters in the “Valoarea” field with with_vat = false.
vatRate
number
Dominant VAT percent. Rounded to one of the Romanian rates. On vatMode = "mix", this is informational only — use amount_vat_manual instead.
vatAmount
number
Total VAT amount across all rate tranches. On vatMode = "mix", send this back as amount_vat_manual when posting the expense.
vatMode
string
"flat" (single rate, send vat = <number>) or "mix" (two or more non-zero rate tranches, send vat = "mix" + amount_vat_manual = vatAmount).
total
number
Gross total printed on the receipt. Always equal to amount + vatAmount on the mix path.
vatIncluded
boolean
true when only the gross total was printed (typical bon fiscal). On mix mode this is false because amount is the explicit subtotal.
vatBreakdown
array
Per-rate breakdown when the receipt mixes more than one rate, otherwise 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.
paymentMethod
string
Payment method inferred from the document footer. One of "cash", "card", "transfer", "other", or null (typical for factură furnizor printed before payment). Map to payment_type when posting:
  • cash1 (Cash, “Numerar prin bon fiscal”)
  • card4 (Card)
  • transfer3 (Bank transfer / Ordin de plată)
  • other / null → caller decides
docNumber
string
Document / receipt / invoice number (reference in the create payload).
description
string
Free-text description, often null.
items
array
Optional. When present, each entry has 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.
warnings
array
Pre-localized Romanian strings safe to surface in the form alert. Examples:
  • "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

// OCR response
{
  "supplier":     { "id": "Q9hjAB", "name": "KAUFLAND ROMANIA SCS", "cui": "15991149" },
  "category":     { "id": "11", "name": "Alte consumabile" },
  "date":         "2026-04-25",
  "dueDate":      null,
  "currency":     "RON",
  "amount":       147.53,
  "vatAmount":    24.15,
  "vatMode":      "mix",
  "total":        171.68,
  "vatBreakdown": [
    { "rate": 21, "net": 80.29, "gross": 97.15, "vatAmount": 16.86 },
    { "rate": 11, "net": 66.27, "gross": 73.56, "vatAmount":  7.29 },
    { "rate":  0, "net":  0.97, "gross":  0.97, "vatAmount":  0.00 }
  ],
  "paymentMethod": "card",
  "docNumber":     "65776",
  "warnings":      ["Mai multe cote TVA"]
}
// → POST /expenses body
{
  "supplier_id":       "Q9hjAB",
  "category_id":       11,
  "reference":         "65776",
  "date":              "2026-04-25",
  "due_date":          "2026-04-25",   // dueDate was null → bon fiscal: due = issue
  "amount":            147.53,
  "vat":               "mix",
  "amount_vat_manual": 24.15,
  "currency":          "RON",
  "vat_breakdown": [
    { "rate": 21, "net": 80.29, "gross": 97.15, "vat_amount": 16.86 },
    { "rate": 11, "net": 66.27, "gross": 73.56, "vat_amount":  7.29 },
    { "rate":  0, "net":  0.97, "gross":  0.97, "vat_amount":  0.00 }
  ],
  "is_paid":      true,
  "paid_date":    "2026-04-25",
  "payment_type": 4                     // paymentMethod="card" → 4
}
The server stores amount_wvat = 147.53, amount_vat = 24.15, amount_total = 171.68 exactly.

Errors

  • 400 file_missing — no image part 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

Authorization
string
header
required

Use your API key (sk_live_xxx or sk_test_xxx)

Body

multipart/form-data
image
file
required

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.

tempFileToken
string

Opaque 15-min token for the staged image; pass through to attachments.

supplier
object
category
object
date
string<date>
dueDate
string | null
currency
string
amount
number

Net subtotal (excluding VAT).

vatRate
number

Dominant VAT rate.

vatAmount
number

Total VAT across all rate tranches.

vatMode
enum<string>

"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.

Available options:
flat,
mix
total
number

Gross total printed on the receipt.

vatIncluded
boolean
vatBreakdown
object[] | null
paymentMethod
enum<string> | null

Inferred payment method. Map to payment_type on POST: cash→1, card→4, transfer→3, other/null→caller decides.

Available options:
cash,
card,
transfer,
other,
null
docNumber
string | null
description
string | null
items
object[]

Optional. May be absent depending on document quality and structure. Clients that ignore this field still get correct totals via the aggregate fields.

warnings
string[]

Pre-localized Romanian strings safe to surface in the form alert.