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

# Update Expense

> Update an existing expense with new information while preserving data integrity

## Overview

The Update Expense endpoint allows you to modify an existing expense record. You can update any field including supplier, category, amounts, payment status, and other details. The system will automatically recalculate VAT and currency conversions based on your changes.

<Warning>
  Some expenses may not be modifiable if they are locked due to accounting periods or other business rules. The system will return an error if the expense cannot be modified.
</Warning>

## Path Parameters

<ParamField path="id" type="string" required>
  The CzUid of the expense to update
</ParamField>

## Request Body

All fields are optional - only include the fields you want to update. Existing values will be preserved for any fields not included in the request.

<ParamField body="supplier_id" type="string">
  Change the supplier for this expense (CzUid)
</ParamField>

<ParamField body="category_id" type="integer">
  Change the expense category
</ParamField>

<ParamField body="reference" type="string">
  Update the reference or invoice number
</ParamField>

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

<ParamField body="amount" type="number">
  Update the expense amount (will trigger VAT recalculation)
</ParamField>

<ParamField body="with_vat" type="boolean">
  Change whether the amount includes VAT
</ParamField>

<ParamField body="vat" type="integer | string">
  Update the VAT percentage. Either a numeric value (`0`, `5`, `9`, `11`, `19`, `21`) or the string `"mix"` for multi-rate receipts where the caller supplies total VAT manually via `amount_vat_manual`. See [Mix VAT mode on Create](/api-reference/endpoints/expenses/create#mix-vat-mode) for the full shape.
</ParamField>

<ParamField body="amount_vat_manual" type="number">
  Total VAT amount in receipt currency. Required (`>= 0`) when `vat = "mix"`, ignored otherwise. The server stores `amount_wvat = amount`, `amount_vat = amount_vat_manual`, `amount_total = amount + amount_vat_manual`.
</ParamField>

<ParamField body="currency" type="string">
  Change the currency (will trigger currency conversion recalculation)
</ParamField>

<ParamField body="date" type="string" format="date">
  Update the expense date (YYYY-MM-DD)
</ParamField>

<ParamField body="due_date" type="string" format="date">
  Update the payment due date (YYYY-MM-DD)
</ParamField>

<ParamField body="is_paid" type="boolean">
  Change the payment status
</ParamField>

<ParamField body="paid_date" type="string" format="date">
  Update the payment date (only relevant if is\_paid is true)
</ParamField>

<ParamField body="payment_type" type="integer">
  Update the payment type ID (1-7, only relevant if 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">
  Enable or disable currency exchange
</ParamField>

<ParamField body="currency_to" type="string">
  Update the target currency for exchange
</ParamField>

<ParamField body="items" type="array">
  Replace the expense line rows. Sending `items` **overwrites** all
  existing lines for this expense — send the full desired set, not a diff.
  Lines you omit are deleted. When `items` is present the server derives
  `amount`, `vat`, and `vat_breakdown` from the lines; the corresponding
  header-level fields on the request body are ignored.

  Omit `items` entirely to leave the existing lines untouched and update
  only the header fields you pass. To convert an itemized expense back to
  flat, send an empty `items: []` alongside new `amount` + `vat`.

  Per-item shape matches the [Create Expense](/api-reference/endpoints/expenses/create) endpoint.
  See [Expense Lines & VAT Breakdown](/concepts/expense-lines) for rounding rules.
</ParamField>

<ParamField body="vat_breakdown" type="array">
  Update the per-rate VAT breakdown for multi-VAT receipts on the flat or
  mix paths. Send an array of `{ rate, net, vat_amount, gross }` entries
  (two or more). On the mix path, the breakdown is also used to synthesize
  one persisted line per rate group. Ignored when `items` is supplied.
</ParamField>

<ParamField body="currency_rate" type="number">
  Update the exchange rate
</ParamField>

## Response

Returns the updated expense object with all recalculated fields.

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

  <Expandable title="Updated Expense Properties">
    <ResponseField name="id" type="string">Expense CzUid (unchanged)</ResponseField>
    <ResponseField name="reference" type="string">Updated reference</ResponseField>
    <ResponseField name="description" type="string">Updated description</ResponseField>
    <ResponseField name="amount" type="object">Recalculated amounts in expense currency</ResponseField>
    <ResponseField name="account_amount" type="object">Recalculated amounts in base currency</ResponseField>
    <ResponseField name="vat_percent" type="integer">Updated VAT percentage</ResponseField>
    <ResponseField name="with_vat" type="boolean">Updated VAT inclusion flag</ResponseField>
    <ResponseField name="date" type="string">Updated expense date</ResponseField>
    <ResponseField name="due_date" type="string">Updated due date</ResponseField>
    <ResponseField name="is_paid" type="boolean">Updated payment status</ResponseField>
    <ResponseField name="paid_date" type="string">Updated payment date</ResponseField>
    <ResponseField name="payment_type" type="object">Updated payment type info</ResponseField>
    <ResponseField name="status" type="string">Recalculated expense status</ResponseField>
    <ResponseField name="supplier_id" type="string">Updated supplier CzUid</ResponseField>
    <ResponseField name="category_id" type="integer">Updated category ID</ResponseField>
    <ResponseField name="user_id" type="integer">Original user ID (unchanged)</ResponseField>
    <ResponseField name="currency_exchange" type="object">Updated currency exchange info</ResponseField>
    <ResponseField name="created_at" type="string">Original creation time (unchanged)</ResponseField>
    <ResponseField name="updated_at" type="string">New modification timestamp</ResponseField>
  </Expandable>
</ResponseField>

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

## Automatic Recalculations

When you update certain fields, the system automatically recalculates related values:

### VAT Recalculation

* **Amount changes**: VAT amounts are recalculated based on the new amount and VAT percentage
* **VAT percentage changes**: All VAT-related amounts are recalculated
* **with\_vat changes**: The relationship between total and VAT-exclusive amounts is recalculated

### Currency Conversion

* **Currency changes**: Account amounts are recalculated using current exchange rates
* **Exchange rate changes**: Account amounts are recalculated with the new rate

### Status Updates

* **Payment status**: The overall expense status is updated based on payment and due date information

## Validation Rules

* **Modifiability**: The expense must not be locked by accounting rules
* **Supplier**: If changed, must exist and be owned by your firm
* **Category**: If changed, must be a valid expense category
* **Amount**: If changed, must be a positive number
* **Dates**: Must be in YYYY-MM-DD format
* **Payment consistency**: If marking as paid, payment\_type is required

<RequestExample>
  ```bash cURL theme={null}
  curl -X PUT "https://api.contazen.ro/v1/expenses/exp_abc123" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "description": "Updated: Office supplies and equipment",
      "amount": 285.00,
      "is_paid": true,
      "paid_date": "2024-01-20",
      "payment_type": 2
    }'
  ```

  ```javascript JavaScript theme={null}
  const expenseId = 'exp_abc123';
  const updateData = {
    description: "Updated: Office supplies and equipment",
    amount: 285.00,
    is_paid: true,
    paid_date: "2024-01-20",
    payment_type: 2
  };

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

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

  ```php PHP theme={null}
  $expenseId = 'exp_abc123';
  $updateData = [
      'description' => 'Updated: Office supplies and equipment',
      'amount' => 285.00,
      'is_paid' => true,
      'paid_date' => '2024-01-20',
      'payment_type' => 2
  ];

  $curl = curl_init();

  curl_setopt_array($curl, [
      CURLOPT_URL => "https://api.contazen.ro/v1/expenses/{$expenseId}",
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_CUSTOMREQUEST => 'PUT',
      CURLOPT_POSTFIELDS => json_encode($updateData),
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer YOUR_API_KEY',
          'Content-Type: application/json'
      ]
  ]);

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

  echo 'Updated expense status: ' . $data['expense']['status'];
  ```
</RequestExample>

<ResponseExample>
  ```json 200 - Success theme={null}
  {
    "success": true,
    "expense": {
      "id": "exp_abc123",
      "reference": "INV-2024-001",
      "description": "Updated: Office supplies and equipment",
      "amount": {
        "total": "285.00",
        "without_vat": "239.50",
        "vat": "45.50",
        "currency": "RON"
      },
      "account_amount": {
        "total": "285.00",
        "without_vat": "239.50",
        "vat": "45.50",
        "currency": "RON"
      },
      "vat_percent": 19,
      "with_vat": true,
      "date": "2024-01-15",
      "due_date": "2024-02-15",
      "paid_date": "2024-01-20",
      "is_paid": true,
      "payment_type": {
        "id": 2,
        "name": "Bank transfer"
      },
      "status": "paid",
      "supplier_id": "sup_xyz789",
      "category_id": 1,
      "user_id": 1,
      "created_at": "2024-01-15 10:30:00",
      "updated_at": "2024-01-20 14:45:00"
    },
    "message": "Expense updated successfully"
  }
  ```

  ```json 403 - Cannot Modify theme={null}
  {
    "success": false,
    "error": {
      "message": "Expense cannot be modified due to accounting restrictions",
      "type": "invalid_request_error",
      "code": "expense_locked"
    }
  }
  ```

  ```json 400 - Validation Error theme={null}
  {
    "success": false,
    "error": {
      "message": "Validation failed",
      "type": "invalid_request_error",
      "code": "validation_error",
      "fields": {
        "amount": "Amount must be greater than 0",
        "payment_type": "Payment type is required when expense is marked as paid"
      }
    }
  }
  ```

  ```json 404 - Not Found theme={null}
  {
    "success": false,
    "error": {
      "message": "Expense not found",
      "type": "invalid_request_error",
      "code": "resource_missing",
      "param": "id"
    }
  }
  ```
</ResponseExample>


## OpenAPI

````yaml PUT /expenses/{id}
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/{id}:
    put:
      tags:
        - Expenses
      summary: Update an expense
      description: Updates an existing expense
      operationId: updateExpense
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Expense CzUid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExpenseUpdateRequest'
      responses:
        '200':
          description: Expense updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Expense'
                  meta:
                    $ref: '#/components/schemas/ResponseMeta'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '404':
          $ref: '#/components/responses/NotFoundError'
components:
  schemas:
    ExpenseUpdateRequest:
      type: object
      description: |
        All fields optional. Sending `items` REPLACES all existing lines —
        send the full desired set, not a diff. Omit `items` to leave lines
        untouched. To convert an itemized expense back to flat, send
        `items: []` alongside new `amount` + `vat`.
      properties:
        supplier_id:
          type: string
        category_id:
          type: integer
          nullable: true
        reference:
          type: string
        description:
          type: string
        amount:
          type: number
        with_vat:
          type: integer
          enum:
            - 0
            - 1
        vat:
          oneOf:
            - type: integer
              enum:
                - 0
                - 5
                - 9
                - 11
                - 19
                - 21
            - type: string
              enum:
                - mix
          description: |
            VAT percentage. Either a numeric value or the string "mix" for
            multi-rate receipts. When set to "mix" the request must include
            `amount_vat_manual`.
        amount_vat_manual:
          type: number
          minimum: 0
          description: |
            Total VAT amount when `vat = "mix"`. Server stores
            `amount_wvat = amount`, `amount_vat = amount_vat_manual`,
            `amount_total = amount + amount_vat_manual`.
        currency:
          type: string
        date:
          type: string
          format: date
        due_date:
          type: string
          format: date
        is_paid:
          type: integer
          enum:
            - 0
            - 1
        paid_date:
          type: string
          format: date
        payment_type:
          type: integer
          minimum: 1
          maximum: 7
        with_currency_exchange:
          type: integer
          enum:
            - 0
            - 1
        currency_to:
          type: string
        currency_rate:
          type: number
        items:
          type: array
          description: |
            Replace all line rows. When present, the server re-derives
            `amount` / `vat` / `vat_breakdown` from the lines and ignores
            the corresponding header fields on the body. Empty array
            (`[]`) clears the lines.
          items:
            $ref: '#/components/schemas/ExpenseLineInput'
        vat_breakdown:
          type: array
          description: |
            Update the per-rate breakdown for flat-path multi-VAT receipts.
            Ignored when `items` is sent.
          items:
            $ref: '#/components/schemas/ExpenseVatBreakdownEntry'
    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:
    UnauthorizedError:
      description: API key is missing or invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFoundError:
      description: The specified resource was not found
      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)

````