Skip to main content
POST
/
expenses
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"
  }'
{
  "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"
}

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.

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 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 using the same supplier / reference / amount fields; it returns the same duplicate shape without writing anything.

Query Parameters

force
integer
default:"0"
Set to 1 to bypass duplicate detection and persist the expense even when a likely duplicate exists.

Request Body

Required Fields

supplier_id
string
required
The CzUid of the supplier for this expense. Alternative: omit and send supplier_data to auto-create.
date
string
required
Expense date in YYYY-MM-DD format
amount
number
The expense amount (positive number). Required on the flat path; ignored when items is sent.
vat
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). Required on the flat and mix paths; when items is sent the server sets this to the dominant rate.
amount_vat_manual
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.

Optional Fields with Defaults

currency
string
default:"RON"
Currency code (e.g., RON, EUR, USD)
  • Defaults to RON if not provided
due_date
string
default:"30 days from today"
Payment due date in YYYY-MM-DD format
  • Defaults to 30 days from today if not provided
category_id
integer
default:"0"
The ID of the expense category
  • Optional, defaults to 0 (uncategorized)
reference
string
Reference or invoice number for the expense
description
string
Detailed description of the expense
with_vat
boolean
default:"false"
Whether the amount includes VAT (true) or excludes VAT (false)
  • Defaults to false if not provided
is_paid
boolean
default:"false"
Whether the expense is already paid
  • Defaults to false if not provided
paid_date
string
default:"today"
Payment date
  • Defaults to today if is_paid is true and paid_date not provided
  • Optional even when is_paid is true
payment_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
with_currency_exchange
boolean
default:"false"
Whether this expense involves currency exchange
currency_to
string
Target currency for exchange (required if with_currency_exchange is true)
currency_rate
number
Exchange rate (required if with_currency_exchange is true)

Itemized Lines

items
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 for rounding rules and multi-VAT handling.
vat_breakdown
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.
supplier_data
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.

Response

Returns the created expense object with all computed fields including VAT calculations and currency conversions.
expense
object
The created expense object with all details
message
string
Success message confirming the expense creation

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 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:
{
  "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
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"
  }'
{
  "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"
}

Authorizations

Authorization
string
header
required

Use your API key (sk_live_xxx or sk_test_xxx)

Body

application/json

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.

date
string<date>
required

Expense date (required, Y-m-d format)

supplier_id
string

Supplier CzUid. Omit when sending supplier_data to auto-create.

supplier_data
object

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.

amount
number

Amount (positive number). Required on the flat path; ignored when items is sent.

Required range: x >= 0.01
vat

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.

Available options:
0,
5,
9,
11,
19,
21
amount_vat_manual
number

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.

Required range: x >= 0
items
object[]

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.

vat_breakdown
object[]

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.

currency
string
default:RON

Currency code (optional, defaults to RON)

due_date
string<date>

Due date (optional, defaults to 30 days from today)

paid_date
string<date>

Payment date (optional, defaults to today if is_paid=true)

payment_type
integer

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

Required range: 1 <= x <= 7
category_id
integer | null

Category ID (optional, defaults to 0)

reference
string

Reference number (optional)

description
string

Description (optional)

with_vat
boolean
default:false

Whether amount includes VAT (optional, defaults to false)

is_paid
boolean
default:false

Whether expense is paid (optional, defaults to false)

with_currency_exchange
boolean
default:false

Whether to apply currency exchange (optional)

currency_to
string

Target currency (if with_currency_exchange)

currency_rate
number

Exchange rate (if with_currency_exchange)

Response

Expense created successfully

success
boolean
data
object
meta
object