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

# Create Expense

> Create a new expense record with minimal required fields and smart defaults for optional fields

## Overview

The Create Expense endpoint allows you to add a new expense to your firm's records with just 4 required fields. The API provides smart defaults for all optional fields, making expense creation quick and simple.

Three save shapes are supported:

* **Flat** — send `amount` + `vat` for a single-line, single-rate expense. Simplest path; covers most utility bills and single-rate receipts.
* **Itemized** — send `items[]` when the receipt has multiple line items, each carrying its own rate. The server derives `amount` / `vat` / `vat_breakdown` from the lines. See [Expense Lines & VAT Breakdown](/concepts/expense-lines) for the full model.
* **Mix VAT (manual)** — send `vat: "mix"` + `amount` (net subtotal) + `amount_vat_manual` (total VAT in RON) when the receipt mixes multiple VAT rates and per-product detail isn't reliable (typical for noisy bon fiscal OCR). Optionally include `vat_breakdown` so per-rate aggregates persist. The server reconstructs `total = amount + amount_vat_manual` exactly — no derivation, no drift.

`amount` + `vat` are required on the flat and mix paths. On the itemized path they're ignored in favor of line-derived totals.

## Duplicate Detection

Before persisting, the API scans your firm's existing expenses for a likely duplicate (same supplier + reference, or same supplier + date + amount within a small tolerance). If a match is found, the request is rejected with **HTTP 409** and a payload describing the existing expense — no row is created.

To override and force creation anyway, send `?force=1` on the request URL.

Clients that want to surface a confirmation dialog to the user before POSTing can pre-check with [`POST /expenses/check-duplicate`](./check-duplicate) using the same supplier / reference / amount fields; it returns the same `duplicate` shape without writing anything.

## Query Parameters

<ParamField query="force" type="integer" default="0">
  Set to `1` to bypass duplicate detection and persist the expense even when a likely duplicate exists.
</ParamField>

## Request Body

### Required Fields

<ParamField body="supplier_id" type="string" required>
  The CzUid of the supplier for this expense. Alternative: omit and send `supplier_data` to auto-create.
</ParamField>

<ParamField body="date" type="string" required format="date">
  Expense date in YYYY-MM-DD format
</ParamField>

<ParamField body="amount" type="number">
  The expense amount (positive number). **Required** on the flat path; ignored when `items` is sent.
</ParamField>

<ParamField body="vat" type="integer | string">
  Either a numeric VAT percentage (`0`, `5`, `9`, `11`, `19`, `21`) **or** the literal string `"mix"` for a multi-rate receipt where the caller supplies the total VAT manually (see [Mix VAT mode](#mix-vat-mode)). **Required** on the flat and mix paths; when `items` is sent the server sets this to the dominant rate.
</ParamField>

<ParamField body="amount_vat_manual" type="number">
  Total VAT amount in receipt currency. **Required when `vat = "mix"`**, ignored otherwise. Must be `>= 0`. The server stores `amount_wvat = amount`, `amount_vat = amount_vat_manual`, `amount_total = amount_wvat + amount_vat_manual` exactly — no rounding inference.
</ParamField>

### Optional Fields with Defaults

<ParamField body="currency" type="string" default="RON">
  Currency code (e.g., RON, EUR, USD)

  * Defaults to RON if not provided
</ParamField>

<ParamField body="due_date" type="string" format="date" default="30 days from today">
  Payment due date in YYYY-MM-DD format

  * Defaults to 30 days from today if not provided
</ParamField>

<ParamField body="category_id" type="integer" default="0">
  The ID of the expense category

  * Optional, defaults to 0 (uncategorized)
</ParamField>

<ParamField body="reference" type="string">
  Reference or invoice number for the expense
</ParamField>

<ParamField body="description" type="string">
  Detailed description of the expense
</ParamField>

<ParamField body="with_vat" type="boolean" default="false">
  Whether the amount includes VAT (true) or excludes VAT (false)

  * Defaults to false if not provided
</ParamField>

<ParamField body="is_paid" type="boolean" default="false">
  Whether the expense is already paid

  * Defaults to false if not provided
</ParamField>

<ParamField body="paid_date" type="string" format="date" default="today">
  Payment date

  * Defaults to today if is\_paid is true and paid\_date not provided
  * Optional even when is\_paid is true
</ParamField>

<ParamField body="payment_type" type="integer" default="1">
  Payment type ID:

  * Defaults to 1 (Cash) if is\_paid is true and payment\_type not provided
  * Optional even when is\_paid is true
  * `1` - Cash
  * `2` - Bank transfer
  * `3` - Card
  * `4` - Check
  * `5` - Promissory note
  * `6` - Other
  * `7` - Compensation
</ParamField>

<ParamField body="with_currency_exchange" type="boolean" default="false">
  Whether this expense involves currency exchange
</ParamField>

<ParamField body="currency_to" type="string">
  Target currency for exchange (required if with\_currency\_exchange is true)
</ParamField>

<ParamField body="currency_rate" type="number">
  Exchange rate (required if with\_currency\_exchange is true)
</ParamField>

### Itemized Lines

<ParamField body="items" type="array">
  Array of line rows. When present and non-empty, the server persists real
  line records and derives `amount` / `vat` / `vat_breakdown` from them —
  the header-level `amount` and `vat` request fields are ignored.

  Each item is an object with:

  * `name` (string, required) — display name for the line
  * `quantity` (number, required, > 0) — supports fractional quantities
  * `unit_price` (number, required) — **net** unit price (excluding VAT). Negative values are accepted for discount rows (e.g. a Romanian bon fiscal `DISCOUNT 4,40-A` rebate); the sign is preserved through to the line's gross.
  * `vat_rate` (integer, required) — one of 0, 5, 9, 11, 19, 21
  * `description` (string) — optional
  * `unit_code` (string) — optional UN/ECE code (e.g. `H87`, `KGM`)
  * `category_id` (string) — optional per-line category override
  * `product_id` (string) — optional link to firm's product catalog

  See [Expense Lines & VAT Breakdown](/concepts/expense-lines) for rounding rules and multi-VAT handling.
</ParamField>

<ParamField body="vat_breakdown" type="array">
  Per-rate breakdown for multi-VAT receipts saved on the flat or mix paths
  (no `items`). Each entry: `{ rate, net, vat_amount, gross }`. The server
  stores the array as JSON on the row when two or more distinct rates are
  present. Ignored when `items` is supplied — in that case the breakdown is
  derived from the lines.

  On the **mix path**, supplying `vat_breakdown` with at least two
  rate tranches makes the server synthesize one persisted line per rate
  group (`Cheltuială (21%)`, `Cheltuială (11%)`, `Cheltuială (0%)`) so
  reports and exports keep accurate per-rate totals. Without a breakdown,
  mix mode falls back to a single line with a synthetic
  `vat_rate = (amount_vat_manual / amount) × 100`.
</ParamField>

<ParamField body="supplier_data" type="object">
  Auto-create a supplier when `supplier_id` is not supplied. Useful when
  ingesting supplier data from external sources where the firm hasn't
  billed with the supplier before.

  Shape: `{ name: string, cui?: string }`. If a supplier with the same CUI
  (or name when CUI is missing) already exists on the firm, it's reused
  instead of creating a duplicate.
</ParamField>

## Response

Returns the created expense object with all computed fields including VAT calculations and currency conversions.

<ResponseField name="expense" type="object">
  The created expense object with all details

  <Expandable title="Expense Object Properties">
    <ResponseField name="id" type="string">
      Unique CzUid identifier for the expense
    </ResponseField>

    <ResponseField name="reference" type="string">
      Reference number or invoice number
    </ResponseField>

    <ResponseField name="description" type="string">
      Expense description
    </ResponseField>

    <ResponseField name="amount" type="object">
      Original expense amounts with VAT calculations
    </ResponseField>

    <ResponseField name="account_amount" type="object">
      Accounting amounts in firm's base currency
    </ResponseField>

    <ResponseField name="vat_percent" type="integer">VAT percentage</ResponseField>
    <ResponseField name="with_vat" type="boolean">Whether amount includes VAT</ResponseField>
    <ResponseField name="date" type="string">Expense date</ResponseField>
    <ResponseField name="due_date" type="string">Payment due date</ResponseField>
    <ResponseField name="is_paid" type="boolean">Payment status</ResponseField>
    <ResponseField name="status" type="string">Expense status</ResponseField>
    <ResponseField name="supplier_id" type="string">Supplier CzUid</ResponseField>
    <ResponseField name="category_id" type="integer">Category ID</ResponseField>
    <ResponseField name="currency_exchange" type="object">Currency exchange details (if applicable)</ResponseField>
    <ResponseField name="created_at" type="string">Creation timestamp</ResponseField>
    <ResponseField name="updated_at" type="string">Last update timestamp</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="message" type="string">
  Success message confirming the expense creation
</ResponseField>

## Validation Rules

* **Supplier**: Must exist and be owned by your firm (required)
* **Amount**: Must be a positive number (required)
* **VAT**: Must be one of: 0, 5, 9, 11, 19, 21, or the string `"mix"` (required)
* **amount\_vat\_manual**: Required (>= 0) when `vat = "mix"`, ignored otherwise
* **Date**: Must be in YYYY-MM-DD format (required)
* **Category**: If provided, must be a valid expense category for your firm
* **Currency**: If provided, must be a valid 3-letter ISO code
* **Dates**: All dates must be in YYYY-MM-DD format
* **Payment Info**: paid\_date and payment\_type are optional with smart defaults

## VAT Calculation

The system automatically calculates VAT amounts based on your input:

* If `vat = "mix"`: `amount` is treated as the net subtotal, `amount_vat_manual` is the total VAT, and `total = amount + amount_vat_manual` exactly. `with_vat` is forced to `false`. See [Mix VAT mode](#mix-vat-mode) below.
* If `with_vat` is **true**: The amount includes VAT, and VAT is extracted from the total
* If `with_vat` is **false**: The amount excludes VAT, and VAT is added to get the total

## Mix VAT mode

Romanian fiscal receipts (bon fiscal) routinely mix multiple VAT rates on a single document — e.g. food items at 11% alongside non-food at 21% and packaging deposits at 0%. When per-product detail isn't reliable, mix mode lets the caller record the printed totals exactly without inferring a single rate.

**Request shape:**

```json theme={null}
{
  "supplier_id": "Q9hjAB",
  "date": "2026-04-25",
  "amount": 147.53,
  "vat": "mix",
  "amount_vat_manual": 24.15,
  "currency": "RON",
  "vat_breakdown": [
    { "rate": 21, "net": 80.29, "vat_amount": 16.86, "gross": 97.15 },
    { "rate": 11, "net": 66.27, "vat_amount":  7.29, "gross": 73.56 },
    { "rate":  0, "net":  0.97, "vat_amount":  0.00, "gross":  0.97 }
  ]
}
```

**What gets stored:**

* `amount_wvat = 147.53`, `amount_vat = 24.15`, `amount_total = 171.68` (exact, no derivation)
* `vat_breakdown` JSON when two or more rate entries are supplied; per-rate aggregates persist for reports. Presence of this JSON with ≥2 entries is what marks the row as multi-rate — the legacy `vat_percent` field is filled with the dominant rate for back-compat with single-rate consumers.
* Without `vat_breakdown`, the receipt is stored as a single-rate row with `vat_percent = (amount_vat_manual / amount) × 100`

**When to use mix vs itemized:**

* Use **itemized** (`items[]`) when you have reliable per-product names, quantities, unit prices, and rates.
* Use **mix** (`vat = "mix"`) when only the printed totals are reliable.

## Currency Exchange

If `with_currency_exchange` is enabled:

* Original amounts are kept in the expense currency
* Account amounts are calculated using the provided exchange rate
* Exchange details are stored for audit purposes

<RequestExample>
  ```bash Minimal (Required Fields Only) theme={null}
  curl -X POST "https://api.contazen.ro/v1/expenses" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "supplier_id": "sup_xyz789",
      "amount": 100.00,
      "vat": 21,
      "date": "2024-01-15"
    }'
  ```

  ```bash With Optional Fields theme={null}
  curl -X POST "https://api.contazen.ro/v1/expenses" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "supplier_id": "sup_xyz789",
      "amount": 238.00,
      "vat": 21,
      "date": "2024-01-15",
      "reference": "INV-2024-001",
      "description": "Office supplies purchase",
      "category_id": 1,
      "with_vat": true,
      "currency": "EUR",
      "due_date": "2024-02-15",
      "is_paid": true
    }'
  ```

  ```bash Itemized (multi-VAT receipt) theme={null}
  curl -X POST "https://api.contazen.ro/v1/expenses" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "supplier_id": "sup_xyz789",
      "date": "2026-02-19",
      "currency": "RON",
      "items": [
        { "name": "Meniul zilei",    "quantity": 3, "unit_price": 28.74, "vat_rate": 11 },
        { "name": "Caserolă meniu",  "quantity": 6, "unit_price": 1.24,  "vat_rate": 21 }
      ]
    }'
  ```

  ```javascript JavaScript (Minimal) theme={null}
  // Minimal required fields only
  const minimalExpense = {
    supplier_id: "sup_xyz789",
    amount: 100.00,
    vat: 21,
    date: "2024-01-15"
  };

  const response = await fetch('https://api.contazen.ro/v1/expenses', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(minimalExpense)
  });

  const data = await response.json();
  console.log('Created expense:', data.expense);
  ```

  ```javascript JavaScript (Full) theme={null}
  // With optional fields
  const fullExpense = {
    supplier_id: "sup_xyz789",
    amount: 238.00,
    vat: 21,
    date: "2024-01-15",
    reference: "INV-2024-001",
    description: "Office supplies purchase",
    category_id: 1,
    with_vat: true,
    currency: "EUR",
    due_date: "2024-02-15",
    is_paid: true,
    paid_date: "2024-01-16",
    payment_type: 2
  };

  const response = await fetch('https://api.contazen.ro/v1/expenses', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(fullExpense)
  });

  const data = await response.json();
  console.log('Created expense:', data.expense);
  ```

  ```php PHP (Minimal) theme={null}
  // Minimal required fields only
  $minimalExpense = [
      'supplier_id' => 'sup_xyz789',
      'amount' => 100.00,
      'vat' => 21,
      'date' => '2024-01-15'
  ];

  $curl = curl_init();

  curl_setopt_array($curl, [
      CURLOPT_URL => 'https://api.contazen.ro/v1/expenses',
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_POSTFIELDS => json_encode($minimalExpense),
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer YOUR_API_KEY',
          'Content-Type: application/json'
      ]
  ]);

  $response = curl_exec($curl);
  $data = json_decode($response, true);
  curl_close($curl);

  echo 'Created expense ID: ' . $data['expense']['id'];
  ```
</RequestExample>

<ResponseExample>
  ```json 201 - Success (Minimal) theme={null}
  {
    "success": true,
    "expense": {
      "id": "exp_abc123",
      "reference": null,
      "description": null,
      "amount": {
        "total": "121.00",
        "without_vat": "100.00",
        "vat": "21.00",
        "currency": "RON"
      },
      "account_amount": {
        "total": "121.00",
        "without_vat": "100.00",
        "vat": "21.00",
        "currency": "RON"
      },
      "vat_percent": 21,
      "with_vat": false,
      "date": "2024-01-15",
      "due_date": "2024-02-14",
      "paid_date": null,
      "is_paid": false,
      "payment_type": null,
      "status": "unpaid",
      "supplier_id": "sup_xyz789",
      "category_id": 0,
      "user_id": 1,
      "created_at": "2024-01-15 10:30:00",
      "updated_at": "2024-01-15 10:30:00"
    },
    "message": "Expense created successfully"
  }
  ```

  ```json 400 - Validation Error theme={null}
  {
    "success": false,
    "error": {
      "message": "Validation failed",
      "type": "invalid_request_error",
      "code": "validation_error",
      "fields": {
        "supplier_id": "Supplier is required",
        "amount": "Amount must be greater than 0"
      }
    }
  }
  ```

  ```json 409 - Duplicate Expense theme={null}
  {
    "success": false,
    "error": {
      "message": "Duplicate expense detected",
      "type": "invalid_request_error",
      "code": "duplicate_expense",
      "duplicate": {
        "id": "d8kZ3Qa",
        "reference": "FX 26101044850",
        "date": "2026-02-24",
        "amount": 584.39,
        "currency": "RON",
        "match_type": "supplier_reference"
      }
    }
  }
  ```
</ResponseExample>


## OpenAPI

````yaml POST /expenses
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:
    post:
      tags:
        - Expenses
      summary: Create an expense
      description: >
        Creates a new expense record with minimal required fields.


        ## Required Fields (only 4)

        - `supplier_id`: Supplier CzUid (required)

        - `amount`: Amount (required, positive number) 

        - `vat`: VAT percentage (required - accepts 0, 5, 9, 11, 19, 21)

        - `date`: Date in Y-m-d format (required)


        ## Optional Fields with Smart Defaults

        - `currency`: Currency code (defaults to RON)

        - `due_date`: Due date (defaults to 30 days from today)

        - `paid_date`: Payment date (defaults to today if is_paid=true)

        - `payment_type`: Payment type ID 1-7 (defaults to 1 if is_paid=true)

        - `category_id`: Category ID (defaults to 0)

        - `reference`: Reference number

        - `description`: Description

        - `with_vat`: Whether amount includes VAT (defaults to false)

        - `is_paid`: Whether expense is paid (defaults to false)

        - `with_currency_exchange`: Boolean for currency exchange

        - `currency_to`: Target currency (if with_currency_exchange)

        - `currency_rate`: Exchange rate (if with_currency_exchange)


        ## VAT Rates

        Supported VAT rates: 0%, 5%, 9%, 11%, 19%, 21% (21% is standard in
        Romania)
      operationId: createExpense
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExpenseCreateRequest'
      responses:
        '201':
          description: Expense created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Expense'
                  meta:
                    $ref: '#/components/schemas/ResponseMeta'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
components:
  schemas:
    ExpenseCreateRequest:
      type: object
      required:
        - date
      description: |
        Either `supplier_id` or `supplier_data` must be provided. Either
        (`amount` + `vat`) or `items[]` must be provided — when `items` is
        sent the header `amount`/`vat` request fields are ignored and the
        server derives them from the lines.
      properties:
        supplier_id:
          type: string
          description: Supplier CzUid. Omit when sending `supplier_data` to auto-create.
        supplier_data:
          type: object
          description: |
            Auto-create a supplier when `supplier_id` is missing. If a
            supplier with the same CUI (or name when CUI is missing) already
            exists on the firm, it is reused instead of creating a duplicate.
          required:
            - name
          properties:
            name:
              type: string
            cui:
              type: string
              nullable: true
        amount:
          type: number
          minimum: 0.01
          description: >-
            Amount (positive number). Required on the flat path; ignored when
            `items` is sent.
        vat:
          oneOf:
            - type: integer
              enum:
                - 0
                - 5
                - 9
                - 11
                - 19
                - 21
            - type: string
              enum:
                - mix
          description: |
            VAT percentage. Either a numeric percent (0/5/9/11/19/21) or the
            string "mix" for multi-rate receipts where the caller supplies
            the total VAT manually via `amount_vat_manual`. Required on the
            flat and mix paths; ignored when `items` is sent.
        amount_vat_manual:
          type: number
          minimum: 0
          description: >
            Total VAT amount in receipt currency. **Required when `vat =
            "mix"`**,

            ignored otherwise. Server stores `amount_wvat = amount`,

            `amount_vat = amount_vat_manual`, `amount_total = amount +
            amount_vat_manual`

            exactly — no rounding inference.
        date:
          type: string
          format: date
          description: Expense date (required, Y-m-d format)
        items:
          type: array
          description: |
            Line rows. When present and non-empty, the server persists real
            line records and derives `amount` / `vat` / `vat_breakdown` from
            them. See the Expense Lines concept for rounding rules.
          items:
            $ref: '#/components/schemas/ExpenseLineInput'
        vat_breakdown:
          type: array
          description: |
            Per-rate VAT tranches for multi-VAT receipts saved on the flat
            or mix paths. Supply two or more entries. On the mix path the
            server synthesizes one persisted line per rate group from the
            breakdown so reports keep accurate per-rate totals. Ignored when
            `items` is sent.
          items:
            $ref: '#/components/schemas/ExpenseVatBreakdownEntry'
        currency:
          type: string
          default: RON
          description: Currency code (optional, defaults to RON)
        due_date:
          type: string
          format: date
          description: Due date (optional, defaults to 30 days from today)
        paid_date:
          type: string
          format: date
          description: Payment date (optional, defaults to today if is_paid=true)
        payment_type:
          type: integer
          minimum: 1
          maximum: 7
          description: |
            Payment type ID 1-7 (optional, defaults to 1 if is_paid=true):
            1 = Cash
            2 = Bank transfer
            3 = Card
            4 = Check
            5 = Promissory note
            6 = Other
            7 = Compensation
        category_id:
          type: integer
          description: Category ID (optional, defaults to 0)
          nullable: true
        reference:
          type: string
          description: Reference number (optional)
        description:
          type: string
          description: Description (optional)
        with_vat:
          type: boolean
          default: false
          description: Whether amount includes VAT (optional, defaults to false)
        is_paid:
          type: boolean
          default: false
          description: Whether expense is paid (optional, defaults to false)
        with_currency_exchange:
          type: boolean
          default: false
          description: Whether to apply currency exchange (optional)
        currency_to:
          type: string
          description: Target currency (if with_currency_exchange)
        currency_rate:
          type: number
          description: Exchange rate (if with_currency_exchange)
    Expense:
      type: object
      required:
        - id
        - reference
        - amount
        - date
        - due_date
        - status
      properties:
        id:
          type: string
          description: Unique identifier (CzUid)
          example: exp_1a2b3c4d5e
        reference:
          type: string
          description: Invoice/document reference number
        description:
          type: string
        amount:
          type: object
          properties:
            total:
              type: number
              example: 500
            without_vat:
              type: number
            vat:
              type: number
            currency:
              type: string
        account_amount:
          type: object
          description: Amounts in account currency (RON)
          properties:
            total:
              type: number
            without_vat:
              type: number
            vat:
              type: number
            currency:
              type: string
              default: RON
        vat_percent:
          type: integer
          enum:
            - 0
            - 5
            - 9
            - 11
            - 19
            - 21
        with_vat:
          type: boolean
          description: Whether amount includes VAT
        date:
          type: string
          format: date
        due_date:
          type: string
          format: date
        paid_date:
          type: string
          format: date
          nullable: true
        is_paid:
          type: boolean
        payment_type:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            name:
              type: string
        status:
          type: string
          enum:
            - paid
            - unpaid
            - overdue
            - registered
        supplier_id:
          type: string
        supplier:
          $ref: '#/components/schemas/Supplier'
        category_id:
          type: integer
        category:
          $ref: '#/components/schemas/ExpenseCategory'
        user_id:
          type: integer
        user:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
        currency_exchange:
          type: object
          nullable: true
          properties:
            from:
              type: string
            to:
              type: string
            rate:
              type: number
        attachment:
          type: object
          nullable: true
          description: Single attachment (stored in the 'image' field)
          properties:
            url:
              type: string
            filename:
              type: string
            type:
              type: string
              enum:
                - pdf
                - image
            size:
              type: integer
        efactura:
          type: object
          nullable: true
          description: |
            Populated when the expense was imported from the ANAF e-Factura SPV
            inbox. `null` for manually created expenses.
          properties:
            supplier_invoice_id:
              type: string
            invoice_number:
              type: string
              nullable: true
            anaf_message_id:
              type: string
              nullable: true
            xml_available:
              type: boolean
            pdf_available:
              type: boolean
            pdf_url:
              type: string
              nullable: true
              description: >-
                Bearer-authenticated URL that streams the e-Factura PDF;
                regenerated on demand when missing
        items:
          type: array
          description: |
            Line rows for the expense. Always at least one entry — legacy flat
            expenses get a single synthesized line built from the header totals
            so API consumers can render a uniform table. Send `items` on
            create/update to persist real multi-line rows.
          items:
            $ref: '#/components/schemas/ExpenseLine'
        vat_breakdown:
          type: array
          nullable: true
          description: |
            Per-rate VAT tranches when the receipt mixes two or more rates.
            `null` for single-rate expenses.
          items:
            $ref: '#/components/schemas/ExpenseVatBreakdownEntry'
        category_source:
          type: string
          nullable: true
          enum:
            - ai
            - supplier
            - manual
          description: |
            How the category was assigned. `null` on uncategorized expenses
            or legacy rows where provenance wasn't recorded.
        category_confidence:
          type: number
          nullable: true
          minimum: 0
          maximum: 1
          description: Auto-classifier confidence when category_source is 'ai'.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ResponseMeta:
      type: object
      properties:
        version:
          type: string
          default: v1
        request_id:
          type: string
          format: uuid
          description: Unique request identifier for debugging
        response_time:
          type: string
          example: 23.45ms
    ExpenseLineInput:
      type: object
      required:
        - name
        - quantity
        - unit_price
        - vat_rate
      properties:
        name:
          type: string
        quantity:
          type: number
          minimum: 0.0001
        unit_price:
          type: number
          description: >-
            Net unit price (excluding VAT). Negative values are accepted for
            discount rows (e.g. bon fiscal `DISCOUNT 4,40-A`).
        vat_rate:
          type: integer
          enum:
            - 0
            - 5
            - 9
            - 11
            - 19
            - 21
        description:
          type: string
          nullable: true
        unit_code:
          type: string
          nullable: true
        category_id:
          type: string
          nullable: true
        product_id:
          type: string
          nullable: true
    ExpenseVatBreakdownEntry:
      type: object
      required:
        - rate
        - vat_amount
      properties:
        rate:
          type: integer
          enum:
            - 0
            - 5
            - 9
            - 11
            - 19
            - 21
        net:
          type: number
          nullable: true
          description: Sum of line nets at this rate (2dp).
        vat_amount:
          type: number
          description: Sum of line VATs at this rate (2dp).
        gross:
          type: number
          nullable: true
          description: Sum of line gross values at this rate (2dp).
    Supplier:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          description: Unique identifier (CzUid)
          example: sup_1a2b3c4d5e
        name:
          type: string
        cui:
          type: string
          description: Tax identification number
        rc:
          type: string
          description: Company registration number
        cnp:
          type: string
          description: Personal identification number
        address:
          type: string
        city:
          type: string
        county:
          type: string
        country:
          type: string
        postal_code:
          type: string
        phone:
          type: string
        email:
          type: string
        iban:
          type: string
        bank:
          type: string
        contact_person:
          type: string
        contact_phone:
          type: string
        contact_email:
          type: string
        is_vat_payer:
          type: boolean
        statistics:
          type: object
          properties:
            expense_count:
              type: integer
            total_amount:
              type: number
            paid_amount:
              type: number
            unpaid_amount:
              type: number
        recent_expenses:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              reference:
                type: string
              amount:
                type: number
              currency:
                type: string
              date:
                type: string
                format: date
              is_paid:
                type: boolean
        metadata:
          type: object
          description: |
            Lightweight expense aggregates. Returned by `GET /suppliers` only
            when `with_totals=1` is supplied. Cheaper than `expand=statistics`
            since the totals come from a single inline subquery rather than
            a per-row N+1 follow-up. Amounts are expressed in the firm's
            accounting currency.
          properties:
            expense_count:
              type: integer
              description: Number of non-deleted expenses for this supplier
            total_expenses:
              type: number
              description: Sum of `account_amount_total` across all matching expenses
            unpaid_total:
              type: number
              description: Sum of `account_amount_total` across unpaid matching expenses
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ExpenseCategory:
      type: object
      required:
        - id
        - name
        - type
      properties:
        id:
          type: integer
        name:
          type: string
        parent_id:
          type: integer
          nullable: true
        is_default:
          type: boolean
          description: Whether this is a system default category
        is_visible:
          type: boolean
        type:
          type: string
          enum:
            - system
            - custom
        expense_count:
          type: integer
          description: Number of expenses in this category
        children:
          type: array
          items:
            $ref: '#/components/schemas/ExpenseCategory'
    ExpenseLine:
      type: object
      required:
        - id
        - line_index
        - name
        - quantity
        - unit_price
        - vat_rate
        - amount_wvat
        - amount_vat
        - amount
      properties:
        id:
          type: integer
          description: Line row ID. `0` for synthesized legacy lines.
        line_index:
          type: integer
          description: Zero-based position within the expense.
        name:
          type: string
        description:
          type: string
          nullable: true
        quantity:
          type: number
          description: Supports fractional quantities (e.g. 0.5 kg).
        unit_code:
          type: string
          nullable: true
          description: Optional UN/ECE unit code (e.g. H87, KGM).
        unit_price:
          type: number
          description: >-
            Net unit price (excluding VAT). Negative values represent discount
            rows (e.g. bon fiscal `DISCOUNT 4,40-A`); the sign is preserved
            through net/VAT/gross.
        vat_rate:
          type: integer
          enum:
            - 0
            - 5
            - 9
            - 11
            - 19
            - 21
        amount_wvat:
          type: number
          description: Line net, 2dp, `round(quantity * unit_price, 2)`.
        amount_vat:
          type: number
          description: Line VAT, 2dp, `round(amount_wvat * vat_rate / 100, 2)`.
        amount:
          type: number
          description: Line gross, 2dp, `amount_wvat + amount_vat`.
        category_id:
          type: string
          nullable: true
          description: >-
            Optional per-line category override (falls back to expense-level
            category).
        product_id:
          type: string
          nullable: true
          description: Optional link to a product in the firm's catalog.
    ErrorResponse:
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          default: false
        error:
          type: object
          required:
            - message
            - type
            - code
          properties:
            message:
              type: string
              description: Human-readable error message
            type:
              type: string
              enum:
                - api_error
                - authentication_error
                - invalid_request_error
                - rate_limit_error
                - permission_error
                - validation_error
            code:
              type: string
              description: Machine-readable error code
            param:
              type: string
              description: The parameter that caused the error
            doc_url:
              type: string
              format: uri
              description: URL to relevant documentation
        meta:
          $ref: '#/components/schemas/ResponseMeta'
  responses:
    ValidationError:
      description: Request validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    UnauthorizedError:
      description: API key is missing or invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Use your API key (sk_live_xxx or sk_test_xxx)

````