Introduction
Welcome to the TheNextInvoice API documentation.
Base URL
Currently, the API lives under the domain https://api.thenextinvoice.com/api/v1/. For brevity, all paths in this documentation will elide this base URL.
Response envelope
Response envelope
{
"success": true,
"data": {},
"error": {
"code": 0,
"message": "error message"
}
}
Our responses will always be wrapped in an envelope.
Of this envelope, some properties will be elided based on the type of response.
As such, the error object will only exist when success is false.
For the brevity of the documentation, this envelope will henceforth be elided.
Authentication
General day-to-day authentication is done either through a JWT token, or an API key.
JWTs are accepted through the Authorization header.
example: Authorization: Bearer {token}.
API tokens are accepted through the X-Api-Token header.
example: X-Api-Token: {token}.
Login using JWT
No authentication needed for this request.
POST /session/jwt
The way of logging in a user directly, using email and password. Can optionally specify what company to log into.
Example curl request
curl -X POST \
-H 'Content-Type: application/json' \
-d '{ "email": "user@example.com", "password": "pa$$word" }' \
https://api.thenextinvoice.com/api/v1/session/jwt
Example request
{
"email": "user@example.com",
"password": "pa$$word",
"company": 1
}
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| body | string | Yes | Email address | |
| password | body | string | Yes | Password |
| company | body | integer | No | Company ID to log in with |
Example response
{
"token": "jwt.auth.token",
"status": "confirmed"
}
Responses
| Status | Description |
|---|---|
| 200 | Login successful |
| 401 | The given combination of email, password (and optionally company) is not found. |
Switching company
POST /session/jwt/switch
After logging in, you can freely switch between the companies the user has access to. See Listing Companies for details on getting a list of companies.
Example request
{
"company": 1
}
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| company | body | integer | Yes | Company ID to request a token for |
Example response
{
"token": "jwt.auth.token"
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Invalid request |
| 401 | You do not have access to the given company, or it does not exist |
User preferences
GET /preferences
Returns various preferences related to the current company.
Example response
{
"package": "premium",
"addons": [
"lightspeed",
"payt"
],
"language": "EN",
"confirmed": true,
"grace": 0,
"user_data": {
"name": "TNI beta user",
"email": "tni@example.com"
},
"company_name": "TNI Beta"
}
Update user preferences
PUT /preferences
Updates the current user’s preferences. Returns the full preferences object (the
same shape as GET /preferences).
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| language | body | string | Yes | Preferred UI language, e.g. EN |
Example request
{
"language": "EN"
}
Responses
| Status | Description |
|---|---|
| 200 | Preferences updated |
| 500 | Update failed |
Ledger
Listing ledgers
GET /settings/ledgernumbers
Retrieves a list of all ledger numbers for the current company.
Example response
[
{
"id": 76,
"name": "Revenue",
"ledgernumber": "8000",
"costcenter": ""
},
{
"id": 85,
"name": "Management fee",
"ledgernumber": "4010",
"costcenter": ""
},
{
"id": 76,
"name": "Shipping",
"ledgernumber": "4020",
"costcenter": "KPL0001"
}
]
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
Creating ledger numbers
POST /settings/ledgernumbers
Creates a new ledger number
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| name | body | string | Yes | Human readable name for the new ledger number |
| ledgernumber | body | string | No | Code used for bookkeeping software. e.g. 4000, 8010 |
| costcenter | body | string | No | Cost center code used for bookkeeping software, e.g. KPL0001, KP2 |
Example response
{
"id": 155,
"name": "Revenue",
"ledgernumber": "8000",
"costcenter": ""
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Request failed |
Updating ledger numbers
PUT /settings/ledgernumbers/{id}
Updates a ledger number
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | query | int | Yes | Ledger id |
| name | body | string | Yes | Human readable name for the new ledger number |
| ledgernumber | body | string | No | Code used for bookkeeping software. e.g. 4000, 8010 |
| costcenter | body | string | No | Cost center code used for bookkeeping software, e.g. KPL0001, KP2 |
Example response
{
"id": 155,
"name": "Revenue",
"ledgernumber": "8000",
"costcenter": ""
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 404 | Ledger not found |
| 400 | Request failed |
Deleting ledger numbers
DELETE /settings/ledgernumbers
Removed a ledger number
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | query | int | Yes | Ledger id |
Example response
"Ledgernumber has been removed"
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 404 | Ledger not found |
| 500 | Request failed |
Invoice
Listing Invoices
GET /invoice/search
Retrieves a paginated list of all invoices in the current company.
Example query
GET /invoice/search?limit=10&reverse=true&status=&type=invoice&order=number&page=1
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| order | query | string | No | number / id | Field to order by. Options: id, customer, number, date, duedate, collection_date, amount. Default depends on type (number for invoice/quotation, id for draft/recurring/sepa). |
| reverse | query | boolean | No | false | Reverses the order of the results (true/false) |
| limit | query | integer | No | 20 | Return max limit rows in the paginated response. Max 100. |
| page | query | integer | No | 1 | What page of the pagination to return |
| status | query | string | No | Filter by status, comma-separated (max 5). Options: open, paid, pastdue, credit, credited, reminded, booked, notbooked, sepa |
|
| type | query | string | No | invoice | Invoice type. Options: invoice, quotation, recurring, draft, sepa |
| term | query | string | No | Free-text search term | |
| customer_id | query | integer | No | Filter by customer id | |
| minnumber | query | string | No | Minimum invoice number | |
| maxnumber | query | string | No | Maximum invoice number | |
| mindate | query | string | No | Minimum invoice date | |
| maxdate | query | string | No | Maximum invoice date | |
| minduedate | query | string | No | Minimum due date | |
| maxduedate | query | string | No | Maximum due date | |
| minamount | query | string | No | Minimum total amount of the invoice | |
| maxamount | query | string | No | Maximum total amount of the invoice |
Example response
{
"current": 1,
"first": 1,
"previous": 1,
"next": 1,
"last": 1,
"limit": 20,
"total_items": 1,
"items": [
{
"id": 45311,
"company": 1,
"number": "202400013",
"status": 1,
"type": "",
"discount": 0,
"includesVat": false,
"lines": [
{
"description": "Product 1",
"ledger": 11,
"price": 100,
"quantity": 1,
"vat": 3
}
],
"sender": 1,
"receiver": 2,
"meta": {
"currency": "EUR",
"dueDate": "2024-05-27",
"language": "nl",
"sendDate": "2024-03-28"
},
"text": {
"top": "This invoice is for item 1425",
"bottom": "Thank you for your order.",
"footer": ""
},
"calculated": {
"totalExclVat": 100,
"totalInclVat": 121,
"discountApplied": 0,
"vatApplied": 21,
"vatTotals": {
"21": 21
}
}
}
]
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
Status of an invoice
GET /invoice/{id}/status
Retrieves the status details of an invoice
Example query
GET /invoice/12552/status
Example response
{
"invoice": {
"state": "paid"
},
"booking": {
"status": true,
"method": "twinfield",
"message": "Invoice 201800013 has been booked",
"time": "2018-04-12 18:30:45"
},
"mail": {
"status": true,
"events": [
{
"recipient": "debtors@example.com",
"event": "queued",
"time": "2019-02-28 21:30:00"
},
{
"recipient": "debtors@example.com",
"event": "delivered",
"time": "2019-02-28 20:30:01"
}
]
},
"comments": {
"status": true,
"comments": [
{
"user": {
"id": 1,
"name": "TNI beta user"
},
"message": "This is a nice comment",
"time": "2023-12-20 11:13:37"
}
]
},
"sepa": {
"status": false,
"message": null,
"time": null
},
"collection": {
"status": false,
"date": null,
"type": null
},
"payment": {
"status": true,
"message": "Bankoverschrijving",
"time": "2019-01-21 00:00:00"
},
"reminder": {
"status": false,
"count": "0",
"time": null
},
"recurring": {
"status": false,
"time": null
},
"quotation": {
"status": false,
"message": null,
"time": null
}
}
Responses
| Status | Description |
|---|---|
| 200 | Invoice found |
| 404 | Invoice not found |
Create Invoice
POST /invoice
Create a new invoice.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| creationToken | body | string | No | Optional: a client-side generated idempotency UUID | |
| discount | body | float | Yes | 0 | Discount in percentage |
| includesVat | body | boolean | Yes | false | Amounts are including or excluding VAT |
| lines | body | object | Yes | List of invoice lines | |
| meta | body | object | Yes | Invoice meta object | |
| receiver | body | int or object | Yes | Receiver id or full Receiver object | |
| sender | body | int or object | Yes | Sender id or full Sender object | |
| status | body | int | No | 0 | Status of the invoice: 0 for drafts, 1 for finalized |
| text | body | object | Yes | Invoice text object | |
| type | body | string | No | "" | The type, either regular (empty string), C for Credit or I for Automatic Collection |
Invoice Line object
| Parameter | Type | Required | Description |
|---|---|---|---|
| quantity | float | Yes | Item quantity. Can be null if ledger, price, vat are all null |
| description | string | Yes | Description, can span multiple lines |
| ledger | int or object | Yes | Ledger id or Ledger object. Can be null if quantity, price, vat are all null |
| vat | int or object | Yes | Vat id or Vat object. Can be null if quantity, ledger, price are all null |
| price | float | Yes | Item price. Can be null if quantity, ledger, vat are all null |
Example request
{
"discount": 0,
"includesVat": false,
"lines": [
{
"description": "Product 1",
"ledger": 11,
"price": 100,
"quantity": 1,
"vat": 3
},
{
"description": "Description-only line",
"ledger": null,
"price": null,
"quantity": null,
"vat": null
}
],
"meta": {
"currency": "EUR",
"dueDate": "2019-05-27",
"language": "nl",
"sendDate": "2019-03-28"
},
"receiver": 2,
"sender": 1,
"status": 0,
"text": {
"bottom": "Thank you for your order.",
"footer": "Wij verzoeken u het factuurbedrag ter hoogte van {factuurbedrag} voor {vervaldatum} te voldoen op onze bankrekening onder vermelding van het factuurnummer {factuurnummer}. Alvast bedankt!",
"top": "This invoice is for item 1425"
},
"type": ""
}
Example response
45311
Responses
| Status | Description |
|---|---|
| 200 | Invoice created |
| 400 | Invoice not valid |
| 500 | Invoice saving failed |
Update an Invoice
PUT /invoice/{id}
Update a draft invoice. This endpoint expects a full Invoice object, as described under Create an Invoice.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| id | query | int | Yes | Invoice id | |
| Invoice object | body | json | Yes | Invoice data |
Example query
PUT /invoice/44783
Example response
44892
Responses
| Status | Description |
|---|---|
| 200 | Invoice updated |
| 400 | Invoice not valid |
| 500 | Invoice saving failed, or invoice was finalized |
Convert a draft Invoice into finalized
POST /invoice/{id}/send
Finalize a draft invoice.
Example request
POST /invoice/45311/send
Example response
45311
Responses
| Status | Description |
|---|---|
| 200 | Invoice created |
| 400 | Invoice not valid |
| 500 | Invoice saving failed |
Credit an invoice
POST /invoice/{id}/credit/
Credit an invoice marking the current invoice as ‘credited’ and creating and new invoice. In the newly created invoice, that is automatically marked as a finalized ‘credit’, all amounts are converted to the opposite signs.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| id | query | int | Yes | Invoice id |
Example request
POST /invoice/44783/credit
Example response
44789
Responses
| Status | Description |
|---|---|
| 201 | Invoice credited, new one created |
| 400 | Invoice not valid or invalid state transition |
| 404 | Invoice to be credited not found |
| 500 | Invoice saving failed |
Get the invoice payment url
GET /invoice/{id}/payment-url
Get the payment url for an invoice. When a user follows this link, they reach a landing page where they can view the invoice pdf, and pay using one of the payment providers set up by the sending company.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| id | query | int | Yes | Invoice id |
Example request
GET /invoice/44783/payment-url
Example response
thenextinvoice.com/t/a1b2c3d4e5f6
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 404 | Invoice not found |
Register an invoice payment
POST /invoice/{id}/payment
Register a finalized invoice as paid using the provided method and transaction date.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| id | query | int | Yes | Invoice id | |
| method | body | int | Yes | Payment method id | |
| date | body | string | Yes | Date on which the invoice was paid |
Example request
{
"date": "2019-04-16",
"method": 8
}
Responses
| Status | Description |
|---|---|
| 201 | Invoice marked as paid |
| 400 | Invoice or request not valid |
| 404 | Invoice or payment method not found |
| 409 | Invoice already paid |
| 500 | Payment saving failed |
Add a comment
POST /invoice/{id}/comment
Invoices support multiple comments, using this endpoint one at a time can be added.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| id | query | int | Yes | Invoice id | |
| message | body | string | Yes | The comment you want to add |
Example request
{
"message": "This is a nice comment"
}
Example response
{
"id": 123,
"invoice_id": 44783,
"user_id": 1,
"time": "2023-12-20 11:13:37",
"message": "This is a nice comment"
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Comment not valid or required field missing |
| 404 | Invoice not found |
| 500 | Comment saving failed |
Get PDF for Invoice
GET /invoice/{id}/view
Download the PDF of a given invoice.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| id | query | int | Yes | Invoice id |
Example query
GET /invoice/44783/view
Example response
binary: application/pdf;
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
Email Invoice
POST /invoice/{id}/send/mail
Email a single, finalized invoice. All placeholders are available and will be replaced with the values corresponding to the invoice.
When it is desired to send multiple invoices, use the /invoice/queue/ endpoint below.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| id | query | int | Yes | Invoice id | |
| subject | body | string | Yes | Email subject | |
| body | body | string | Yes | Email body | |
| attachments | body | object | No | Optional attachments, with base64 encoded binary in content field |
Example request
{
"subject": "Invoice {invoicenumber}",
"body": "<p>Dear {companyname},</p><p><br></p><p>Thank you for your order. Attached please find invoice {invoicenumber} that is due on {duedate}.</p><p>You can pay this invoice directly by following the link: {paymentlink}</p><p><br></p><p>Sincerely,</p><p><br></p><p>{companyname}</p>",
"attachments": {
"content": "base64 encoded binary",
"filename": "Order confirmation.png"
}
}
Example response
45311
Responses
| Status | Description |
|---|---|
| 200 | Invoice mailed |
| 400 | Invoice not valid or required field missing |
| 404 | Invoice not found |
| 500 | Invoice saving failed |
Bulk operations
Several invoice actions accept a comma-separated list of ids in place of a
single {id}, e.g. POST /invoice/45311,45312,45313/book. These are noted as
“bulk” below.
Delete invoice(s)
DELETE /invoice/{id}
Soft-delete one or more invoices. Bulk (comma-separated ids).
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | Invoice id, or comma-separated ids |
Responses
| Status | Description |
|---|---|
| 200 | Invoice(s) deleted |
| 404 | Invoice not found |
Book invoice(s)
POST /invoice/{id}/book
Queue one or more invoices to be booked into the connected accounting system. Bulk.
Responses
| Status | Description |
|---|---|
| 200 | Updated the booking queue |
| 404 | Invoice not found |
| 500 | Could not add invoice to queue |
Remind invoice(s)
POST /invoice/{id}/remind
Register a payment reminder for one or more invoices. Bulk. Takes no body.
Responses
| Status | Description |
|---|---|
| 200 | Reminder(s) created |
| 500 | Could not save reminder |
Approve invoice(s)
POST /invoice/{id}/approve
Approve one or more quotations. Bulk. Takes no body.
Responses
| Status | Description |
|---|---|
| 200 | Invoice(s) approved |
| 400 | Invalid state |
Archive invoice(s)
POST /invoice/{id}/archive
Archive one or more quotations. Bulk. Takes no body.
Responses
| Status | Description |
|---|---|
| 200 | Invoice(s) archived |
| 400 | Invalid state |
Queue multiple invoices for mailing
POST /invoice/{id}/queue
Queue finalized invoices to be emailed. Bulk.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | Invoice id(s) |
| subject | body | string | Yes | Email subject |
| body | body | string | Yes | Email body |
Responses
| Status | Description |
|---|---|
| 200 | Invoices added to queue |
| 400 | Missing subject and/or body |
Create SEPA batch
POST /invoice/{id}/sepa
Create a SEPA direct-debit batch for one or more invoices. Bulk.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | Invoice id(s) |
| date | body | string | Yes | Collection date, YYYY-MM-DD |
Responses
| Status | Description |
|---|---|
| 200 | SEPA batch created |
| 400 | Date empty or invalid |
| 404 | Invoice not found |
Send invoice by SMS
POST /invoice/{id}/send/sms
Send a finalized invoice to its receiver by SMS. Requires SMS to be enabled and the receiver to have a phone number. The SMS text is generated automatically.
Responses
| Status | Description |
|---|---|
| 200 | Sent |
| 403 | SMS not enabled |
| 400 | Receiver has no phone number |
| 404 | Invoice not found |
Send invoice over Peppol
POST /invoice/{id}/send/peppol
Send a finalized invoice over the Peppol network. Requires a Peppol-verified sender profile.
Responses
| Status | Description |
|---|---|
| 200 | Sent |
| 403 | Sender profile not Peppol-verified |
| 404 | Invoice not found |
Resend an invoice
POST /invoice/{id}/resend
Resend a finalized invoice through its original send channel. Takes no body.
Responses
| Status | Description |
|---|---|
| 200 | Resent |
| 404 | Invoice not found |
Set tracking number
POST /invoice/{id}/tracking-number
Set the shipment tracking number on an invoice.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | int | Yes | Invoice id |
| tracking_number | body | string | Yes | Shipment tracking number |
Responses
| Status | Description |
|---|---|
| 200 | Tracking number saved |
| 404 | Invoice not found |
| 500 | Failed to save tracking number |
Delete invoice payment
DELETE /invoice/{id}/payment
Remove the registered payment from a paid invoice.
Responses
| Status | Description |
|---|---|
| 200 | Payment removed |
| 409 | Invoice is not paid |
| 404 | Invoice not found |
Get UBL for invoice
GET /invoice/{id}/ubl
Get the UBL (XML) document for a finalized invoice.
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| id | path | int | Yes | Invoice id | |
| peppol | query | boolean | No | false | Render the Peppol BIS variant of UBL |
Responses
| Status | Description |
|---|---|
| 200 | UBL document (application/xml) |
| 400 | Cannot generate UBL from a draft |
| 404 | Invoice not found |
Sync payments
POST /invoice/syncpayments
Trigger a reconciliation sync of invoice payments from connected payment providers. Takes no body.
Responses
| Status | Description |
|---|---|
| 200 | Invoice payments are being synced |
| 500 | Could not request payment sync |
Get an invoice
GET /invoice/{id}
Retrieves a single invoice by id, with the full invoice object (the same shape as the items returned by invoice search).
Draft invoices (
status: 0) returnreceiver,senderandcalculatedasnull. These are resolved and snapshotted onto the invoice only when it is finalized, so totals and embedded sender/receiver details are not yet available for drafts. To find drafts for a specific customer, filter invoice search bycustomer_id(the link exists even thoughreceiveris not yet populated).
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | int | Yes | Invoice id |
Responses
| Status | Description |
|---|---|
| 200 | Invoice found |
| 404 | Invoice not found |
Invoice totals
GET /invoice/totals
Returns aggregate totals for the invoices matching the given filters. Accepts
the same filter parameters as invoice search (type,
status, term, customer_id, date/amount ranges). Totals are keyed by
currency.
Example response
{
"EUR": {
"amount_incl": 12100.00,
"amount_excl": 10000.00
}
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 404 | No totals found for this period |
Invoice downloads
These endpoints return binary/file responses rather than JSON.
Download invoice PDF
POST /invoice/{id}/send/pdf
Generates and returns the invoice PDF.
Example response
binary: application/pdf
Responses
| Status | Description |
|---|---|
| 200 | PDF returned |
| 404 | Invoice not found |
| 500 | Could not generate PDF |
Download invoice ZIP
GET /invoice/{id}/zip
Returns a ZIP containing the invoice PDF and any attachments. Bulk (comma-separated ids).
Example response
binary: application/zip
Responses
| Status | Description |
|---|---|
| 200 | ZIP returned |
| 404 | Invoice not found |
Download an invoice attachment
GET /invoice/{id}/attachment/{attachmentId}
Returns a single attachment file for the invoice, with its original content type.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | int | Yes | Invoice id |
| attachmentId | path | int | Yes | Attachment id |
Responses
| Status | Description |
|---|---|
| 200 | Attachment returned |
| 404 | Invoice/attachment not found |
Download packing slip
GET /invoice/{id}/packing-slip
Returns the packing-slip PDF for an invoice.
Example response
binary: application/pdf
Responses
| Status | Description |
|---|---|
| 200 | PDF returned |
| 404 | Invoice not found |
Export invoices as CSV
GET /invoice/csv
Exports the invoices matching the current filters as a ;-separated CSV.
Accepts the same filter parameters as invoice search.
Example response
binary: text/csv
Responses
| Status | Description |
|---|---|
| 200 | CSV returned |
Customer
Listing Customers
GET /customer
Retrieves a list of all customers in the current company.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| term | query | string | No | Search term for name or contact |
Example response
[
{
"id": "1",
"debnumber": "debtor number",
"companyname": "Company name",
"companycontact": "Contact name",
"address": "Address Street 101",
"postalcode": "1111AA",
"city": "Amsterdam",
"country": "NL",
"vatnumber": "VAT NUMBER",
"email": "company@example.com",
"ccaddress": "companycc@example.com",
"phone": "+31699999999",
"iban": "NL73EXMPL000000000",
"optionalfield_1": null,
"optionalfield_2": null,
"person": "0",
"option_expiration": null,
"option_extratext": null,
"option_extratextbottom": null,
"option_language": null,
"option_currency": null
},
{
"id": "2",
"debnumber": null,
"companyname": "Example Person",
"companycontact": null,
"address": "Somewhere Street 1",
"postalcode": "111111",
"city": "Amsterdammeke",
"country": "NL",
"vatnumber": "",
"email": "person@example.com",
"ccaddress": "",
"phone": null,
"iban": null,
"optionalfield_1": null,
"optionalfield_2": null,
"person": "1",
"option_expiration": null,
"option_extratext": null,
"option_extratextbottom": null,
"option_language": null,
"option_currency": null
}
]
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
Paginated Customers
GET /customer/search
Retrieves a paginated list of all customers in the current company.
Example query
GET /customer/search?limit=10&page=1&term=Test
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| term | query | string | No | Search term for name or contact | |
| limit | query | integer | No | 50 | Return max limit rows in the paginated response. Max 100. |
| page | query | integer | No | 1 | What page of the pagination to return |
Example response
{
"current": "1",
"first": "1",
"items": [
{
"id": "1",
"debnumber": "debtor number",
"companyname": "Company name",
"companycontact": "Contact name",
"address": "Address Street 101",
"postalcode": "1111AA",
"city": "Amsterdam",
"country": "NL",
"vatnumber": "VAT NUMBER",
"email": "company@example.com",
"ccaddress": "companycc@example.com",
"phone": "+31699999999",
"iban": "NL73EXMPL000000000",
"optionalfield_1": null,
"optionalfield_2": null,
"person": "0",
"option_expiration": null,
"option_extratext": null,
"option_extratextbottom": null,
"option_language": null,
"option_currency": null
},
{
"id": "2",
"debnumber": null,
"companyname": "Example Person",
"companycontact": null,
"address": "Somewhere Street 1",
"postalcode": "111111",
"city": "Amsterdammeke",
"country": "NL",
"vatnumber": "",
"email": "person@example.com",
"ccaddress": "",
"phone": null,
"iban": null,
"optionalfield_1": null,
"optionalfield_2": null,
"person": "1",
"option_expiration": null,
"option_extratext": null,
"option_extratextbottom": null,
"option_language": null,
"option_currency": null
}
],
"last": "1",
"limit": "50",
"next": "1",
"previous": "1",
"total_items": "2"
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
Get Single Customer
GET /customer/{id}
Get all details for a single customer.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | ID of customer |
Example response
{
"id": "1",
"debnumber": "debtor number",
"companyname": "Company name",
"companycontact": "Contact name",
"address": "Address Street 101",
"postalcode": "1111AA",
"city": "Amsterdam",
"country": "NL",
"vatnumber": "VAT NUMBER",
"email": "company@example.com",
"ccaddress": "companycc@example.com",
"phone": "+31699999999",
"iban": "NL73EXMPL000000000",
"optionalfield_1": null,
"optionalfield_2": null,
"person": "0",
"option_expiration": null,
"option_extratext": null,
"option_extratextbottom": null,
"option_language": null,
"option_currency": null
}
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 404 | Customer does not exist |
Create Customer
POST /customer
Create a new customer.
Example request
{
"companyname": "Company name",
"companycontact": "Contact name",
"address": "Address Street 101",
"postalcode": "1111AA",
"city": "Amsterdam",
"country": "NL",
"email": "company@example.com",
"person": false
}
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| companyname | body | string | Yes | Customer name |
| companycontact | body | string | No | Contact name (if customer is not a person) |
| address | body | string | Yes | Address |
| postalcode | body | string | Yes | Postal code |
| city | body | string | Yes | City |
| country | body | string | Yes | Country code |
| body | string | Yes | Email address | |
| ccaddress | body | string | No | CC email address |
| debnumber | body | string | No | Debtor number |
| vatnumber | body | string | No | VAT number |
| phone | body | string | No | Phone number |
| iban | body | string | No | IBAN number |
| optionalfield_1 | body | string | No | optional field |
| optionalfield_2 | body | string | No | optional field |
| person | body | bool | Yes | is customer a person (true) or company (false) |
| option_expiration | body | number | No | custom expiration time for invoices |
| option_extratext | body | string | No | custom extra text for invoices |
| option_extratextbottom | body | string | No | custom extra text (bottom) for invoices |
| option_language | body | string | No | custom language for invoices |
| option_currency | body | string | No | custom currency for invoices |
Example response
{
"id": 1
}
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 400 | Invalid request |
Update Customer
PUT /customer/{id}
Updates an existing customer. All properties that are not set will be defaulted back to their default value.
Example request
{
"companyname": "Company name",
"companycontact": "Contact name",
"address": "Address Street 101",
"postalcode": "1111AA",
"city": "Amsterdam",
"country": "NL",
"email": "company@example.com",
"person": false
}
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| companyname | body | string | Yes | Customer name |
| companycontact | body | string | No | Contact name (if customer is not a person) |
| address | body | string | Yes | Address |
| postalcode | body | string | Yes | Postal code |
| city | body | string | Yes | City |
| country | body | string | Yes | Country code |
| body | string | Yes | Email address | |
| ccaddress | body | string | No | CC email address |
| debnumber | body | string | No | Debtor number |
| vatnumber | body | string | No | VAT number |
| phone | body | string | No | Phone number |
| iban | body | string | No | IBAN number |
| optionalfield_1 | body | string | No | optional field |
| optionalfield_2 | body | string | No | optional field |
| person | body | bool | Yes | is customer a person (true) or company (false) |
| option_expiration | body | number | No | custom expiration time for invoices |
| option_extratext | body | string | No | custom extra text for invoices |
| option_extratextbottom | body | string | No | custom extra text (bottom) for invoices |
| option_language | body | string | No | custom language for invoices |
| option_currency | body | string | No | custom currency for invoices |
Example response
{
"id": 1
}
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 400 | Invalid request |
Delete Customer
DELETE /customer/{id}
Deletes an existing customer.
Responses
| Status | Description |
|---|---|
| 200 | Customer deleted |
| 404 | Customer does not exist |
Resolving customer data through VAT number
GET /customer/checkvat
Attempts to resolve the given VAT information to a company, verifying if it exists, and if so returning some basic properties.
Example query
GET /customer/checkvat?countryCode=NL&vatNumber=857494454B01
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| countryCode | query | string | Yes | Country code for the VAT number |
| vatNumber | query | string | Yes | VAT number (without country code) |
Example response
{
"name": "Thenextinvoice b.v.",
"street": "Condensatorweg 00054",
"zip": "1014AX",
"city": "Amsterdam"
}
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 400 | Invalid request |
| 404 | VAT code not found |
Resolving customer data through Chamber of Commerce
GET /customer/checkcoc
Looks up a Dutch company in the Chamber of Commerce (KvK) register by CoC number
or by name. Provide at least one of number or name.
Example query
GET /customer/checkcoc?number=12345678
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| number | query | string | No* | Chamber of Commerce number |
| name | query | string | No* | Company name to search for |
* At least one of number or name is required.
Example response
{
"name": "Thenextinvoice b.v.",
"street": "Condensatorweg 54",
"zip": "1014AX",
"city": "Amsterdam"
}
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 400 | No name or number given |
| 404 | Company not found |
Listing duplicate customers
GET /customer/duplicates
Returns groups of customers that appear to be duplicates (matched on name / contact details), to support de-duplication.
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
Merging customers
PUT /customer/merge/{id}
Merges one or more duplicate customers into the master customer {id}: all
invoices belonging to the listed customers are reassigned to the master, and the
merged customers are removed.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | int | Yes | Master customer id to merge into |
| (body) | body | array | Yes | JSON array of customer ids to merge into the master |
Example request
[
12,
34,
56
]
Responses
| Status | Description |
|---|---|
| 200 | Customers merged |
| 404 | Customer does not exist |
Importing customers
POST /customer/import
Bulk-creates customers from a JSON array of customer objects (each object uses the same fields as Create Customer). The import is transactional: if any row fails validation the whole import is rolled back.
Example request
[
{
"companyname": "Company A",
"address": "Street 1",
"postalcode": "1111AA",
"city": "Amsterdam",
"country": "NL",
"email": "a@example.com",
"person": false
},
{
"companyname": "Company B",
"address": "Street 2",
"postalcode": "2222BB",
"city": "Rotterdam",
"country": "NL",
"email": "b@example.com",
"person": false
}
]
Responses
| Status | Description |
|---|---|
| 200 | Customers imported |
| 400 | A row failed validation (rolled back) |
Verifying a customer for Peppol
GET /customer/verifypeppol/{id}
Checks whether a customer is a registered participant on the Peppol network. Requires Peppol to be enabled for the company.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | int | Yes | Customer id |
Responses
| Status | Description |
|---|---|
| 200 | Verification result returned |
| 403 | Peppol not enabled |
| 404 | Customer does not exist |
Product
Listing Products
GET /product/search
Lists (and optionally searches for) a list of all products in the current company. Response is paginated.
Example query
GET /product/search?order=id&page=1
Parameters
| Parameter | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
| term | query | string | No | Search term for product description | |
| order | query | string | No | id | Specifies which field to order by. Options are id, description, price |
| reverse | query | boolean | No | false | Reverses the order of the results |
| limit | query | integer | No | 10 | Return max limit rows in the paginated response. Max 100. |
| page | query | integer | No | 0 | What page of the pagination to return |
Example response
{
"before": 1,
"current": 1,
"first": 1,
"items": [
{
"description": "Product 1",
"id": 1,
"ledgernumber_id": 1,
"price": 101,
"vat_id": 3
},
{
"description": "Product 2",
"id": 7,
"ledgernumber_id": null,
"price": 10,
"vat_id": null
},
{
"description": "Product 3",
"id": 9,
"ledgernumber_id": 11,
"price": 12,
"vat_id": 2
},
{
"description": "Product 4",
"id": 10,
"ledgernumber_id": 11,
"price": 15,
"vat_id": 3
},
{
"description": "Product 5",
"id": 13,
"ledgernumber_id": 11,
"price": 10,
"vat_id": 1
}
],
"last": 140,
"limit": 5,
"next": 2,
"total_items": 699,
"total_pages": 140
}
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
Get Single Product
GET /product/{id}
Get all details for a single product.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | ID of product |
Example response
{
"description": "Product 2",
"id": 7,
"ledgernumber_id": null,
"price": 10,
"vat_id": null
}
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 404 | Product does not exist |
Create Product
POST /product
Create a new product.
Example request
{
"description": "Description",
"price": 100,
"vat_id": null,
"ledgernumber_id": null
}
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| description | body | string | Yes | Product description |
| price | body | number | Yes | Price of the product |
| vat_id | body | number | No | ID of the associated VAT number |
| ledgernumber_id | body | number | No | ID of the associated ledger number |
Example response
"Product has been created"
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 400 | Invalid request |
Update Product
PUT /product/{id}
Updates a product.
Example request
{
"description": "Updated Description",
"price": 1000,
"vat_id": 4,
"ledgernumber_id": null
}
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | number | Yes | ID of the product to update |
| description | body | string | Yes | Product description |
| price | body | number | Yes | Price of the product |
| vat_id | body | number | No | ID of the associated VAT number |
| ledgernumber_id | body | number | No | ID of the associated ledger number |
Example response
{
"id": 1,
"description": "Updated Description",
"price": 1000,
"vat_id": 4,
"ledgernumber_id": null
}
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 400 | Invalid request |
Delete Product
DELETE /product/{id}
Deletes an existing product.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | number | Yes | ID of the product to delete |
Example response
"Product has been removed"
Responses
| Status | Description |
|---|---|
| 200 | Product deleted |
| 404 | Product does not exist |
Company
Listing Companies
GET /company
Retrieves a list of all companies the current user has access to.
NOTE: See Profile Logo for usage of the logo option.
Parameters
| Parameter | In | Type | Required | Description |
|---|
Example response
[
{
"id": 1,
"name": "Company name",
"address": "Company address",
"postalcode": "Company zipcode",
"city": "Amsterdam",
"coc": "CoC code",
"next_number": "000001",
"next_qnumber": "000001",
"country": "NL",
"logo": "1.png"
}
]
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
| 401 | You do not have access to any companies |
VAT codes
Table of types:
| Type | Description |
|---|---|
| high | High |
| low | Low |
| exempt | Exempt |
| none | No VAT |
| ICPS | Services |
| ICPG | Goods |
| EBU | Export outside EU |
Listing VAT codes
GET /settings/vatcodes
Retrieves a list of all vat codes for the current company.
Example response
[
{
"id": 155,
"name": "6% BTW",
"code": "VL",
"factor": 1.06,
"type": null
},
{
"id": 156,
"name": "21% BTW",
"code": "VH",
"factor": 1.21,
"type": null
},
{
"id": 154,
"name": "Geen BTW",
"code": "",
"factor": 1,
"type": "none"
}
]
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
Creating VAT codes
POST /settings/vatcodes
Creates a new VAT code
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| name | body | string | Yes | Human readable name for the new VAT code |
| code | body | string | No | Code used for bookkeeping software. e.g. VL, VH |
| factor | body | number | Yes | Multiplier factor for the VAT. e.g. 1.21 => 21% VAT |
| type | body | string | Yes | Type of VAT code. See VAT codes for a table of options |
Example response
{
"id": 155,
"name": "6% BTW",
"code": "VL",
"factor": 1.06,
"type": null
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Request failed |
Updating VAT codes
PUT /settings/vatcodes/{id}
Updates a VAT code
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | query | int | Yes | Vat id |
| name | body | string | Yes | Human readable name for the new VAT code |
| code | body | string | No | Code used for bookkeeping software. e.g. VL, VH |
| factor | body | number | Yes | Multiplier factor for the VAT. e.g. 1.21 => 21% VAT |
| type | body | string | No | Type of VAT code. See VAT codes for a table of options |
Example response
{
"id": 155,
"name": "6% BTW",
"code": "VL",
"factor": 1.06,
"type": null
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Request failed |
| 404 | Vat not found |
Deleting VAT codes
DELETE /settings/vatcodes/{id}
Remove a VAT code
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | query | int | Yes | VAT id |
Example response
"Vatcode has been removed"
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 404 | VAT code not found |
| 500 | Request failed |
Listing VAT codes (percentage form)
GET /vatcodes
Like GET /settings/vatcodes, but returns the VAT rate as a percentage
rather than a factor. For example a 21% code is returned as factor: 21 here,
versus factor: 1.21 from /settings/vatcodes. Useful when you want the rate
to display directly.
Example response
[
{
"id": 156,
"name": "21% BTW",
"code": "VH",
"factor": 21,
"type": null
},
{
"id": 154,
"name": "Geen BTW",
"code": "",
"factor": 0,
"type": "none"
}
]
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
Analytics
Turnover
GET /analytics/turnover/{interval}
Returns turnover for the current company over the last 12 periods. The response
contains 12 labels and 12 corresponding values, oldest first.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| interval | path | string | Yes | Aggregation: month or year |
Example query
GET /analytics/turnover/month
Example response
{
"labels": ["Jul 2025", "Aug 2025", "Sep 2025", "Oct 2025", "Nov 2025", "Dec 2025", "Jan 2026", "Feb 2026", "Mar 2026", "Apr 2026", "May 2026", "Jun 2026"],
"values": [1200.5, 980, 0, 3400.75, 1500, 2100, 1800, 950, 2750, 3010.4, 1990, 2230]
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Invalid interval (allowed: month, year) |
Top customers
GET /analytics/customers/{limit}
Returns the top customers by turnover (within the last year), highest first.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| limit | path | integer | Yes | Number of customers to return (1–50) |
Example response
[
{ "customerid": 2, "companyname": "ACME B.V.", "amount": 14250.00 },
{ "customerid": 9, "companyname": "Example Person", "amount": 8800.50 }
]
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Invalid limit (must be 1–50) |
Prognosis
A prognosis holds a company’s financial forecast. costs, debts and
revenue are JSON arrays of monthly figures, starting from
start_month/start_year.
Get prognosis
GET /prognosis
Returns the current company’s prognosis, or null if none has been set.
Example response
{
"id": 12,
"company_id": 1,
"start_month": 1,
"start_year": 2026,
"costs": [1000, 1000, 1200],
"debts": [0, 500, 0],
"revenue": [3000, 3200, 4100]
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
Save prognosis
POST /prognosis or PUT /prognosis
Creates or updates the company’s prognosis (a company has at most one).
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| start_month | body | integer | Yes | First month of the forecast (1–12) |
| start_year | body | integer | Yes | First year of the forecast |
| costs | body | array | Yes | Monthly cost figures |
| debts | body | array | Yes | Monthly debt figures |
| revenue | body | array | Yes | Monthly revenue figures |
Example request
{
"start_month": 1,
"start_year": 2026,
"costs": [1000, 1000, 1200],
"debts": [0, 500, 0],
"revenue": [3000, 3200, 4100]
}
Responses
| Status | Description |
|---|---|
| 200 | Prognosis saved |
| 400 | Invalid request |
SEPA
SEPA direct-debit batches are created from invoices (see Create SEPA batch under Invoice). The endpoints here list and download existing batches.
Listing SEPA batches
GET /sepa/search
Retrieves a paginated list of SEPA batches for the current company. Accepts the
same pagination and filter parameters as invoice search
(limit, page, order, reverse, date/amount ranges, …).
Example query
GET /sepa/search?limit=10&page=1
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
SEPA totals
GET /sepa/totals
Returns aggregate totals for the SEPA batches matching the current filters, keyed by currency.
Example response
{
"EUR": {
"amount_incl": 4235.00,
"amount_excl": 3500.00
}
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
Download a SEPA file
GET /sepa/{id}/view
Returns the SEPA direct-debit XML (pain.008) for a batch, ready to upload to a bank.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | int | Yes | SEPA batch id |
Example response
binary: application/xml
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 404 | SEPA batch not found |
Payment methods
Listing payment methods
GET /settings/paymentmethods
Retrieves a list of all payment methods for the current company.
Example response
[
{
"id": 8,
"name": "Bank transfer"
},
{
"id": 76,
"name": "Stripe"
}
]
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 404 | No payment methods found |
Creating payment methods
POST /settings/paymentmethods
Creates a new payment method
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| name | body | string | Yes | Human readable name for the new payment method |
Example response
{
"id": 155,
"name": "Mollie iDeal transfer"
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Request failed |
Updating payment methods
PUT /settings/paymentmethods/{id}
Updates a payment method
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | query | int | Yes | Payment method id |
| name | body | string | Yes | Human readable name for the payment method |
Example response
{
"id": 155,
"name": "iDeal transfer"
}
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Request failed |
| 404 | Payment method not found |
Deleting payment methods
DELETE /settings/paymentmethods
Remove a payment method from this company
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | query | int | Yes | Payment method id |
Example response
"Payment method has been removed"
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 404 | Payment method not found |
| 500 | Request failed |
Company settings
General per-company configuration. (VAT codes, ledger numbers and payment methods have their own pages.)
Invoice & quotation numbering
GET /settings/numbers
Returns the next invoice and quotation numbers.
Example response
{
"invoice": "000001",
"quotation": "00001"
}
PUT /settings/numbers
Updates the next invoice and/or quotation numbers.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| invoice | body | string | No | Next invoice number |
| quotation | body | string | No | Next quotation number |
Responses
| Status | Description |
|---|---|
| 200 | Success (GET) / numbers updated (PUT) |
| 400 | Update failed (PUT) |
| 404 | Company not found |
Debit ledger number
GET /settings/ledgernumberdebit
Returns the debit (accounts-receivable) ledger number used for bookkeeping (defaults to 1000).
Example response
{ "ledgerNumber": 1300 }
PUT /settings/ledgernumberdebit
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| ledgerNumber | body | integer | Yes | Debit ledger number |
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | ledgerNumber empty (PUT) |
| 500 | Save failed (PUT) |
SEPA creditor settings
GET /settings/sepa
Returns the company’s SEPA creditor configuration (used when creating SEPA direct-debit batches).
Example response
{
"id": 1,
"company_id": 1,
"creditor_name": "ACME B.V.",
"iban": "NL73EXMPL000000000",
"creditor_identifier": "NL98ZZZ999999990000"
}
PUT /settings/sepa
Updates the creditor settings. Values are validated; validation is skipped only if all three are empty.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| creditor_name | body | string | No | Creditor name (max 70 chars) |
| iban | body | string | No | Creditor IBAN |
| creditor_identifier | body | string | No | SEPA creditor identifier |
Responses
| Status | Description |
|---|---|
| 200 | SEPA information updated |
| 400 | Invalid name / IBAN / identifier |
Payment messages
GET /settings/payment
Returns the messages shown on the payment landing page.
Example response
{
"message": "Please pay invoice {invoicenumber}.",
"success_message": "Thank you for your payment."
}
PUT /settings/payment
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| message | body | string | Yes | Payment-page message |
| success_message | body | string | Yes | Message shown after payment |
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 400 | Missing field (PUT) |
| 500 | Save failed (PUT) |
Peppol settings
GET /settings/peppol
Returns whether Peppol is enabled for the company.
Example response
{ "id": 1, "company_id": 1, "enabled": true }
PUT /settings/peppol
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| enabled | body | boolean | Yes | Enable/disable Peppol |
Responses
| Status | Description |
|---|---|
| 200 | Request successful |
| 500 | Could not load/save Peppol settings |
Terms & conditions
A company can store a single terms & conditions document.
HEAD /settings/termsandconditions — check whether a document exists (200 if so,
404 if not).
GET /settings/termsandconditions — download the stored document (binary).
POST /settings/termsandconditions — upload a document. Send exactly one
file as multipart/form-data.
DELETE /settings/termsandconditions — remove the document.
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | No file, or more than one file uploaded (POST) |
| 404 | No terms and conditions set (HEAD/GET) |
| 500 | Storage error (POST upload / DELETE) |
Email settings
Sender & CC addresses
Email can be sent from verified from addresses, optionally CC’d to cc
addresses. Each address is verified before it can be used.
List addresses
GET /settings/email/address
Example response
[
{ "id": 1, "type": "from", "email": "sender@example.com", "verified": true },
{ "id": 2, "type": "cc", "email": "cc@example.com", "verified": false }
]
Add an address
POST /settings/email/address
Adds an address and sends it a verification email.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| type | body | string | Yes | from or cc |
| body | string | Yes | Email address | |
| redirect | body | string | No | URL to redirect to from the verification link |
Example response
{ "id": 3, "type": "from", "email": "new@example.com", "verified": false }
Verify an address
POST /settings/email/verify
Confirms an address using the key from the verification email. (This is the target of the verification link; usually not called directly.)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| body | string | Yes | Address being verified | |
| key | body | string | Yes | Verification key (SHA-256) |
| company | body | integer | Yes | Company id |
Remove an address
DELETE /settings/email/address/{id}/{type}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | integer | Yes | Address id |
| type | path | string | Yes | from or cc |
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | Invalid type / redirect, or invalid verify request |
| 404 | Address not found (remove) |
| 500 | Verification email could not be sent (add) |
Email templates
Reusable email subject/body templates. Placeholders such as {invoicenumber},
{duedate}, {paymentlink} are substituted at send time.
List templates
GET /settings/email/text
Example response
[
{ "id": 1, "subject": "Invoice {invoicenumber}", "name": "Default", "text": "<p>Dear {companyname}...</p>" }
]
Get / create / update / delete
GET /settings/email/text/{id} — fetch one.
POST /settings/email/text — create.
PUT /settings/email/text/{id} — update.
DELETE /settings/email/text/{id} — remove.
For create and update:
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| subject | body | string | Yes | Email subject |
| name | body | string | Yes | Template name |
| text | body | string | Yes | Email body |
Example response
{ "id": 1, "subject": "Invoice {invoicenumber}", "name": "Default", "text": "<p>Dear {companyname}...</p>" }
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | Validation failed |
| 404 | Template not found |
SMS settings
Enable / disable SMS
GET /settings/sms
Example response
{ "id": 1, "company_id": 1, "enabled": true }
PUT /settings/sms
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| enabled | body | boolean | Yes | Enable/disable SMS |
Returns 200 on success, or 500 if the settings could not be loaded/saved.
SMS usage
GET /settings/sms/count
Returns the number of SMS segments used last month, this month, and in total.
Example response
{ "last": 150, "current": 320, "total": 5234 }
SMS templates
Reusable SMS templates. The text must contain a payment link placeholder
({paymentlink} / {betaallink}) or an invoice link placeholder
({viewinvoice} / {bekijkfactuur}).
List templates
GET /settings/sms/text
Example response
[
{ "id": 1, "name": "Payment reminder", "text": "Please pay via {paymentlink}" }
]
Get / create / update / delete
GET /settings/sms/text/{id} — fetch one.
POST /settings/sms/text — create.
PUT /settings/sms/text/{id} — update.
DELETE /settings/sms/text/{id} — remove.
For create and update:
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| name | body | string | Yes | Template name |
| text | body | string | Yes | SMS text (must include a payment/invoice placeholder) |
Example response
{ "id": 1, "name": "Payment reminder", "text": "Please pay via {paymentlink}" }
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | Text missing a required placeholder (create/update) |
| 404 | Template not found (get/update/delete) |
| 500 | Save/delete failed |
Attachments
Reusable file attachments that can be attached to outgoing invoice/quotation emails (linked from an invoice profile’s send settings).
List attachments
GET /settings/attachments
Example response
[
{ "id": 1, "name": "General terms", "filename": "terms.pdf" }
]
Create an attachment
POST /settings/attachments
The file is uploaded as base64 in the content field.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| name | body | string | Yes | Display name |
| filename | body | string | Yes | Original filename (with extension) |
| content | body | string | Yes | Base64-encoded file content |
Example response
{ "id": 1, "name": "General terms", "filename": "terms.pdf" }
Update an attachment
PUT /settings/attachments/{id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | integer | Yes | Attachment id |
| name | body | string | No | New display name |
| filename | body | string | No* | New filename (required if content is sent) |
| content | body | string | No* | New base64 content (required if filename is sent) |
Delete an attachment
DELETE /settings/attachments/{id}
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | Missing fields / invalid base64 (create/update) |
| 404 | Attachment not found (update/delete) |
| 500 | Storage (S3) or save/delete failure |
Invoice profiles
A profile is a sender identity / branding template: sender details, logo,
texts, layout and default email/SMS templates. A company can have multiple
profiles, one of which is the default. Profiles are referenced as the sender
when creating an invoice.
List profiles
GET /settings/profile
Returns all profiles. Each profile is a large object; key fields:
Example response (truncated)
[
{
"id": 1,
"name": "Default Profile",
"default": true,
"logo": "1.png",
"language": "nl",
"expiration": 14,
"companyName": "ACME B.V.",
"address": "Condensatorweg 54",
"postalCode": "1014AX",
"city": "Amsterdam",
"country": "NL",
"vatNumber": "NL857494454B01",
"email": "info@acme.com",
"logo_position": "left",
"sender_position": "right",
"peppol_verified": false
}
]
Get a profile
GET /settings/profile/{id}
{id} is a numeric id or the literal default. Returns the full profile object.
Create a profile
POST /settings/profile
Creates a profile with defaults. Returns the new profile id.
Example response
2
Update a profile
Profiles are updated through several focused endpoints. Each returns the profile id on success.
Name / language / expiration
PUT /settings/profile/{id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| name | body | string | No | Profile name |
| language | body | string | No | Default language, e.g. nl |
| expiration | body | integer | No | Invoice due period in days (default 14) |
Sender details
PUT /settings/profile/{id}/sender
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| companyName | body | string | Yes | Sender company name |
| address | body | string | Yes | Street address |
| postalCode | body | string | Yes | Postal code |
| city | body | string | Yes | City |
| country | body | string | Yes | ISO country code (uppercase) |
| body | string | Yes | Contact email | |
| contact | body | string | No | Contact name |
| vatNumber | body | string | No | VAT number |
| coc | body | string | No | Chamber of Commerce number |
| iban | body | string | No | IBAN |
| phone | body | string | No | Phone number |
| extra | body | string | No | Extra sender line |
| config | body | object | Yes | Which sender fields to show on the PDF |
When Peppol is enabled, sender details are validated more strictly.
Texts
PUT /settings/profile/{id}/text
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| extratext | body | string | No | Invoice top text |
| extratextbottom | body | string | No | Invoice bottom text |
| footer | body | string | No | Invoice footer |
| quotationextratext | body | string | No | Quotation top text |
| quotationextratextbottom | body | string | No | Quotation bottom text |
| quotationfooter | body | string | No | Quotation footer |
Invoice layout
PUT /settings/profile/{id}/invoice
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| logo_position | body | string | Yes | left, right or center |
| margin_global | body | integer | Yes | Global margin (mm) |
| margin_header | body | integer | Yes | Header margin (mm) |
| sender_position | body | string | Yes | left or right |
| merge_mail_attachments | body | boolean | No | Merge email attachments into the PDF |
Default send settings
PUT /settings/profile/{id}/sending
Links the templates/addresses used when sending from this profile. All optional
(integer ids or null): email_from_id, email_cc_id, email_text_id,
email_text_reminder_id, email_text_quotation_id, sms_text_id,
email_attachment_id, email_attachment_quotation_id.
Set default profile
POST /settings/profile/{id}/default
Makes the profile the company default.
Logo
GET /settings/profile/{id}.{type} — returns the logo as a base64 data URI
(e.g. {id}.png).
POST or PUT /settings/profile/{id}/logo — upload a logo as
multipart/form-data under the field file. Accepts PNG, JPEG, GIF, SVG.
DELETE /settings/profile/{id}/logo — remove the logo.
Delete a profile
DELETE /settings/profile/{id} — the default profile cannot be deleted.
Responses
Note the deliberate distinction: the read endpoints (list, get, logo)
return 401 when the profile does not exist, while the mutating endpoints
return 404.
| Status | Description |
|---|---|
| 200 | Success |
| 400 | Validation failed (create / update endpoints) |
| 401 | Profile not found (list / get / logo) |
| 403 | Default profile cannot be deleted (delete) |
| 404 | Profile not found (mutations); logo not found (GET logo) |
| 415 | Unsupported logo media type (logo upload) |
| 500 | Storage error (logo upload / profile delete) |
Address verification
Verifies a profile’s sender address by post (required for some Peppol use). Operates per profile.
Status
GET /settings/verification
Example response
[
{
"id": 1,
"profile_id": 5,
"status": "verified",
"verified_at": "2026-06-15 14:23:00",
"attempts_remaining": 5,
"sent_at": "2026-06-08 10:00:00",
"can_resend": false,
"cooldown_remaining": 345600,
"address": "Condensatorweg 54",
"postal_code": "1014 AX",
"city": "Amsterdam",
"country": "NL"
}
]
status is one of pending, sent, verified, invalid.
Initiate
POST /settings/verification/{profileId}/initiate
Starts verification for a profile (uses its VAT/address data). Returns the verification object.
Confirm
POST /settings/verification/{id}/confirm
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | integer | Yes | Verification id |
| code | body | string | Yes | 8-character code from the letter |
Example response
{
"success": true,
"message": "Address successfully verified",
"verification": { "id": 2, "profile_id": 5, "status": "verified" }
}
Resend
POST /settings/verification/{id}/resend
Resends the verification letter (subject to a cooldown).
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | Validation failed (no sender/VAT/address, empty/invalid code) |
| 404 | Profile / verification not found |
| 429 | Already pending, resend cooldown, or max attempts exceeded |
| 500 | Could not create / reset verification |
Integrations
Connections to external bookkeeping, payment and POS systems. Each integration
is addressed by a {type}:
twinfield, exact, xero, yuki, visma, mollie,
multisafepay, lightspeed, lsretail, lskseries,
untill, thenextinvoice.
Many connectors (Twinfield, Exact, Xero, Mollie, Visma, Lightspeed*) use
OAuth2: add returns a redirect URL, the provider then calls back to the
callback endpoint.
List integrations
GET /settings/integrations
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| class | query | string | No | Filter by class: bookkeeping, payment, source, crm |
Example response
[
{ "name": "twinfield", "enabled": true, "office": "123", "option": {}, "free": true, "config": {} },
{ "name": "mollie", "enabled": false, "office": null, "option": null, "free": true, "config": {} }
]
Get one integration
GET /settings/integrations/{type}
Returns the integration’s current state (disabled state if not configured).
Active bookkeeping integration
GET /settings/integrations/check
Returns the company’s active bookkeeping integration (may be inherited from a
linked accountant). 404 if none is active.
Add / connect an integration
POST /settings/integrations/{type}
Body parameters depend on the connector. For OAuth2 connectors the body is
usually empty (and may take an optional option object); credential-based
connectors take their own fields, e.g.:
- untill:
office(POS IP),option[username],option[password] - yuki:
office,access_token - multisafepay:
api_key
Example response (OAuth2 connector)
{ "done": false, "redirect": "https://login.twinfield.com/oauth/...", "popup": true, "openConfig": true }
For credential connectors done is typically true and no redirect is needed.
OAuth2 callback
GET /settings/integrations/callback/{type}
The redirect target for OAuth2 connectors; not called directly.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| code | query | string | Yes | OAuth authorization code |
| state | query | string | No | OAuth state |
Update an integration
PUT /settings/integrations/{type}
Saves connector configuration / mappings, e.g. ledger & VAT-code mappings for
bookkeeping connectors (option[ledgernumbers], option[vatcodes], office),
or product/category mappings for POS connectors. Returns a confirmation message
or the updated integration.
Test an integration
GET /settings/integrations/{type}/test
Example response
{ "ok": true }
Remove an integration
DELETE /settings/integrations/{type}
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | OAuth / mapping / config error (callback, update) |
| 404 | Unknown type, or integration not configured/active |
| 409 | API token already exists (add thenextinvoice) |
| 500 | Setup, save, or remove failure |
Company users
Manage which users have access to the current company and their role.
List users
GET /settings/user
Example response
[
{ "id": 1, "email": "owner@example.com", "name": "John Owner", "role": "owner", "language": "nl" },
{ "id": 2, "email": "staff@example.com", "name": "Jane Staff", "role": "employee", "language": "en" }
]
Add a user
POST /settings/user
Grants a user access to the company. If the email does not belong to an existing
user, a new account is created (then password, name and language are
required).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| body | string | Yes | User email | |
| role | body | string | Yes | owner or employee |
| password | body | string | No* | Required when creating a new account |
| name | body | string | No* | Required when creating a new account |
| language | body | string | No* | Required when creating a new account |
Example response
{ "id": 3, "email": "new@example.com", "name": "New User", "language": "nl" }
Update a user’s role
PUT /settings/user/{id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | integer | Yes | User id |
| role | body | string | Yes | owner or employee |
Remove a user
DELETE /settings/user/{id}
Removes the user from the company. The sole remaining owner cannot be removed.
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | Validation failed |
| 403 | User already in company / sole owner |
| 404 | User not in company |
Two-factor authentication
TOTP-based 2FA for the current user.
Status
GET /settings/2fa
Example response
{ "status": "enabled" }
status is one of disabled, connecting, enabled.
Begin enrollment
POST /settings/2fa
Generates a TOTP secret and provisioning URI to show as a QR code.
Example response
{
"secret": "abcd efgh ijkl mnop",
"url": "otpauth://totp/TheNextInvoice:user@example.com?secret=ABCD...&issuer=TheNextInvoice"
}
Finish enrollment
PUT /settings/2fa
Confirms enrollment with a code from the authenticator app.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| code | body | integer | Yes | TOTP code |
Returns "Connected!" on success.
Disable
DELETE /settings/2fa
Returns "TOTP disabled".
Recovery codes
POST /settings/2fa/recovery
Generates a fresh set of one-time recovery codes (invalidating previous ones).
Example response
["abcd-efgh-ijkl", "mnop-qrst-uvwx", "yzab-cdef-ghij"]
Responses
| Status | Description |
|---|---|
| 200 | Success |
| 400 | 2FA not enabled, not connecting, or invalid code |
| 409 | 2FA already enabled (begin / finish) |
| 500 | Save error |
Notifications
List Notifications
GET /notifications
Retrieves all notifications for the current company.
Example Response
[
{
"id": 42,
"company_id": 1,
"message": "Invoice 201800013 has been paid",
"data": {
"invoice_id": 12552
},
"time": "2019-02-28 21:30:00"
}
]
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
Dismiss Notifications
DELETE /notifications
Dismisses all notifications for the current company.
Example Response
"Notifications have been removed"
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |
Retrieving stream token
GET /token/lilium
Requests a token to authenticate with the notification stream service.
Example response
"thisisatoken"
Responses
| Status | Description |
|---|---|
| 200 | Request succesful |