Create invoice

Creates a new draft invoice for the specified company.

POST /api/v1/invoices

Headers

NameTypeRequiredDescription
AuthorizationstringYesBearer token for authentication
X-CompanystringYesCompany UUID to scope the request
Content-TypestringYesMust be application/json

Request body

NameTypeRequiredDescription
clientIdstringNoClient UUID. Either clientId or receiverName should be provided.
receiverNamestringNoReceiver name (when no client entity exists). Either clientId or receiverName should be provided.
receiverCifstringNoReceiver tax ID / CIF (used with receiverName)
documentSeriesIdstringNoInvoice series UUID (uses default if not provided)
documentTypestringNoDocument type (e.g., invoice, credit_note)
parentDocumentIdstringNoParent document UUID (for refunds/credit notes)
issueDatestringYesInvoice issue date (ISO 8601: YYYY-MM-DD)
dueDatestringNoPayment due date (ISO 8601: YYYY-MM-DD)
currencystringNoISO 4217 currency code (default: RON)
exchangeRatenumberNoExchange rate (default: 1.0)
invoiceTypeCodestringNoUBL invoice type code (default: 380)
notesstringNoPublic notes visible to client
paymentTermsstringNoPayment terms description
paymentMethodstringNoPayment method: bank_transfer (default), cash, card, cheque, other
deliveryLocationstringNoDelivery address
projectReferencestringNoProject reference number
orderNumberstringNoPurchase order number
contractNumberstringNoContract reference number
issuerNamestringNoName of person issuing the invoice
issuerIdstringNoIssuer ID number
mentionsstringNoAdditional legal mentions
internalNotestringNoInternal note (not visible to client)
salesAgentstringNoSales agent name
deputyNamestringNoDeputy/representative name
deputyIdentityCardstringNoDeputy ID card number
deputyAutostringNoDeputy vehicle registration
languagestringNoDocument language for PDF generation: ro, en, de, fr (default: ro)
tvaLaIncasarebooleanNoVAT on collection / TVA la încasare (default: false)
platitorTvabooleanNoWhether sender is VAT payer (default: false)
plataOnlinebooleanNoEnable online payment via Stripe (default: from company Stripe Connect settings)
showClientBalancebooleanNoShow client balance on invoice (default: false)
clientBalanceExistingstringNoExisting client balance amount
clientBalanceOverduestringNoOverdue client balance amount
collectobjectNoCreate immediate payment on the invoice (see below)
autoApplyVatRulesbooleanNoAuto-apply EU VAT rules: reverse charge (0% VAT) for VIES-valid EU clients, OSS destination country VAT rate for non-VIES EU clients (default: false)
vatIncludedbooleanNoWhen used with autoApplyVatRules, sets whether unit prices include VAT on all lines. This ensures correct totals after VAT rules change rates (e.g., reverse charge sets VAT to 0%). Without this, use per-line vatIncluded instead.
idempotencyKeystringNoStable key that lets you safely retry a failed request — see Idempotency
ublExtensionsobjectNoUBL extension fields for advanced e-Factura compliance (see below)
linesarrayYesArray of invoice line items

Collect object

When provided, creates an immediate payment record on the invoice.

NameTypeRequiredDescription
valuenumberNoPayment amount (defaults to invoice total)
typestringNoPayment method: bank_transfer, cash, card, etc. (default: bank_transfer)
issueDatestringNoPayment date (ISO 8601: YYYY-MM-DD)
documentNumberstringNoPayment reference/document number
mentionsstringNoPayment notes

Invoice line object

NameTypeRequiredDescription
descriptionstringYesLine item description
quantitynumberYesQuantity (must be positive, or non-zero for refunds)
unitPricenumberYesUnit price (must be non-negative)
vatRatenumberNoVAT rate percentage (default: 21.00)
vatCategoryCodestringNoUBL VAT category code (default: S). Usually not needed — auto-determined from vatRate: 0% rate auto-corrects to Z, and zero-rate codes with rate > 0 auto-correct to S. Only set explicitly for special categories like AE (reverse charge), E (exempt), K (intra-community), G (export).
vatRateIdstringNoVAT rate UUID (uses default if not provided)
unitOfMeasurestringNoUnit of measure (e.g., "hours", "pcs", "kg")
productIdstringNoProduct UUID (optional reference)
discountnumberNoFixed discount amount
discountPercentnumberNoDiscount percentage
vatIncludedbooleanNoWhether price includes VAT (default: false)
productCodestringNoProduct code for reference
ublExtensionsobjectNoLine-level UBL extensions (see below)

e-Factura BT fields

These optional fields are used for advanced e-Factura (UBL) compliance:

NameTypeDescription
taxPointDatestringTax point date (ISO 8601: YYYY-MM-DD)
taxPointDateCodestringTax point date code
buyerReferencestringBuyer reference
receivingAdviceReferencestringReceiving advice reference
despatchAdviceReferencestringDespatch advice reference
tenderOrLotReferencestringTender or lot reference
invoicedObjectIdentifierstringInvoiced object identifier
buyerAccountingReferencestringBuyer accounting reference
businessProcessTypestringBusiness process type
payeeNamestringPayee name (if different from seller)
payeeIdentifierstringPayee identifier
payeeLegalRegistrationIdentifierstringPayee legal registration identifier

UBL extensions (document-level)

The ublExtensions object supports UBL XML elements that don't have dedicated invoice fields. All sub-fields are optional. Unknown keys are silently stripped.

NameTypeDescription
invoicePeriodobjectBilling period: startDate (YYYY-MM-DD), endDate (YYYY-MM-DD), descriptionCode (e.g., "35")
deliveryobjectDelivery info: actualDeliveryDate (YYYY-MM-DD), deliveryAddress object with streetName, cityName, countrySubentity, countryCode
allowanceChargesarrayDocument-level allowances/charges (max 20). Each: chargeIndicator (bool, false=discount), amount (numeric string), taxCategoryCode (S/Z/E/AE/K/G/O), taxRate (numeric string). Optional: reasonCode, reason, baseAmount, multiplierFactorNumeric
prepaidAmountstringPrepaid amount (numeric string >= 0). Reduces PayableAmount in UBL XML
additionalDocumentReferencesarrayAdditional references (max 10). Each: id (required, max 200), optional documentTypeCode, documentDescription
ℹ️

Document-level allowanceCharges adjust TaxTotal and LegalMonetaryTotal in the generated UBL XML. The stored invoice subtotal/vatTotal/total remain unchanged — the adjustments are computed at XML generation time.

UBL extensions (line-level)

Each line item can include a ublExtensions object:

NameTypeDescription
invoicePeriodobjectLine billing period: startDate (YYYY-MM-DD), endDate (YYYY-MM-DD)
allowanceChargesarrayLine-level allowances/charges (max 10). Each: chargeIndicator (bool), amount (numeric string). Optional: reasonCode, reason, baseAmount, multiplierFactorNumeric
additionalItemPropertiesarrayItem properties (max 20). Each: name (max 50 chars), value (max 100 chars)
originCountrystringItem origin country (ISO 3166-1 alpha-2, e.g., "DE")

Idempotency

Network failures, timeouts, and 5xx errors can leave a client unsure whether the invoice was actually created. Naive retry logic produces duplicate drafts. Storno offers two protections:

1. Explicit idempotency key (recommended)

Include idempotencyKey in the request body. The same key sent in a future request returns the original invoice instead of creating a new one.

The key must be:

  • Stable per logical operation. Same Stripe payment intent, same shopping-cart checkout, same external order ID → same key, every retry.
  • Unique within your namespace. Prefix with your service name to avoid collisions: stripe:pi_3TNpEhHy..., paddle:txn_01H..., myapp:order_42.
  • Up to 255 characters. Use the underlying transaction ID and you'll be safe.
{
  "clientId": "...",
  "currency": "USD",
  "lines": [...],
  "idempotencyKey": "stripe:pi_3TNpEhHyDIBD6PSZ1ZnXYN4I"
}
OutcomeStatusBehaviour
First call201 CreatedInvoice created, key recorded
Retry with same key201 CreatedThe original invoice is returned (same body, same id, same createdAt); no new row is inserted. Detect a retry on your side by storing/comparing the returned id
Retry with different key201 CreatedA new invoice is created — keys must match across retries

The idempotency_key column has a unique index — you cannot accidentally have two invoices with the same key.

2. Fuzzy fallback (when no key is provided)

If your client doesn't send an idempotencyKey and a draft for the same (company, client, currency, total) was created in the last hour, that existing draft is returned instead of creating a new one. This catches retry storms from clients that don't yet support idempotency keys.

The fuzzy fallback only matches DRAFT invoices and only within a 60-minute window. It will not interfere with legitimate "create two similar drafts back-to-back" workflows once an hour has passed, and it never affects already-issued invoices.

Best practice

Always send an idempotencyKey — it's the safest, most explicit, and survives the 60-minute fuzzy window. Use the underlying business identifier (Stripe payment intent ID, your order ID) prefixed with your service name.

Common invoice type codes

  • 380 - Commercial invoice (default)
  • 381 - Credit note
  • 384 - Corrected invoice
  • 389 - Self-billed invoice
  • 751 - Invoice information for accounting purposes

Request

curl -X POST https://api.storno.ro/api/v1/invoices \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Company: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
    "issueDate": "2024-02-15",
    "dueDate": "2024-03-15",
    "currency": "RON",
    "notes": "Payment terms: 30 days net",
    "paymentTerms": "Net 30",
    "idempotencyKey": "myapp:order_42",
    "lines": [
      {
        "description": "Web Development Services",
        "quantity": 10,
        "unitPrice": 100.00,
        "unitOfMeasure": "hours",
        "vatIncluded": false
      }
    ]
  }'

Response

Returns the created invoice object along with UBL validation results, with status 201 Created.

{
  "invoice": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "number": "DRAFT-a1b2c3d4",
    "status": "draft",
    "direction": "outgoing",
    "currency": "RON",
    "exchangeRate": 1.0,
    "issueDate": "2024-02-15",
    "dueDate": "2024-03-15",
    "subtotal": 1000.00,
    "vatTotal": 190.00,
    "total": 1190.00,
    "amountPaid": 0.00,
    "balance": 1190.00,
    "notes": "Payment terms: 30 days net",
    "paymentTerms": "Net 30",
    "client": {
      "id": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
      "name": "Acme Corporation SRL",
      "cif": "RO98765432"
    },
    "lines": [
      {
        "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
        "description": "Web Development Services",
        "quantity": 10.0,
        "unitPrice": 100.00,
        "unitOfMeasure": "hours",
        "vatRate": 19.0,
        "vatAmount": 190.00,
        "subtotal": 1000.00,
        "total": 1190.00
      }
    ],
    "createdAt": "2024-02-15T08:30:00Z",
    "updatedAt": "2024-02-15T08:30:00Z"
  },
  "validation": {
    "valid": true,
    "errors": [],
    "warnings": [],
    "schematronAvailable": true
  }
}

Error codes

CodeDescription
400Validation error - missing required fields or invalid data
401Missing or invalid authentication token
403No access to the specified company
404Client or series not found
422Business validation error (e.g., invalid dates, negative amounts)