> ## Documentation Index
> Fetch the complete documentation index at: https://docs.contazen.ro/llms.txt
> Use this file to discover all available pages before exploring further.

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

## Request

`Content-Type: multipart/form-data`

<ParamField body="image" type="file" required>
  Receipt image — JPEG, PNG, or WebP, up to 10 MB. Mobile clients should compress to JPEG before upload (HEIC is rejected).
</ParamField>

## Response (200)

The response is a normalized prefill payload, not a stored expense. Persist it via [`POST /expenses`](./create) once the user confirms.

<ResponseField name="tempFileToken" type="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.
</ResponseField>

<ResponseField name="supplier" type="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
  * `matchedExisting` — `true` when an existing supplier was found
</ResponseField>

<ResponseField name="category" type="object">
  Fuzzy-matched category from the firm's list (system + custom merged): `{ id, name }`. Both may be `null` when no match is found.
</ResponseField>

<ResponseField name="date" type="string" format="date">
  Issue date in `YYYY-MM-DD`. Falls back to today if unreadable.
</ResponseField>

<ResponseField name="dueDate" type="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).
</ResponseField>

<ResponseField name="currency" type="string">3-letter ISO code (RON, EUR, USD).</ResponseField>

<ResponseField name="amount" type="number">
  Net subtotal (excluding VAT). On the **flat** path this is what the user enters in the "Valoarea" field with `with_vat = false`.
</ResponseField>

<ResponseField name="vatRate" type="number">
  Dominant VAT percent. Rounded to one of the Romanian rates. On `vatMode = "mix"`, this is informational only — use `amount_vat_manual` instead.
</ResponseField>

<ResponseField name="vatAmount" type="number">
  Total VAT amount across all rate tranches. **On `vatMode = "mix"`, send this back as `amount_vat_manual` when posting the expense.**
</ResponseField>

<ResponseField name="vatMode" type="string">
  `"flat"` (single rate, send `vat = <number>`) or `"mix"` (two or more non-zero rate tranches, send `vat = "mix"` + `amount_vat_manual = vatAmount`).
</ResponseField>

<ResponseField name="total" type="number">
  Gross total printed on the receipt. Always equal to `amount + vatAmount` on the mix path.
</ResponseField>

<ResponseField name="vatIncluded" type="boolean">
  `true` when only the gross total was printed (typical bon fiscal). On mix mode this is `false` because `amount` is the explicit subtotal.
</ResponseField>

<ResponseField name="vatBreakdown" type="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.
</ResponseField>

<ResponseField name="paymentMethod" type="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:

  * `cash` → `1` (Cash, "Numerar prin bon fiscal")
  * `card` → `4` (Card)
  * `transfer` → `3` (Bank transfer / Ordin de plată)
  * `other` / `null` → caller decides
</ResponseField>

<ResponseField name="docNumber" type="string">
  Document / receipt / invoice number (`reference` in the create payload).
</ResponseField>

<ResponseField name="description" type="string">Free-text description, often `null`.</ResponseField>

<ResponseField name="items" type="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.
</ResponseField>

<ResponseField name="warnings" type="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
</ResponseField>

## Mapping the response to Create Expense

```jsonc theme={null}
// 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"]
}
```

```jsonc theme={null}
// → 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


## OpenAPI

````yaml POST /expenses/ocr-extract
openapi: 3.1.0
info:
  title: Contazen API
  version: 1.2.0
  description: >
    Build powerful integrations with the Contazen invoicing platform. The
    Contazen API is organized around REST, 

    has predictable resource-oriented URLs, accepts JSON request bodies, returns
    JSON-encoded responses, 

    and uses standard HTTP response codes, authentication, and verbs.


    ## Authentication

    The API uses Bearer token authentication. Include your API key in the
    Authorization header.


    ### Getting your API Key

    1. Log in to your Contazen account

    2. Navigate to Settings > API

    3. Generate or copy your API key (starts with `sk_live_` for production or
    `sk_test_` for testing)


    ### Using the API Key

    Include your API key in the Authorization header:

    ```

    Authorization: Bearer sk_live_YOUR_API_KEY

    ```


    ### Example Request with cURL

    ```bash

    curl --request GET \
      --url https://api.contazen.ro/v1/clients \
      --header 'Authorization: Bearer sk_live_YOUR_API_KEY' \
      --header 'Accept: application/json'
    ```


    ## Rate Limiting

    - 1000 requests per hour per API key

    - 100 create operations per minute per API key


    Rate limit information is included in response headers:

    - `X-RateLimit-Limit`: Maximum requests allowed

    - `X-RateLimit-Remaining`: Requests remaining

    - `X-RateLimit-Reset`: Reset time (Unix timestamp)


    ## Pagination

    All list endpoints return paginated results with the following format:

    ```json

    {
      "success": true,
      "data": {
        "object": "list",
        "data": [...],
        "has_more": true,
        "total": 245,
        "page": 1,
        "per_page": 50,
        "total_pages": 5
      },
      "meta": {
        "version": "v1",
        "request_id": "req_1a2b3c4d",
        "response_time": "23.45ms"
      }
    }

    ```


    ## Multi-Work-Point Access

    API keys belong to a specific work point but can access data from all work
    points

    within the same parent company.


    ## Error Handling

    The API uses conventional HTTP response codes to indicate success or
    failure. 

    In general: 2xx codes indicate success, 4xx codes indicate an error due to
    the 

    information provided, and 5xx codes indicate an error with Contazen's
    servers.


    ## Expanding Nested Objects

    Many endpoints support the `expand` parameter to include related objects in
    the response.

    This follows the Stripe API pattern. For example:

    - `expand[]=lines` - Include invoice line items

    - `expand[]=payments` - Include payment records

    - `expand[]=client` - Include full client object


    ## Localization

    The API supports multiple languages through:

    - `locale` query parameter (en, ro)

    - `Accept-Language` header

    - Default: English
  contact:
    name: Contazen Support
    email: support@contazen.ro
    url: https://contazen.ro
  license:
    name: Proprietary
    url: https://www.contazen.ro/termeni-si-conditii-de-utilizare/
servers:
  - url: https://api.contazen.ro/v1
    description: Production API server
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: API authentication and test endpoints
  - name: Clients
    description: Manage your customers (B2B and B2C)
  - name: Invoices
    description: Create and manage invoices, proformas, and receipts
  - name: Products
    description: Manage your product and service catalog
  - name: Expenses
    description: Track and manage business expenses
  - name: Expense Categories
    description: Organize expenses with categories
  - name: Suppliers
    description: Manage expense suppliers and vendors
  - name: Settings
    description: API settings and configuration
  - name: Payments
    description: Payments received against invoices
  - name: Receipts
    description: Cash receipts (chitanțe) — standalone or paired with an invoice
  - name: Company Lookup
    description: Romanian company lookup (ANAF / VIES)
  - name: Invoice Series
    description: Manage invoice numbering series
  - name: Bank Accounts
    description: Manage IBAN bank accounts
  - name: E-Factura
    description: Romanian e-invoicing status and configuration
  - name: Supplier Bills
    description: Supplier invoices imported from the ANAF SPV inbox
  - name: VAT Rates
    description: Firm-scoped custom VAT rates on top of the Romanian catalog
  - name: Currencies
    description: Currencies enabled for the firm's bill templates
  - name: Languages
    description: Languages enabled for the firm's bill templates
  - name: Conta
    description: |
      Public ANAF data for the firm's CUI: fiscal profile (TVA scope, RTVAI,
      split TVA, status, e-Factura registration, CAEN) and annual balance
      sheets (cifra de afaceri, profit, capitaluri, datorii, salariați).

      Powered by the public ANAF webservices (no OAuth required for these
      endpoints). The data is cached locally and refreshed on demand via
      the `/sync` actions.
paths:
  /expenses/ocr-extract:
    post:
      tags:
        - Expenses
      summary: Scan receipt (OCR)
      description: >
        Upload a Romanian receipt or supplier invoice photo and receive a

        structured prefill payload (supplier match, dates, totals, per-rate

        VAT breakdown, optional per-line items, payment-method hint).


        Use the response to prefill an expense form. Persist via

        POST /expenses — typically with `vat = "mix"` + `amount_vat_manual`

        for multi-rate receipts.


        See [the OCR endpoint
        guide](/api-reference/endpoints/expenses/ocr-extract).
      operationId: ocrExtractReceipt
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - image
              properties:
                image:
                  type: string
                  format: binary
                  description: Receipt image (JPEG / PNG / WebP, ≤ 10 MB).
      responses:
        '200':
          description: Extracted prefill payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OcrReceiptPayload'
        '400':
          description: Invalid file (missing, wrong type, too large)
        '502':
          description: Extraction failed (transient — safe to retry)
        '503':
          description: OCR not enabled on this deployment
components:
  schemas:
    OcrReceiptPayload:
      type: object
      description: |
        Normalized prefill returned by POST /expenses/ocr-extract. Map to
        a Create Expense body — see the OCR endpoint guide for the full
        translation table.
      properties:
        tempFileToken:
          type: string
          description: >-
            Opaque 15-min token for the staged image; pass through to
            attachments.
        supplier:
          type: object
          properties:
            id:
              type: string
              nullable: true
            name:
              type: string
            cui:
              type: string
              nullable: true
            matchedExisting:
              type: boolean
        category:
          type: object
          properties:
            id:
              type: string
              nullable: true
            name:
              type: string
              nullable: true
        date:
          type: string
          format: date
        dueDate:
          type: string
          nullable: true
        currency:
          type: string
        amount:
          type: number
          description: Net subtotal (excluding VAT).
        vatRate:
          type: number
          description: Dominant VAT rate.
        vatAmount:
          type: number
          description: Total VAT across all rate tranches.
        vatMode:
          type: string
          enum:
            - flat
            - mix
          description: |
            "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.
        total:
          type: number
          description: Gross total printed on the receipt.
        vatIncluded:
          type: boolean
        vatBreakdown:
          type: array
          nullable: true
          items:
            type: object
            properties:
              rate:
                type: number
              net:
                type: number
                nullable: true
              gross:
                type: number
                nullable: true
              vatAmount:
                type: number
        paymentMethod:
          type: string
          nullable: true
          enum:
            - cash
            - card
            - transfer
            - other
            - null
          description: |
            Inferred payment method. Map to `payment_type` on POST:
            cash→1, card→4, transfer→3, other/null→caller decides.
        docNumber:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        items:
          type: array
          description: |
            Optional. May be absent depending on document quality and
            structure. Clients that ignore this field still get correct
            totals via the aggregate fields.
          items:
            type: object
            properties:
              name:
                type: string
              quantity:
                type: number
              unit_price:
                type: number
                description: >-
                  NET (ex-VAT). Negative on discount rows (e.g. bon fiscal
                  `DISCOUNT 4
                40-A`).: null
              vat_rate:
                type: number
        warnings:
          type: array
          description: Pre-localized Romanian strings safe to surface in the form alert.
          items:
            type: string
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Use your API key (sk_live_xxx or sk_test_xxx)

````