Create invoice
/v1/invoicesCreates a sales invoice draft. Each line takes its VAT code and sales account from the item, or from the invoice's vat_code and sales_account_number when the item has none, so one invoice can mix rates (for example a 25% item and a fee without VAT). Line sales accounts must be marked as sales accounts and line VAT codes must be sales codes.
With book: true the request also needs invoices:book and books the invoice the same way as POST /v1/invoices/{id}/book. Each call is your own approval of the invoice: Numina does not review it, so make sure a person at your company has checked it or has approved the rules your system books by. Booking is final and cannot be undone through this API. It does not send the invoice. Fuld Service customers get 403 booking_not_available, a date in a closed accounting year or before the first accounting date gets 400, and an invoice with no bank account to be paid into gets 400 payment_details_required, all before anything is written.
Payment details come from bank_account_id, or from the company's default account when you omit it (see is_invoice_default on GET /v1/bank-accounts). Numina never guesses between several accounts: with more than one and no default, pass bank_account_id.
If booking fails after those checks (400, 409 or 500), the draft remains, and the error response does not include its id. Look for it with GET /v1/invoices?status=draft before you retry, because a failed request frees its Idempotency-Key and a retry creates a second draft. You can also create without book and then call POST /v1/invoices/{id}/book, which is safe to repeat.
Send an Idempotency-Key header (for example a UUID generated per request) to retry safely after a timeout or network error. A retry with the same key and the same body returns the original response with the header Idempotent-Replayed: true. Keys are scoped to the company and kept for 24 hours after a successful request. A failed request (4xx/5xx) frees the key, so a retry with that key runs again. Reusing a key with a different body returns 422; retrying while the first request is still processing returns 409.
Requires one ofinvoices:writewrite:draftwrite:ledger
Headers
- Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.
Body
application/json
- ID of the contact for this invoice
- Invoice issue date in YYYY-MM-DD format
- Payment due date in YYYY-MM-DD format
- ISO 4217 currency code (default: DKK). A currency Numina has no exchange rate for returns 400.
Default
"DKK" - currency_ratenumberExchange rate to DKK (1.0 for DKK invoices), kept while the invoice is a draft. Ignored at booking: a foreign-currency invoice then uses the latest rate Numina has on or before its issue date. Without such a rate Numina uses a fixed fallback rate.
Default
1 - sales_account_numberintegerSales account for lines that don't set their own (from GET /v1/accounts). Required unless every item has its own
sales_account_number. - vat_codestringVAT code for lines that don't set their own (e.g. 'U25'). Omit for 0% VAT on those lines. The rate comes from the code; GET /v1/vat-codes lists them. Set
vat_codeon each item to mix rates on one invoice. - Invoice line items (minimum 1 item required)
+Show 5 child attributesHide child attributes
- Line item description
- Quantity
- Unit price (excl. VAT)
- sales_account_numberintegerSales account for this line (from GET /v1/accounts). Omit to use the invoice's
sales_account_number. - VAT code for this line (from GET /v1/vat-codes).
nullfor no VAT on this line. Omitted, the line takes the default VAT code of its ownsales_account_number, as in the invoice editor, and otherwise the invoice'svat_code.
- Note displayed on the invoice
- bank_account_idstring (uuid)The bank account the customer pays into (from GET /v1/bank-accounts, one with
usable_on_invoices). Its payment details are copied onto the invoice. Omit to use the account markedis_invoice_default; when there is none the draft has no payment details and cannot be booked. - bookbooleanIf true, also requires invoices:book and books the invoice (assigns invoice number, creates AR entries, status becomes 'sent'). It does not send it. If false, saves a draft.
Default
false
Errors
400Invalid input or validation failed401Invalid or missing authentication403Missing scope, the company is archived, or the company is on Fuld Service409The invoice is being updated (with `book: true`; the draft remains), or a request with the same Idempotency-Key is still being processed422The Idempotency-Key was already used with a different request429Rate limit exceeded500Internal Server Error
Every error has the same shape. See Errors.
curl -X POST https://api.numina.app/v1/invoices \
-H "Authorization: Bearer numi_YOUR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "550e8400-e29b-41d4-a716-446655440000",
"issue_date": "2026-01-15",
"due_date": "2026-02-15",
"currency": "DKK",
"currency_rate": 1,
"sales_account_number": 1000,
"vat_code": "U25",
"items": [
{
"description": "Consulting services",
"quantity": 10,
"unit_price": 1000,
"sales_account_number": 1030,
"vat_code": "U25"
}
],
"memo": "Payment due within 30 days",
"bank_account_id": "550e8400-e29b-41d4-a716-446655440000",
"book": false
}'const response = await fetch("https://api.numina.app/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUMINA_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"contact_id": "550e8400-e29b-41d4-a716-446655440000",
"issue_date": "2026-01-15",
"due_date": "2026-02-15",
"currency": "DKK",
"currency_rate": 1,
"sales_account_number": 1000,
"vat_code": "U25",
"items": [
{
"description": "Consulting services",
"quantity": 10,
"unit_price": 1000,
"sales_account_number": 1030,
"vat_code": "U25"
}
],
"memo": "Payment due within 30 days",
"bank_account_id": "550e8400-e29b-41d4-a716-446655440000",
"book": false
}),
});
const data = await response.json();import uuid
import os
import requests
response = requests.post(
"https://api.numina.app/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['NUMINA_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"contact_id": "550e8400-e29b-41d4-a716-446655440000",
"issue_date": "2026-01-15",
"due_date": "2026-02-15",
"currency": "DKK",
"currency_rate": 1,
"sales_account_number": 1000,
"vat_code": "U25",
"items": [
{
"description": "Consulting services",
"quantity": 10,
"unit_price": 1000,
"sales_account_number": 1030,
"vat_code": "U25"
}
],
"memo": "Payment due within 30 days",
"bank_account_id": "550e8400-e29b-41d4-a716-446655440000",
"book": False
},
)
data = response.json(){
"id": "550e8400-e29b-41d4-a716-446655440000",
"invoice_number": 1001,
"type": "invoice",
"credited_invoice_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "sent",
"issue_date": "2026-01-15",
"due_date": "2026-02-15",
"contact_id": "550e8400-e29b-41d4-a716-446655440001",
"contact_name": "Acme ApS",
"currency": "DKK",
"subtotal": 10000,
"vat": 2500,
"vat_rate": 25,
"total": 12500,
"has_mixed_vat": false,
"outstanding_balance": 12500,
"outstanding_balance_currency": "DKK",
"currency_rate": 0,
"delivery_status": "sent",
"email_delivery_status": "string",
"email_sent_at": "string",
"e_invoice_sent_at": "string",
"is_overdue": false,
"payment_state": "open",
"unmatched_payments_note": "string",
"items": [
{
"unit": "string",
"vat_account_id": "string",
"vat_code": "string",
"sales_account_number": 0,
"vat_rate": 0,
"id": "550e8400-e29b-41d4-a716-446655440002",
"description": "Consulting services",
"quantity": 10,
"unit_price": 1000
}
],
"memo": "Project Q1 delivery",
"payment_details": {
"bank_name": "Danske Bank",
"scheme": "dk-registration-number",
"clearing_code": "3409",
"account_number": "0012345678",
"iban": "DK5030000012345678",
"swift_bic": "DABADKKK"
},
"created_at": "2026-01-15T10:30:00Z"
}