API reference
Invoices
Invoices with their lines and payments: draft them, update them while they are still drafts, send them, cancel them and record payments.
Endpoints
-
GET
/invoicesList invoices -
GET
/invoices/{invoice}Get an invoice -
POST
/invoicesCreate an invoice -
PATCH
/invoices/{invoice}Update an invoice -
POST
/invoices/{invoice}/sendSend an invoice -
POST
/invoices/{invoice}/cancelCancel an invoice -
POST
/invoices/{invoice}/paymentsRecord a payment -
DELETE
/invoices/{invoice}Delete an invoice
List invoices
/api/v1/invoices
A list of invoices, newest first, with their lines and recorded payments. Filter by status, customer or change date. Use filter[status]=paid together with filter[updated_since] to pick up what has been paid since your last sync.
- Scope
-
invoices.read— Read invoices, with their lines, amounts, payments and the customer details on them - Required feature
invoices
Query parameters
| Name | Type | Description |
|---|---|---|
limit
|
integer | Number of results per page. from 1 to 100 · default: 50 |
cursor
|
string | The next_cursor or prev_cursor from meta of the previous response. |
sort
|
string | Sort by created_at or updated_at; a leading minus sign sorts descending. one of: -created_at, created_at, -updated_at, updated_at · default: -created_at |
filter[status]
|
string | Only invoices with this status, for example paid or overdue. one of: draft, sent, viewed, paid, partial, overdue, cancelled, refunded |
filter[customer_id]
|
string (uuid) | Only what belongs to this customer. |
filter[updated_since]
|
string (date-time) | Only what changed since this moment: ISO 8601 with a time zone, for example 2026-09-14T10:15:00Z. Useful for synchronising. |
Example request
curl "https://app.klantly.com/api/v1/invoices?filter[status]=paid&sort=-updated_at" \
-H "Authorization: Bearer $KLANTLY_API_KEY"$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('GET', 'invoices', [
'query' => [
'filter[status]' => 'paid',
'sort' => '-updated_at',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/invoices?filter[status]=paid&sort=-updated_at', {
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
const { data } = await response.json();import os
import requests
response = requests.get(
"https://app.klantly.com/api/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
params={
"filter[status]": "paid",
"sort": "-updated_at"
},
)
data = response.json()["data"]Response 200
The response is a list with cursor pagination: data contains the objects, meta the pagination.
{
"data": [
{
"object": "invoice",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dd",
"number": "FAC-00042",
"status": "sent",
"title": "Veranda 400 × 300",
"description": null,
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"type": "business",
"name": "De Vries Bouw",
"email": "jan@example.com",
"phone": "+31 6 12345678",
"address": "Dorpsstraat 1",
"postal_code": "3511 AB",
"city": "Utrecht",
"country": "NL",
"company_name": "De Vries Bouw",
"vat_number": null,
"coc_number": null
},
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"deal_id": null,
"language": "nl",
"currency": "EUR",
"invoice_date": "2026-09-16",
"due_date": "2026-10-16",
"payment_term_days": 30,
"is_term_invoice": false,
"term_percentage": null,
"terms_conditions": null,
"notes": null,
"outro_text": null,
"hide_line_amounts": false,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"subtotal": "2450.00",
"tax_amount": "514.50",
"total": "2964.50",
"amount_paid": "0.00",
"amount_due": "2964.50",
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"cancelled_at": null,
"items": [
{
"object": "invoice_item",
"id": "9d3f87ac-d2b4-4d9b-8aef-f4a2b3c4d5ee",
"type": "product",
"name": "Veranda",
"description": null,
"sku": null,
"quantity": "1.00",
"unit": "stuk",
"unit_price": "2450.00",
"unit_price_incl": null,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"line_total": "2450.00",
"line_total_incl": null,
"is_taxable": true,
"tax_rate": "21.00"
}
],
"payments": [
{
"object": "invoice_payment",
"id": "9d3f88cd-e3c5-4eac-9bf0-a5b3c4d5e6ff",
"amount": "2964.50",
"type": "full",
"status": "paid",
"payment_method": "bank_transfer",
"description": null,
"paid_at": "2026-09-16T10:15:00Z",
"created_at": "2026-09-16T10:15:00Z"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
],
"meta": {
"limit": 50,
"next_cursor": "eyJpZCI6IjlkM2Y2YzFlIn0",
"prev_cursor": null
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
422
validation_failed— The input is invalid.
Get an invoice
/api/v1/invoices/{invoice}
One invoice by id, with its lines, amounts and payments. The response carries an ETag you can send back in If-Match when updating.
- Scope
-
invoices.read— Read invoices, with their lines, amounts, payments and the customer details on them - Required feature
invoices
Path parameters
| Name | Type | Description |
|---|---|---|
invoice required |
string (uuid) | The id (UUID) of the invoice. |
Example request
curl "https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
-H "Authorization: Bearer $KLANTLY_API_KEY"$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('GET', 'invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
const { data } = await response.json();import os
import requests
response = requests.get(
"https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Response 200
{
"data": {
"object": "invoice",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dd",
"number": "FAC-00042",
"status": "sent",
"title": "Veranda 400 × 300",
"description": null,
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"type": "business",
"name": "De Vries Bouw",
"email": "jan@example.com",
"phone": "+31 6 12345678",
"address": "Dorpsstraat 1",
"postal_code": "3511 AB",
"city": "Utrecht",
"country": "NL",
"company_name": "De Vries Bouw",
"vat_number": null,
"coc_number": null
},
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"deal_id": null,
"language": "nl",
"currency": "EUR",
"invoice_date": "2026-09-16",
"due_date": "2026-10-16",
"payment_term_days": 30,
"is_term_invoice": false,
"term_percentage": null,
"terms_conditions": null,
"notes": null,
"outro_text": null,
"hide_line_amounts": false,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"subtotal": "2450.00",
"tax_amount": "514.50",
"total": "2964.50",
"amount_paid": "0.00",
"amount_due": "2964.50",
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"cancelled_at": null,
"items": [
{
"object": "invoice_item",
"id": "9d3f87ac-d2b4-4d9b-8aef-f4a2b3c4d5ee",
"type": "product",
"name": "Veranda",
"description": null,
"sku": null,
"quantity": "1.00",
"unit": "stuk",
"unit_price": "2450.00",
"unit_price_incl": null,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"line_total": "2450.00",
"line_total_incl": null,
"is_taxable": true,
"tax_rate": "21.00"
}
],
"payments": [
{
"object": "invoice_payment",
"id": "9d3f88cd-e3c5-4eac-9bf0-a5b3c4d5e6ff",
"amount": "2964.50",
"type": "full",
"status": "paid",
"payment_method": "bank_transfer",
"description": null,
"paid_at": "2026-09-16T10:15:00Z",
"created_at": "2026-09-16T10:15:00Z"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
404
not_found— Not found.
Create an invoice
/api/v1/invoices
Creates an invoice for a customer, always as a draft. Klantly assigns the number and calculates the totals; name, address and contact details come from the customer. Use quote_id to link it to a quote.
- Scope
-
invoices.write— Create and update invoices (drafts only), cancel them and record payments - Required feature
invoices
Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.
Body (JSON)
| Field | Type | Description |
|---|---|---|
customer_id
required
|
string (uuid) | The customer. Required when creating; name, address and contact details come from the customer. |
title
required
|
string | Title of the invoice. Required when creating. at most 255 characters |
items
required
|
array | The lines (up to 200). When sending: a list with name (required) per line and optionally type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate and is_taxable. They replace all existing lines. |
quote_id
optional
|
string (uuid) | The quote this invoice came from, or null. can be empty (null) |
description
optional
|
string | Short description; shown above the lines. can be empty (null) · at most 20000 characters |
language
optional
|
string | Language of the invoice: nl, en, de or fr. one of: nl, en, de, fr |
invoice_date
optional
|
string (date) | Invoice date (YYYY-MM-DD). |
due_date
optional
|
string (date) | Due date (YYYY-MM-DD). Leave it out and it is the invoice date plus the payment term. |
payment_term_days
optional
|
integer | Payment term in days. Without one, the company default applies. from 0 to 365 |
terms_conditions
optional
|
string | Terms shown on the invoice. can be empty (null) · at most 20000 characters |
notes
optional
|
string | Notes for the customer on the invoice. When recording a payment: a note about that payment. can be empty (null) · at most 20000 characters |
outro_text
optional
|
string | Closing text below the lines. can be empty (null) · at most 20000 characters |
hide_line_amounts
optional
|
boolean | Hides the per-line amounts on the invoice; the customer only sees the total. |
discount_percentage
optional
|
number | Discount on the total, as a percentage. from 0 to 100 |
discount_amount
optional
|
number | Discount on the total, as an amount. from 0 to 9999999 |
discount_description
optional
|
string | Why the discount was given; shown on the invoice. can be empty (null) · at most 500 characters |
Example request
curl -X POST "https://app.klantly.com/api/v1/invoices" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
-d '{
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"title": "Veranda 400 × 300",
"items": [
{
"name": "Veranda",
"quantity": 1,
"unit_price": "2450.00",
"tax_rate": "21.00"
}
]
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'invoices', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
'json' => [
'customer_id' => '9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70',
'title' => 'Veranda 400 × 300',
'items' => [
0 => [
'name' => 'Veranda',
'quantity' => 1,
'unit_price' => '2450.00',
'tax_rate' => '21.00',
],
],
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
},
body: JSON.stringify({
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"title": "Veranda 400 × 300",
"items": [
{
"name": "Veranda",
"quantity": 1,
"unit_price": "2450.00",
"tax_rate": "21.00"
}
]
}),
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
json={
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"title": "Veranda 400 × 300",
"items": [
{
"name": "Veranda",
"quantity": 1,
"unit_price": "2450.00",
"tax_rate": "21.00"
}
]
},
)
data = response.json()["data"]Response 201
{
"data": {
"object": "invoice",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dd",
"number": "FAC-00042",
"status": "sent",
"title": "Veranda 400 × 300",
"description": null,
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"type": "business",
"name": "De Vries Bouw",
"email": "jan@example.com",
"phone": "+31 6 12345678",
"address": "Dorpsstraat 1",
"postal_code": "3511 AB",
"city": "Utrecht",
"country": "NL",
"company_name": "De Vries Bouw",
"vat_number": null,
"coc_number": null
},
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"deal_id": null,
"language": "nl",
"currency": "EUR",
"invoice_date": "2026-09-16",
"due_date": "2026-10-16",
"payment_term_days": 30,
"is_term_invoice": false,
"term_percentage": null,
"terms_conditions": null,
"notes": null,
"outro_text": null,
"hide_line_amounts": false,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"subtotal": "2450.00",
"tax_amount": "514.50",
"total": "2964.50",
"amount_paid": "0.00",
"amount_due": "2964.50",
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"cancelled_at": null,
"items": [
{
"object": "invoice_item",
"id": "9d3f87ac-d2b4-4d9b-8aef-f4a2b3c4d5ee",
"type": "product",
"name": "Veranda",
"description": null,
"sku": null,
"quantity": "1.00",
"unit": "stuk",
"unit_price": "2450.00",
"unit_price_incl": null,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"line_total": "2450.00",
"line_total_incl": null,
"is_taxable": true,
"tax_rate": "21.00"
}
],
"payments": [
{
"object": "invoice_payment",
"id": "9d3f88cd-e3c5-4eac-9bf0-a5b3c4d5e6ff",
"amount": "2964.50",
"type": "full",
"status": "paid",
"payment_method": "bank_transfer",
"description": null,
"paid_at": "2026-09-16T10:15:00Z",
"created_at": "2026-09-16T10:15:00Z"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
422
validation_failed— The input is invalid. -
422
unknown_field— The input contains an unknown field. -
415
unsupported_media_type— This format is not supported. -
413
payload_too_large— The request body is too large. -
403
limit_reached— The subscription limit has been reached. -
422
idempotency_key_reused— This Idempotency-Key was already used for a different request. -
409
idempotency_in_progress— A request with this Idempotency-Key is still in progress.
Update an invoice
/api/v1/invoices/{invoice}
Changes only the fields you send. items replaces all lines as a whole. Only a draft can still change: once the invoice has been sent it sits in the customer's bookkeeping, and you get a 409.
- Scope
-
invoices.write— Create and update invoices (drafts only), cancel them and record payments - Required feature
invoices
Send the ETag in If-Match and you will never accidentally overwrite a newer version.
Path parameters
| Name | Type | Description |
|---|---|---|
invoice required |
string (uuid) | The id (UUID) of the invoice. |
Body (JSON)
| Field | Type | Description |
|---|---|---|
title
optional
|
string | Title of the invoice. Required when creating. at most 255 characters |
description
optional
|
string | Short description; shown above the lines. can be empty (null) · at most 20000 characters |
language
optional
|
string | Language of the invoice: nl, en, de or fr. one of: nl, en, de, fr |
invoice_date
optional
|
string (date) | Invoice date (YYYY-MM-DD). |
due_date
optional
|
string (date) | Due date (YYYY-MM-DD). Leave it out and it is the invoice date plus the payment term. |
payment_term_days
optional
|
integer | Payment term in days. Without one, the company default applies. from 0 to 365 |
terms_conditions
optional
|
string | Terms shown on the invoice. can be empty (null) · at most 20000 characters |
notes
optional
|
string | Notes for the customer on the invoice. When recording a payment: a note about that payment. can be empty (null) · at most 20000 characters |
outro_text
optional
|
string | Closing text below the lines. can be empty (null) · at most 20000 characters |
hide_line_amounts
optional
|
boolean | Hides the per-line amounts on the invoice; the customer only sees the total. |
discount_percentage
optional
|
number | Discount on the total, as a percentage. from 0 to 100 |
discount_amount
optional
|
number | Discount on the total, as an amount. from 0 to 9999999 |
discount_description
optional
|
string | Why the discount was given; shown on the invoice. can be empty (null) · at most 500 characters |
items
optional
|
array | The lines (up to 200). When sending: a list with name (required) per line and optionally type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate and is_taxable. They replace all existing lines. |
Example request
curl -X PATCH "https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"notes": "Graag betalen binnen 14 dagen."
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('PATCH', 'invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', [
'json' => [
'notes' => 'Graag betalen binnen 14 dagen.',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"notes": "Graag betalen binnen 14 dagen."
}),
});
const { data } = await response.json();import os
import requests
response = requests.patch(
"https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
json={
"notes": "Graag betalen binnen 14 dagen."
},
)
data = response.json()["data"]Response 200
{
"data": {
"object": "invoice",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dd",
"number": "FAC-00042",
"status": "sent",
"title": "Veranda 400 × 300",
"description": null,
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"type": "business",
"name": "De Vries Bouw",
"email": "jan@example.com",
"phone": "+31 6 12345678",
"address": "Dorpsstraat 1",
"postal_code": "3511 AB",
"city": "Utrecht",
"country": "NL",
"company_name": "De Vries Bouw",
"vat_number": null,
"coc_number": null
},
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"deal_id": null,
"language": "nl",
"currency": "EUR",
"invoice_date": "2026-09-16",
"due_date": "2026-10-16",
"payment_term_days": 30,
"is_term_invoice": false,
"term_percentage": null,
"terms_conditions": null,
"notes": null,
"outro_text": null,
"hide_line_amounts": false,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"subtotal": "2450.00",
"tax_amount": "514.50",
"total": "2964.50",
"amount_paid": "0.00",
"amount_due": "2964.50",
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"cancelled_at": null,
"items": [
{
"object": "invoice_item",
"id": "9d3f87ac-d2b4-4d9b-8aef-f4a2b3c4d5ee",
"type": "product",
"name": "Veranda",
"description": null,
"sku": null,
"quantity": "1.00",
"unit": "stuk",
"unit_price": "2450.00",
"unit_price_incl": null,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"line_total": "2450.00",
"line_total_incl": null,
"is_taxable": true,
"tax_rate": "21.00"
}
],
"payments": [
{
"object": "invoice_payment",
"id": "9d3f88cd-e3c5-4eac-9bf0-a5b3c4d5e6ff",
"amount": "2964.50",
"type": "full",
"status": "paid",
"payment_method": "bank_transfer",
"description": null,
"paid_at": "2026-09-16T10:15:00Z",
"created_at": "2026-09-16T10:15:00Z"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
422
validation_failed— The input is invalid. -
422
unknown_field— The input contains an unknown field. -
415
unsupported_media_type— This format is not supported. -
413
payload_too_large— The request body is too large. -
404
not_found— Not found. -
412
precondition_failed— The record has been changed in the meantime. -
409
invalid_state_transition— This action is not possible in the current status.
Send an invoice
/api/v1/invoices/{invoice}/send
Emails the invoice to the customer using the company's notification template and sets the status to sent. Sending again counts as a reminder: reminder_count goes up.
- Scope
-
invoices.send— Email invoices to customers - Required feature
invoices
Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.
Path parameters
| Name | Type | Description |
|---|---|---|
invoice required |
string (uuid) | The id (UUID) of the invoice. |
Body (JSON)
| Field | Type | Description |
|---|---|---|
message
optional
|
string | Personal message in the email to the customer. can be empty (null) · at most 5000 characters |
Example request
curl -X POST "https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/send" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
-d '{
"message": "Bijgaand de factuur voor de geplaatste veranda."
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/send', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
'json' => [
'message' => 'Bijgaand de factuur voor de geplaatste veranda.',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
},
body: JSON.stringify({
"message": "Bijgaand de factuur voor de geplaatste veranda."
}),
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/send",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
json={
"message": "Bijgaand de factuur voor de geplaatste veranda."
},
)
data = response.json()["data"]Response 200
{
"data": {
"object": "invoice",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dd",
"number": "FAC-00042",
"status": "sent",
"title": "Veranda 400 × 300",
"description": null,
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"type": "business",
"name": "De Vries Bouw",
"email": "jan@example.com",
"phone": "+31 6 12345678",
"address": "Dorpsstraat 1",
"postal_code": "3511 AB",
"city": "Utrecht",
"country": "NL",
"company_name": "De Vries Bouw",
"vat_number": null,
"coc_number": null
},
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"deal_id": null,
"language": "nl",
"currency": "EUR",
"invoice_date": "2026-09-16",
"due_date": "2026-10-16",
"payment_term_days": 30,
"is_term_invoice": false,
"term_percentage": null,
"terms_conditions": null,
"notes": null,
"outro_text": null,
"hide_line_amounts": false,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"subtotal": "2450.00",
"tax_amount": "514.50",
"total": "2964.50",
"amount_paid": "0.00",
"amount_due": "2964.50",
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"cancelled_at": null,
"items": [
{
"object": "invoice_item",
"id": "9d3f87ac-d2b4-4d9b-8aef-f4a2b3c4d5ee",
"type": "product",
"name": "Veranda",
"description": null,
"sku": null,
"quantity": "1.00",
"unit": "stuk",
"unit_price": "2450.00",
"unit_price_incl": null,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"line_total": "2450.00",
"line_total_incl": null,
"is_taxable": true,
"tax_rate": "21.00"
}
],
"payments": [
{
"object": "invoice_payment",
"id": "9d3f88cd-e3c5-4eac-9bf0-a5b3c4d5e6ff",
"amount": "2964.50",
"type": "full",
"status": "paid",
"payment_method": "bank_transfer",
"description": null,
"paid_at": "2026-09-16T10:15:00Z",
"created_at": "2026-09-16T10:15:00Z"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
422
validation_failed— The input is invalid. -
422
unknown_field— The input contains an unknown field. -
415
unsupported_media_type— This format is not supported. -
413
payload_too_large— The request body is too large. -
404
not_found— Not found. -
403
forbidden— Access denied. -
409
invalid_state_transition— This action is not possible in the current status. -
422
idempotency_key_reused— This Idempotency-Key was already used for a different request. -
409
idempotency_in_progress— A request with this Idempotency-Key is still in progress.
Cancel an invoice
/api/v1/invoices/{invoice}/cancel
Sets the invoice to cancelled. A paid invoice cannot be cancelled; create a credit note for that.
- Scope
-
invoices.write— Create and update invoices (drafts only), cancel them and record payments - Required feature
invoices
Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.
Path parameters
| Name | Type | Description |
|---|---|---|
invoice required |
string (uuid) | The id (UUID) of the invoice. |
Example request
curl -X POST "https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/cancel" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f"$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/cancel', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/cancel', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
},
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/cancel",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
)
data = response.json()["data"]Response 200
{
"data": {
"object": "invoice",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dd",
"number": "FAC-00042",
"status": "sent",
"title": "Veranda 400 × 300",
"description": null,
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"type": "business",
"name": "De Vries Bouw",
"email": "jan@example.com",
"phone": "+31 6 12345678",
"address": "Dorpsstraat 1",
"postal_code": "3511 AB",
"city": "Utrecht",
"country": "NL",
"company_name": "De Vries Bouw",
"vat_number": null,
"coc_number": null
},
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"deal_id": null,
"language": "nl",
"currency": "EUR",
"invoice_date": "2026-09-16",
"due_date": "2026-10-16",
"payment_term_days": 30,
"is_term_invoice": false,
"term_percentage": null,
"terms_conditions": null,
"notes": null,
"outro_text": null,
"hide_line_amounts": false,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"subtotal": "2450.00",
"tax_amount": "514.50",
"total": "2964.50",
"amount_paid": "0.00",
"amount_due": "2964.50",
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"cancelled_at": null,
"items": [
{
"object": "invoice_item",
"id": "9d3f87ac-d2b4-4d9b-8aef-f4a2b3c4d5ee",
"type": "product",
"name": "Veranda",
"description": null,
"sku": null,
"quantity": "1.00",
"unit": "stuk",
"unit_price": "2450.00",
"unit_price_incl": null,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"line_total": "2450.00",
"line_total_incl": null,
"is_taxable": true,
"tax_rate": "21.00"
}
],
"payments": [
{
"object": "invoice_payment",
"id": "9d3f88cd-e3c5-4eac-9bf0-a5b3c4d5e6ff",
"amount": "2964.50",
"type": "full",
"status": "paid",
"payment_method": "bank_transfer",
"description": null,
"paid_at": "2026-09-16T10:15:00Z",
"created_at": "2026-09-16T10:15:00Z"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
404
not_found— Not found. -
409
invalid_state_transition— This action is not possible in the current status. -
422
idempotency_key_reused— This Idempotency-Key was already used for a different request. -
409
idempotency_in_progress— A request with this Idempotency-Key is still in progress.
Record a payment
/api/v1/invoices/{invoice}/payments
Records a payment that came in outside Klantly, for example a bank transfer. The amount can never be higher than what is still due. If the invoice is settled in full it moves to paid and the invoice.paid event follows.
- Scope
-
invoices.write— Create and update invoices (drafts only), cancel them and record payments - Required feature
invoices
Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.
Path parameters
| Name | Type | Description |
|---|---|---|
invoice required |
string (uuid) | The id (UUID) of the invoice. |
Body (JSON)
| Field | Type | Description |
|---|---|---|
amount
required
|
number | The amount of the payment you are recording. Never more than what is outstanding. from 0 to 9999999 |
payment_method
optional
|
string | How it was paid: bank_transfer, cash, card, ideal, credit_card, paypal, bancontact or other. one of: bank_transfer, cash, card, ideal, credit_card, paypal, bancontact, other |
reference
optional
|
string | Your own reference for the payment, for example the bank statement or transaction id. can be empty (null) · at most 255 characters |
notes
optional
|
string | Notes for the customer on the invoice. When recording a payment: a note about that payment. can be empty (null) · at most 1000 characters |
Example request
curl -X POST "https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/payments" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
-d '{
"amount": "2964.50",
"payment_method": "bank_transfer",
"reference": "NL02ABNA0123456789"
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/payments', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
'json' => [
'amount' => '2964.50',
'payment_method' => 'bank_transfer',
'reference' => 'NL02ABNA0123456789',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
},
body: JSON.stringify({
"amount": "2964.50",
"payment_method": "bank_transfer",
"reference": "NL02ABNA0123456789"
}),
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/payments",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
json={
"amount": "2964.50",
"payment_method": "bank_transfer",
"reference": "NL02ABNA0123456789"
},
)
data = response.json()["data"]Response 200
{
"data": {
"object": "invoice",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dd",
"number": "FAC-00042",
"status": "sent",
"title": "Veranda 400 × 300",
"description": null,
"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
"customer": {
"type": "business",
"name": "De Vries Bouw",
"email": "jan@example.com",
"phone": "+31 6 12345678",
"address": "Dorpsstraat 1",
"postal_code": "3511 AB",
"city": "Utrecht",
"country": "NL",
"company_name": "De Vries Bouw",
"vat_number": null,
"coc_number": null
},
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"deal_id": null,
"language": "nl",
"currency": "EUR",
"invoice_date": "2026-09-16",
"due_date": "2026-10-16",
"payment_term_days": 30,
"is_term_invoice": false,
"term_percentage": null,
"terms_conditions": null,
"notes": null,
"outro_text": null,
"hide_line_amounts": false,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"subtotal": "2450.00",
"tax_amount": "514.50",
"total": "2964.50",
"amount_paid": "0.00",
"amount_due": "2964.50",
"sent_at": null,
"viewed_at": null,
"paid_at": null,
"cancelled_at": null,
"items": [
{
"object": "invoice_item",
"id": "9d3f87ac-d2b4-4d9b-8aef-f4a2b3c4d5ee",
"type": "product",
"name": "Veranda",
"description": null,
"sku": null,
"quantity": "1.00",
"unit": "stuk",
"unit_price": "2450.00",
"unit_price_incl": null,
"discount_percentage": "0.00",
"discount_amount": "0.00",
"discount_description": null,
"line_total": "2450.00",
"line_total_incl": null,
"is_taxable": true,
"tax_rate": "21.00"
}
],
"payments": [
{
"object": "invoice_payment",
"id": "9d3f88cd-e3c5-4eac-9bf0-a5b3c4d5e6ff",
"amount": "2964.50",
"type": "full",
"status": "paid",
"payment_method": "bank_transfer",
"description": null,
"paid_at": "2026-09-16T10:15:00Z",
"created_at": "2026-09-16T10:15:00Z"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
422
validation_failed— The input is invalid. -
422
unknown_field— The input contains an unknown field. -
415
unsupported_media_type— This format is not supported. -
413
payload_too_large— The request body is too large. -
404
not_found— Not found. -
409
invalid_state_transition— This action is not possible in the current status. -
422
idempotency_key_reused— This Idempotency-Key was already used for a different request. -
409
idempotency_in_progress— A request with this Idempotency-Key is still in progress.
Delete an invoice
/api/v1/invoices/{invoice}
Deletes the invoice. Only a draft can go: an invoice that has been sent stays.
- Scope
-
invoices.delete— Delete draft invoices - Required feature
invoices
Path parameters
| Name | Type | Description |
|---|---|---|
invoice required |
string (uuid) | The id (UUID) of the invoice. |
Example request
curl -X DELETE "https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
-H "Authorization: Bearer $KLANTLY_API_KEY"$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('DELETE', 'invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
const { data } = await response.json();import os
import requests
response = requests.delete(
"https://app.klantly.com/api/v1/invoices/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Response 200
{
"data": {
"object": "note",
"id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
"deleted": true
}
}Possible errors
-
403
insufficient_scope— This API key has no access to this action. -
404
not_found— Not found. -
409
invalid_state_transition— This action is not possible in the current status.
The object
All fields are always present; a field without a value is null.
| Field | Type | Description |
|---|---|---|
object |
string | Always "invoice". |
id |
string (uuid) | Unique id (UUID). |
number |
string | Invoice number, for example FAC-00042; Klantly assigns it. |
status |
string | draft, sent, viewed, paid, partial (partly paid), overdue, cancelled or refunded. one of: draft, sent, viewed, paid, partial, overdue, cancelled, refunded |
title |
string | Title of the invoice. Required when creating. can be empty (null) |
description |
string | Short description; shown above the lines. can be empty (null) |
customer_id |
string (uuid) | The customer. Required when creating; name, address and contact details come from the customer. can be empty (null) |
customer |
object | The customer details on the invoice, as they were when it was created. |
customer.type |
string | individual or business. can be empty (null) |
customer.name |
string | Name; for a business the company name. can be empty (null) |
customer.email |
string | Email address. can be empty (null) |
customer.phone |
string | Phone number. can be empty (null) |
customer.address |
string | Street and house number. can be empty (null) |
customer.postal_code |
string | Postal code. can be empty (null) |
customer.city |
string | City. can be empty (null) |
customer.country |
string | Country. can be empty (null) |
customer.company_name |
string | Company name. can be empty (null) |
customer.vat_number |
string | VAT number. can be empty (null) |
customer.coc_number |
string | Chamber of commerce number. can be empty (null) |
quote_id |
string (uuid) | The quote this invoice came from, or null. can be empty (null) |
deal_id |
string (uuid) | The deal on the pipeline board this invoice belongs to, or null. can be empty (null) |
language |
string | Language of the invoice: nl, en, de or fr. one of: nl, en, de, fr |
currency |
string | Always "EUR". |
invoice_date |
string (date) | Invoice date (YYYY-MM-DD). can be empty (null) |
due_date |
string (date) | Due date (YYYY-MM-DD). Leave it out and it is the invoice date plus the payment term. can be empty (null) |
payment_term_days |
integer | Payment term in days. Without one, the company default applies. can be empty (null) |
is_term_invoice |
boolean | Whether this is a partial invoice for part of a quote. |
term_percentage |
string | Which share of the quote this partial invoice covers, as a percentage; otherwise null. can be empty (null) |
terms_conditions |
string | Terms shown on the invoice. can be empty (null) |
notes |
string | Notes for the customer on the invoice. When recording a payment: a note about that payment. can be empty (null) |
outro_text |
string | Closing text below the lines. can be empty (null) |
hide_line_amounts |
boolean | Hides the per-line amounts on the invoice; the customer only sees the total. |
discount_percentage |
string | Discount on the total, as a percentage. |
discount_amount |
string | Discount on the total, as an amount. |
discount_description |
string | Why the discount was given; shown on the invoice. can be empty (null) |
subtotal |
string | Total excluding VAT, as a string with two decimals. |
tax_amount |
string | VAT amount. |
total |
string | Total including VAT. |
amount_paid |
string | What has been paid so far. |
amount_due |
string | What is still outstanding. |
sent_at |
string (date-time) | When the invoice was sent to the customer. can be empty (null) |
viewed_at |
string (date-time) | When the customer opened it. can be empty (null) |
paid_at |
string (date-time) | When the invoice was settled in full. can be empty (null) |
cancelled_at |
string (date-time) | When the invoice was cancelled. can be empty (null) |
items |
array<object> | The lines (up to 200). When sending: a list with name (required) per line and optionally type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate and is_taxable. They replace all existing lines. |
items.object |
string | Always "invoice_item". |
items.id |
string (uuid) | Unique id of the line (UUID). |
items.type |
string | product, service, expense or discount. one of: product, service, expense, discount |
items.name |
string | Name of the line. |
items.description |
string | Description below the line. can be empty (null) |
items.sku |
string | Article number. can be empty (null) |
items.quantity |
string | Quantity. |
items.unit |
string | Unit, for example piece or hour. can be empty (null) |
items.unit_price |
string | Price per unit, excluding VAT. |
items.unit_price_incl |
string | Unit price including VAT, as entered. When set, the line is calculated from this amount (unit_price and line_total are derived from it), so the total matches the entered price to the cent. null = the line is calculated from unit_price. can be empty (null) |
items.discount_percentage |
string | Discount on this line, as a percentage. |
items.discount_amount |
string | Discount on this line, as an amount. |
items.discount_description |
string | Why the discount on this line was given. can be empty (null) |
items.line_total |
string | Line total excluding VAT, after discount. |
items.line_total_incl |
string | Line total including VAT, after discount. Only set when the line is calculated from unit_price_incl; otherwise null. can be empty (null) |
items.is_taxable |
boolean | Whether this line counts towards VAT. |
items.tax_rate |
string | VAT rate of this line, as a percentage. |
payments |
array<object> | The payments recorded on this invoice, oldest first. |
payments.object |
string | Always "invoice_payment". |
payments.id |
string (uuid) | Unique id of the payment. |
payments.amount |
string | The amount paid. |
payments.type |
string | full, deposit or partial. one of: full, deposit, partial |
payments.status |
string | open, pending, paid, failed, expired, cancelled or refunded. one of: open, pending, paid, failed, expired, cancelled, refunded |
payments.payment_method |
string | How it was paid, for example bank_transfer or ideal. can be empty (null) |
payments.description |
string | Note about the payment. can be empty (null) |
payments.paid_at |
string (date-time) | When the payment came in. can be empty (null) |
payments.created_at |
string (date-time) | When the payment was recorded. |
created_at |
string (date-time) | When the invoice was created. |
updated_at |
string (date-time) | When the invoice was last changed. |