Invoices
Create, send, and manage invoices through the admin API.
For general conventions, see JSON-API Conventions. For authentication, see Authentication.
Prerequisites
- Bearer token with admin access +
?o={organization_id}
Invoice Actions
Cancel an Invoice
POST /api/v1/invoices/{invoice_id}/cancel?o={org_id}
Creates a credit note and voids the invoice.
Mark as Paid
Mark a SENT invoice as paid without processing a payment:
PUT /api/v1/invoices/{invoice_id}/set-paid?o={org_id}
{
"send_notification": true
}
Set send_notification to true to email the customer a payment confirmation.
Change Recipient
Transfers an invoice to a different customer. This cancels the original invoice and creates a new copy for the new recipient:
POST /api/v1/invoices/{invoice_id}/change-recipient?o={org_id}
{
"customer_id": "new-customer-uuid"
}
Creating an Invoice
All examples below assume BASE_URL = https://{your-domain}/api/v1.
Step 1 — Find a Customer by Email
GET {BASE_URL}/customers?o={org_id}&filter[email]=jane@example.com&include=address
The email filter performs an exact (case-insensitive) match.
For a fuzzy name/email/phone search, use the search filter instead:
GET {BASE_URL}/customers?o={org_id}&filter[search]=jane&include=address
Filter by Custom Field
Custom fields are referenced by their UUID. Each filter entry requires three parts:
| Key | Description |
|---|---|
f_id |
Custom field UUID |
o |
Operator: =, <>, <, >, <=, >=, in |
v |
Value to match |
GET {BASE_URL}/customers?o={org_id}&filter[custom_fields][{FIELD_UUID}][f_id]={FIELD_UUID}&filter[custom_fields][{FIELD_UUID}][o]==&filter[custom_fields][{FIELD_UUID}][v]=Premium
For checkbox fields, pass true / false as the value. For multi-select fields, the in operator accepts comma-separated values.
Step 2 — Create a Customer (if not found)
POST {BASE_URL}/customers?o={org_id}
{
"data": {
"type": "customers",
"attributes": {
"given_name": "Jane",
"family_name": "Doe",
"email": "jane@example.com",
"company": "Acme Inc",
"locale": "de",
"mobile": "+4917612345678"
}
}
}
Required: email, locale
Create the customer's address as a separate resource:
POST {BASE_URL}/addresses?o={org_id}
{
"data": {
"type": "addresses",
"attributes": {
"name": "Jane Doe",
"street_address": "Hauptstraße 42",
"zip_code": "50667",
"city": "Köln",
"country_code": "DE"
},
"relationships": {
"addressable": {
"data": { "type": "customers", "id": "{customer_id}" }
}
}
}
}
Step 3 — Create the Invoice (draft)
POST {BASE_URL}/invoices?o={org_id}
{
"data": {
"type": "invoices",
"attributes": {
"currency": "EUR",
"recipient": "Acme Inc\nJane Doe\nHauptstraße 42\n50667 Köln",
"calculate_from_net": true
},
"relationships": {
"customer": {
"data": { "type": "customers", "id": "{customer_id}" }
}
}
}
}
Customer linking
| Method | When to use |
|---|---|
relationships.customer in the request body |
Manual invoices — pass the customer ID directly |
relationships.reference (order, subscription, booking pass) |
Backend derives customer from the referenced entity |
| Neither | Create without a customer; link one later via PATCH |
When the invoice is sent, the backend snapshots recipient and buyer_data from the customer so the invoice stays valid even if the profile changes later.
Attributes
| Attribute | Type | Required | Notes |
|---|---|---|---|
currency |
string | yes | e.g. "EUR", "USD" |
recipient |
string | no | Free-text address block; auto-filled from customer on send if blank |
invoicing_party |
string | no | Your company's address block |
calculate_from_net |
boolean | no | Default false. Set true when entering net amounts |
issued_at |
date | no | Required when status ≠ draft |
due_date |
date | no | |
notes |
string | no | Appears on the invoice |
vat_number |
string | no | |
is_quote |
boolean | no | Set true to create a quote instead |
number |
string | no | Must be unique within org if provided |
Step 4 — Add Line Items
POST {BASE_URL}/items?o={org_id}&calculate_from_net=true
{
"data": {
"type": "items",
"attributes": {
"title": "Consulting — March 2026",
"quantity": 10,
"single_net_amount": 150.00,
"tax_rate": 19
},
"relationships": {
"document": {
"data": { "type": "invoices", "id": "{invoice_id}" }
}
}
}
}
| Attribute | Type | Required | Notes |
|---|---|---|---|
title |
string | yes | Max 185 chars |
quantity |
integer | yes | |
tax_rate |
number | yes | ≥ 0 (e.g. 19 for 19%) |
single_net_amount |
number | yes* | Required when calculate_from_net=true |
single_gross_amount |
number | yes* | Required when calculate_from_net=false |
subtitle |
string | no | Max 185 chars |
* Provide the amount matching the invoice direction. The backend derives the other automatically.
Repeat for each line item. Invoice totals recalculate after each item.
Step 5 — Send the Invoice
POST {BASE_URL}/invoices/{invoice_id}/send?o={org_id}
Transitions status to sent and delivers via the configured notification channel.
Step 6 — Mark as Paid (optional)
POST {BASE_URL}/invoices/{invoice_id}/set-paid?o={org_id}&sendNotification=true
Full Workflow Summary
1. GET /customers?filter[email]=...&include=address → Find customer
2. POST /customers → Create customer (if new)
3. POST /addresses → Create customer address (if new)
4. POST /invoices → Create draft invoice
5. POST /items (repeat per line item) → Add line items
6. POST /invoices/{id}/send → Send invoice
7. POST /invoices/{id}/set-paid → Mark as paid
All endpoints include ?o={organization_id} for tenant context.
Common Errors
| Error | Cause | Solution |
|---|---|---|
403 — unauthorized |
Missing permissions for invoice operations | Ensure admin token has the required abilities |
422 — invoice not in SENT status |
set-paid on draft or already-paid invoice |
Check invoice status first |