API-Referenz
Angebote
Angebote mit ihren Positionen und Anhängen: erstellen, ändern solange sie Entwurf sind, per E-Mail an den Kunden senden, das Ergebnis festhalten, das PDF abrufen und eine Rechnung daraus erstellen.
Endpunkte
-
GET
/quotesAngebote abrufen -
GET
/quotes/{quote}Angebot abrufen -
POST
/quotesAngebot erstellen -
PATCH
/quotes/{quote}Angebot ändern -
POST
/quotes/{quote}/sendAngebot senden -
POST
/quotes/{quote}/acceptAngebot annehmen -
POST
/quotes/{quote}/rejectAngebot ablehnen -
GET
/quotes/{quote}/pdfAngebot als PDF -
POST
/quotes/{quote}/invoiceAngebot in Rechnung umwandeln -
DELETE
/quotes/{quote}Angebot löschen
Angebote abrufen
/api/v1/quotes
Eine Liste der Angebote, neueste zuerst, mit ihren Positionen. Filtern nach Status, Kunde oder Änderungsdatum. Mit filter[status]=sent und filter[updated_since] holen Sie ab, was seit Ihrer letzten Synchronisierung gesendet wurde.
- Scope
-
quotes.read— Angebote lesen, mit Positionen, Beträgen und den Kundendaten darauf - Erforderliche Funktion
quotes
Query-Parameter
| Name | Typ | Beschreibung |
|---|---|---|
limit
|
integer | Anzahl der Ergebnisse pro Seite. von 1 bis 100 · Standard: 50 |
cursor
|
string | Der next_cursor oder prev_cursor aus meta der vorherigen Antwort. |
sort
|
string | Das Feld, nach dem sortiert wird; ein Minuszeichen davor bedeutet absteigend. Welche Felder Sie wählen können und was der Standard ist, steht bei diesem Parameter. einer von: -created_at, created_at, -updated_at, updated_at · Standard: -created_at |
filter[status]
|
string | Nur Angebote mit diesem Status, zum Beispiel sent oder accepted. einer von: draft, pending_review, sent, viewed, accepted, rejected, expired, completed |
filter[customer_id]
|
string (uuid) | Nur was zu diesem Kunden gehört. |
filter[updated_since]
|
string (date-time) | Nur was seit diesem Zeitpunkt geändert wurde: ISO 8601 mit Zeitzone, zum Beispiel 2026-09-14T10:15:00Z. Praktisch zum Synchronisieren. Überlappen Sie Ihren vorherigen Zeitpunkt um einige Minuten, weil die Uhr Ihres Systems und die von Klantly nicht exakt gleich laufen und weil eine Änderung kurz nach Ihrem vorherigen Zeitpunkt sonst knapp außerhalb liegen würde. Die Grenze ist inklusiv: etwas, das genau zu diesem Zeitpunkt geändert wurde, zählt mit. |
Beispielanfrage
curl "https://app.klantly.com/api/v1/quotes?filter[status]=sent&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', 'quotes', [
'query' => [
'filter[status]' => 'sent',
'sort' => '-updated_at',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/quotes?filter[status]=sent&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/quotes",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
params={
"filter[status]": "sent",
"sort": "-updated_at"
},
)
data = response.json()["data"]Antwort 200
Die Antwort ist eine Liste mit Cursor-Paginierung: data enthält die Objekte, meta die Paginierung.
{
"data": [
{
"object": "quote",
"id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"number": "OFF-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
},
"custom_answers": [
{
"key": "custom_1760000000000_1",
"question": "Wanneer wil je ervan genieten?",
"answer": "Zo snel mogelijk"
}
],
"configurator_selections": [
{
"option_id": "01k2example0000000000000000",
"name": "Dakmateriaal",
"values": [
{
"id": "01k2example0000000000000001",
"label": "Glas"
}
],
"number": null
},
{
"option_id": "01k2example0000000000000002",
"name": "Breedte",
"values": [],
"number": 450
}
],
"deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
"pipeline_stage_id": "stg_0q8v4n61h9x0zc",
"language": "nl",
"currency": "EUR",
"quote_date": "2026-09-16",
"valid_until": "2026-09-30",
"intro_text": null,
"outro_text": null,
"terms_conditions": null,
"notes": 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",
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"rejected_at": null,
"invoice_id": null,
"items": [
{
"object": "quote_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
"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"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
],
"meta": {
"limit": 50,
"next_cursor": "eyJpZCI6IjlkM2Y2YzFlIn0",
"prev_cursor": null
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
422
validation_failed— Die Eingabe ist ungültig.
Angebot abrufen
/api/v1/quotes/{quote}
Ein Angebot per id, mit Positionen und Beträgen. Die Antwort enthält ein ETag, das Sie beim Ändern in If-Match mitsenden können.
- Scope
-
quotes.read— Angebote lesen, mit Positionen, Beträgen und den Kundendaten darauf - Erforderliche Funktion
quotes
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die ID (UUID) des Angebots. |
Beispielanfrage
curl "https://app.klantly.com/api/v1/quotes/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', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/quotes/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/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Antwort 200
{
"data": {
"object": "quote",
"id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"number": "OFF-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
},
"custom_answers": [
{
"key": "custom_1760000000000_1",
"question": "Wanneer wil je ervan genieten?",
"answer": "Zo snel mogelijk"
}
],
"configurator_selections": [
{
"option_id": "01k2example0000000000000000",
"name": "Dakmateriaal",
"values": [
{
"id": "01k2example0000000000000001",
"label": "Glas"
}
],
"number": null
},
{
"option_id": "01k2example0000000000000002",
"name": "Breedte",
"values": [],
"number": 450
}
],
"deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
"pipeline_stage_id": "stg_0q8v4n61h9x0zc",
"language": "nl",
"currency": "EUR",
"quote_date": "2026-09-16",
"valid_until": "2026-09-30",
"intro_text": null,
"outro_text": null,
"terms_conditions": null,
"notes": 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",
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"rejected_at": null,
"invoice_id": null,
"items": [
{
"object": "quote_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
"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"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden.
Angebot erstellen
/api/v1/quotes
Erstellt ein Angebot für einen Kunden, immer als Entwurf. Klantly vergibt die Nummer und berechnet die Summen; Name, Adresse und Kontaktdaten kommen vom Kunden. Senden Sie es danach mit Angebot senden. Hat der Kunde noch keinen offenen Deal, legt Klantly stillschweigend einen an (deal_id in der Antwort); ein separater POST /deals für diesen Kunden liefert danach 409.
- Scope
-
quotes.write— Angebote erstellen und bearbeiten (nur Entwürfe) sowie im Namen des Kunden annehmen oder ablehnen - Erforderliche Funktion
quotes
Senden Sie einen Idempotency-Key mit, dann erzeugt ein erneuter Versuch nach einem Timeout keinen doppelten Datensatz.
Body (JSON)
| Feld | Typ | Beschreibung |
|---|---|---|
customer_id
erforderlich
|
string (uuid) | Der Kunde. Beim Erstellen erforderlich; Name, Adresse und Kontaktdaten kommen vom Kunden. |
title
erforderlich
|
string | Titel des Angebots. Beim Erstellen erforderlich. höchstens 255 Zeichen |
items
erforderlich
|
array | Die Positionen (maximal 200). Beim Senden: eine Liste mit je Position name (erforderlich) und optional type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate und is_taxable. Sie ersetzen alle bestehenden Positionen. |
description
optional
|
string | Kurze Beschreibung; steht über den Positionen. kann leer sein (null) · höchstens 20000 Zeichen |
language
optional
|
string | Sprache des Angebots: nl, en, de oder fr. einer von: nl, en, de, fr |
quote_date
optional
|
string (date) | Angebotsdatum (JJJJ-MM-TT). |
valid_until
optional
|
string (date) | Gültig bis einschließlich (JJJJ-MM-TT). Ohne Angabe gilt die Standardfrist des Unternehmens. |
intro_text
optional
|
string | Einleitung über den Positionen. kann leer sein (null) · höchstens 20000 Zeichen |
outro_text
optional
|
string | Schlusstext unter den Positionen. kann leer sein (null) · höchstens 20000 Zeichen |
terms_conditions
optional
|
string | Bedingungen auf dem Angebot. kann leer sein (null) · höchstens 20000 Zeichen |
notes
optional
|
string | Anmerkungen für den Kunden. kann leer sein (null) · höchstens 20000 Zeichen |
hide_line_amounts
optional
|
boolean | Blendet die Beträge je Position aus; der Kunde sieht nur die Summe. |
discount_percentage
optional
|
number | Rabatt auf die Summe, in Prozent. von 0 bis 100 |
discount_amount
optional
|
number | Rabatt auf die Summe, als Betrag. von 0 bis 9999999 |
discount_description
optional
|
string | Warum der Rabatt gewährt wurde; steht auf dem Angebot. kann leer sein (null) · höchstens 500 Zeichen |
Beispielanfrage
curl -X POST "https://app.klantly.com/api/v1/quotes" \
-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', 'quotes', [
'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/quotes', {
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/quotes",
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"]Antwort 201
{
"data": {
"object": "quote",
"id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"number": "OFF-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
},
"custom_answers": [
{
"key": "custom_1760000000000_1",
"question": "Wanneer wil je ervan genieten?",
"answer": "Zo snel mogelijk"
}
],
"configurator_selections": [
{
"option_id": "01k2example0000000000000000",
"name": "Dakmateriaal",
"values": [
{
"id": "01k2example0000000000000001",
"label": "Glas"
}
],
"number": null
},
{
"option_id": "01k2example0000000000000002",
"name": "Breedte",
"values": [],
"number": 450
}
],
"deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
"pipeline_stage_id": "stg_0q8v4n61h9x0zc",
"language": "nl",
"currency": "EUR",
"quote_date": "2026-09-16",
"valid_until": "2026-09-30",
"intro_text": null,
"outro_text": null,
"terms_conditions": null,
"notes": 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",
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"rejected_at": null,
"invoice_id": null,
"items": [
{
"object": "quote_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
"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"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
422
validation_failed— Die Eingabe ist ungültig. -
422
unknown_field— Die Eingabe enthält ein unbekanntes Feld. -
415
unsupported_media_type— Dieses Format wird nicht unterstützt. -
413
payload_too_large— Der Body der Anfrage ist zu groß. -
403
limit_reached— Das Limit des Abonnements ist erreicht. -
422
idempotency_key_reused— Dieser Idempotency-Key wurde bereits für eine andere Anfrage verwendet. -
409
idempotency_in_progress— Eine Anfrage mit diesem Idempotency-Key wird noch verarbeitet.
Angebot ändern
/api/v1/quotes/{quote}
Ändert nur die Felder, die Sie mitsenden. items ersetzt alle Positionen als Ganzes; diese erhalten dabei neue ids. Nur ein Entwurf kann sich noch ändern: Ist das Angebot gesendet, hat der Kunde es gesehen, und Sie erhalten 409.
- Scope
-
quotes.write— Angebote erstellen und bearbeiten (nur Entwürfe) sowie im Namen des Kunden annehmen oder ablehnen - Erforderliche Funktion
quotes
Senden Sie das ETag in If-Match mit, dann überschreiben Sie nie versehentlich eine neuere Version.
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die ID (UUID) des Angebots. |
Body (JSON)
| Feld | Typ | Beschreibung |
|---|---|---|
title
optional
|
string | Titel des Angebots. Beim Erstellen erforderlich. höchstens 255 Zeichen |
description
optional
|
string | Kurze Beschreibung; steht über den Positionen. kann leer sein (null) · höchstens 20000 Zeichen |
language
optional
|
string | Sprache des Angebots: nl, en, de oder fr. einer von: nl, en, de, fr |
quote_date
optional
|
string (date) | Angebotsdatum (JJJJ-MM-TT). |
valid_until
optional
|
string (date) | Gültig bis einschließlich (JJJJ-MM-TT). Ohne Angabe gilt die Standardfrist des Unternehmens. |
intro_text
optional
|
string | Einleitung über den Positionen. kann leer sein (null) · höchstens 20000 Zeichen |
outro_text
optional
|
string | Schlusstext unter den Positionen. kann leer sein (null) · höchstens 20000 Zeichen |
terms_conditions
optional
|
string | Bedingungen auf dem Angebot. kann leer sein (null) · höchstens 20000 Zeichen |
notes
optional
|
string | Anmerkungen für den Kunden. kann leer sein (null) · höchstens 20000 Zeichen |
hide_line_amounts
optional
|
boolean | Blendet die Beträge je Position aus; der Kunde sieht nur die Summe. |
discount_percentage
optional
|
number | Rabatt auf die Summe, in Prozent. von 0 bis 100 |
discount_amount
optional
|
number | Rabatt auf die Summe, als Betrag. von 0 bis 9999999 |
discount_description
optional
|
string | Warum der Rabatt gewährt wurde; steht auf dem Angebot. kann leer sein (null) · höchstens 500 Zeichen |
items
optional
|
array | Die Positionen (maximal 200). Beim Senden: eine Liste mit je Position name (erforderlich) und optional type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate und is_taxable. Sie ersetzen alle bestehenden Positionen. |
Beispielanfrage
curl -X PATCH "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Veranda 400 × 300 (herzien)"
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('PATCH', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', [
'json' => [
'title' => 'Veranda 400 × 300 (herzien)',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"title": "Veranda 400 × 300 (herzien)"
}),
});
const { data } = await response.json();import os
import requests
response = requests.patch(
"https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
json={
"title": "Veranda 400 × 300 (herzien)"
},
)
data = response.json()["data"]Antwort 200
{
"data": {
"object": "quote",
"id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"number": "OFF-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
},
"custom_answers": [
{
"key": "custom_1760000000000_1",
"question": "Wanneer wil je ervan genieten?",
"answer": "Zo snel mogelijk"
}
],
"configurator_selections": [
{
"option_id": "01k2example0000000000000000",
"name": "Dakmateriaal",
"values": [
{
"id": "01k2example0000000000000001",
"label": "Glas"
}
],
"number": null
},
{
"option_id": "01k2example0000000000000002",
"name": "Breedte",
"values": [],
"number": 450
}
],
"deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
"pipeline_stage_id": "stg_0q8v4n61h9x0zc",
"language": "nl",
"currency": "EUR",
"quote_date": "2026-09-16",
"valid_until": "2026-09-30",
"intro_text": null,
"outro_text": null,
"terms_conditions": null,
"notes": 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",
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"rejected_at": null,
"invoice_id": null,
"items": [
{
"object": "quote_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
"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"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
422
validation_failed— Die Eingabe ist ungültig. -
422
unknown_field— Die Eingabe enthält ein unbekanntes Feld. -
415
unsupported_media_type— Dieses Format wird nicht unterstützt. -
413
payload_too_large— Der Body der Anfrage ist zu groß. -
404
not_found— Nicht gefunden. -
412
precondition_failed— Der Datensatz wurde inzwischen geändert. -
409
invalid_state_transition— Diese Aktion ist im aktuellen Status nicht möglich.
Angebot senden
/api/v1/quotes/{quote}/send
Sendet das Angebot per E-Mail an den Kunden über die Benachrichtigungsvorlage des Unternehmens, setzt den Status auf gesendet und hält den Zeitpunkt fest. Ihre eigene Nachricht erscheint in der E-Mail. Erneutes Senden nach einer Ablehnung setzt das Angebot wieder auf gesendet.
- Scope
-
quotes.send— Angebote per E-Mail an Kunden senden - Erforderliche Funktion
quotes
Senden Sie einen Idempotency-Key mit, dann erzeugt ein erneuter Versuch nach einem Timeout keinen doppelten Datensatz.
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die ID (UUID) des Angebots. |
Body (JSON)
| Feld | Typ | Beschreibung |
|---|---|---|
message
optional
|
string | Persönliche Nachricht in der E-Mail an den Kunden. kann leer sein (null) · höchstens 5000 Zeichen |
Beispielanfrage
curl -X POST "https://app.klantly.com/api/v1/quotes/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 offerte die we vanmorgen bespraken."
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/send', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
'json' => [
'message' => 'Bijgaand de offerte die we vanmorgen bespraken.',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/quotes/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 offerte die we vanmorgen bespraken."
}),
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/quotes/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 offerte die we vanmorgen bespraken."
},
)
data = response.json()["data"]Antwort 200
{
"data": {
"object": "quote",
"id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"number": "OFF-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
},
"custom_answers": [
{
"key": "custom_1760000000000_1",
"question": "Wanneer wil je ervan genieten?",
"answer": "Zo snel mogelijk"
}
],
"configurator_selections": [
{
"option_id": "01k2example0000000000000000",
"name": "Dakmateriaal",
"values": [
{
"id": "01k2example0000000000000001",
"label": "Glas"
}
],
"number": null
},
{
"option_id": "01k2example0000000000000002",
"name": "Breedte",
"values": [],
"number": 450
}
],
"deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
"pipeline_stage_id": "stg_0q8v4n61h9x0zc",
"language": "nl",
"currency": "EUR",
"quote_date": "2026-09-16",
"valid_until": "2026-09-30",
"intro_text": null,
"outro_text": null,
"terms_conditions": null,
"notes": 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",
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"rejected_at": null,
"invoice_id": null,
"items": [
{
"object": "quote_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
"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"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
422
validation_failed— Die Eingabe ist ungültig. -
422
unknown_field— Die Eingabe enthält ein unbekanntes Feld. -
415
unsupported_media_type— Dieses Format wird nicht unterstützt. -
413
payload_too_large— Der Body der Anfrage ist zu groß. -
404
not_found— Nicht gefunden. -
403
forbidden— Kein Zugriff. -
409
invalid_state_transition— Diese Aktion ist im aktuellen Status nicht möglich. -
422
idempotency_key_reused— Dieser Idempotency-Key wurde bereits für eine andere Anfrage verwendet. -
409
idempotency_in_progress— Eine Anfrage mit diesem Idempotency-Key wird noch verarbeitet.
Angebot annehmen
/api/v1/quotes/{quote}/accept
Hält fest, dass der Kunde zugestimmt hat, zum Beispiel nach einer Zusage außerhalb von Klantly. Ein angenommenes Angebot kann danach nicht mehr abgelehnt werden.
- Scope
-
quotes.write— Angebote erstellen und bearbeiten (nur Entwürfe) sowie im Namen des Kunden annehmen oder ablehnen - Erforderliche Funktion
quotes
Senden Sie einen Idempotency-Key mit, dann erzeugt ein erneuter Versuch nach einem Timeout keinen doppelten Datensatz.
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die ID (UUID) des Angebots. |
Beispielanfrage
curl -X POST "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/accept" \
-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', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/accept', [
'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/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/accept', {
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/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/accept",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
)
data = response.json()["data"]Antwort 200
{
"data": {
"object": "quote",
"id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"number": "OFF-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
},
"custom_answers": [
{
"key": "custom_1760000000000_1",
"question": "Wanneer wil je ervan genieten?",
"answer": "Zo snel mogelijk"
}
],
"configurator_selections": [
{
"option_id": "01k2example0000000000000000",
"name": "Dakmateriaal",
"values": [
{
"id": "01k2example0000000000000001",
"label": "Glas"
}
],
"number": null
},
{
"option_id": "01k2example0000000000000002",
"name": "Breedte",
"values": [],
"number": 450
}
],
"deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
"pipeline_stage_id": "stg_0q8v4n61h9x0zc",
"language": "nl",
"currency": "EUR",
"quote_date": "2026-09-16",
"valid_until": "2026-09-30",
"intro_text": null,
"outro_text": null,
"terms_conditions": null,
"notes": 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",
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"rejected_at": null,
"invoice_id": null,
"items": [
{
"object": "quote_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
"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"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden. -
409
invalid_state_transition— Diese Aktion ist im aktuellen Status nicht möglich. -
422
idempotency_key_reused— Dieser Idempotency-Key wurde bereits für eine andere Anfrage verwendet. -
409
idempotency_in_progress— Eine Anfrage mit diesem Idempotency-Key wird noch verarbeitet.
Angebot ablehnen
/api/v1/quotes/{quote}/reject
Hält fest, dass der Kunde abgelehnt hat. Sie können danach ein überarbeitetes Angebot senden; das setzt es wieder auf gesendet.
- Scope
-
quotes.write— Angebote erstellen und bearbeiten (nur Entwürfe) sowie im Namen des Kunden annehmen oder ablehnen - Erforderliche Funktion
quotes
Senden Sie einen Idempotency-Key mit, dann erzeugt ein erneuter Versuch nach einem Timeout keinen doppelten Datensatz.
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die ID (UUID) des Angebots. |
Beispielanfrage
curl -X POST "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/reject" \
-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', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/reject', [
'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/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/reject', {
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/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/reject",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
)
data = response.json()["data"]Antwort 200
{
"data": {
"object": "quote",
"id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"number": "OFF-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
},
"custom_answers": [
{
"key": "custom_1760000000000_1",
"question": "Wanneer wil je ervan genieten?",
"answer": "Zo snel mogelijk"
}
],
"configurator_selections": [
{
"option_id": "01k2example0000000000000000",
"name": "Dakmateriaal",
"values": [
{
"id": "01k2example0000000000000001",
"label": "Glas"
}
],
"number": null
},
{
"option_id": "01k2example0000000000000002",
"name": "Breedte",
"values": [],
"number": 450
}
],
"deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
"pipeline_stage_id": "stg_0q8v4n61h9x0zc",
"language": "nl",
"currency": "EUR",
"quote_date": "2026-09-16",
"valid_until": "2026-09-30",
"intro_text": null,
"outro_text": null,
"terms_conditions": null,
"notes": 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",
"sent_at": null,
"viewed_at": null,
"accepted_at": null,
"rejected_at": null,
"invoice_id": null,
"items": [
{
"object": "quote_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
"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"
}
],
"created_at": "2026-09-14T10:15:00Z",
"updated_at": "2026-09-14T10:15:00Z"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden. -
409
invalid_state_transition— Diese Aktion ist im aktuellen Status nicht möglich. -
422
idempotency_key_reused— Dieser Idempotency-Key wurde bereits für eine andere Anfrage verwendet. -
409
idempotency_in_progress— Eine Anfrage mit diesem Idempotency-Key wird noch verarbeitet.
Angebot als PDF
/api/v1/quotes/{quote}/pdf
Liefert das Angebot als PDF, so wie der Kunde es erhält: im Design des Unternehmens und in der Sprache des Angebots. Beim ersten Mal erstellt Klantly es (das kann einige Sekunden dauern); danach kommt es aus dem Cache, bis sich das Angebot ändert. Zählt zum strengeren Limit.
- Scope
-
quotes.read— Angebote lesen, mit Positionen, Beträgen und den Kundendaten darauf - Erforderliche Funktion
quotes
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die ID (UUID) des Angebots. |
Beispielanfrage
curl "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/pdf" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-o document.pdf$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('GET', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/pdf');
file_put_contents('document.pdf', (string) $response->getBody());import { writeFile } from 'node:fs/promises';
const response = await fetch('https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/pdf', {
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
await writeFile('document.pdf', Buffer.from(await response.arrayBuffer()));import os
import requests
response = requests.get(
"https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/pdf",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
with open("document.pdf", "wb") as pdf:
pdf.write(response.content)Antwort 200
Die Antwort ist die Datei selbst (application/pdf), kein JSON. Content-Disposition enthält den Dateinamen.
Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden.
Angebot in Rechnung umwandeln
/api/v1/quotes/{quote}/invoice
Erstellt aus einem angenommenen Angebot einen Rechnungsentwurf, wie die Schaltfläche in der App: Kunde, Positionen, Rabatt und Texte werden übernommen. Ohne Datumsangaben erhält die Rechnung das heutige Datum und das Zahlungsziel des Unternehmens. Ein Angebot hat höchstens eine Rechnung: Eine zweite Anfrage liefert 409 conflict mit der ID der vorhandenen Rechnung in invoice_id. Erfordert das Feature und den Scope für Rechnungen.
- Scope
-
invoices.write— Rechnungen erstellen (auch aus einem Angebot) und bearbeiten (nur Entwürfe), stornieren, Zahlungen erfassen, gutschreiben und eine Rückerstattung erfassen - Erforderliche Funktion
quotes, invoices
Senden Sie einen Idempotency-Key mit, dann erzeugt ein erneuter Versuch nach einem Timeout keinen doppelten Datensatz.
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die ID (UUID) des Angebots. |
Body (JSON)
| Feld | Typ | Beschreibung |
|---|---|---|
invoice_date
optional
|
string (date) | Rechnungsdatum (JJJJ-MM-TT). |
due_date
optional
|
string (date) | Fälligkeitsdatum (JJJJ-MM-TT). Ohne Angabe ist es das Rechnungsdatum plus die Zahlungsfrist. |
payment_term_days
optional
|
integer | Zahlungsfrist in Tagen. Ohne Angabe gilt der Standard des Unternehmens. von 0 bis 365 |
Beispielanfrage
curl -X POST "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/invoice" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
-d '{
"payment_term_days": 14
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/invoice', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
'json' => [
'payment_term_days' => 14,
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/invoice', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
},
body: JSON.stringify({
"payment_term_days": 14
}),
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/invoice",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
json={
"payment_term_days": 14
},
)
data = response.json()["data"]Antwort 201
{
"data": {
"object": "invoice",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dd",
"number": "FAC-00042",
"type": "invoice",
"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,
"term_amount": 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,
"credited_invoice_id": null,
"credited_invoice_number": null,
"credit_reason": null,
"refund_amount": null,
"refunded_at": null,
"credit_note_state": null,
"credited_amount": "0.00",
"credit_note_ids": [],
"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",
"credited_item_id": null
}
],
"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"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
422
validation_failed— Die Eingabe ist ungültig. -
422
unknown_field— Die Eingabe enthält ein unbekanntes Feld. -
415
unsupported_media_type— Dieses Format wird nicht unterstützt. -
413
payload_too_large— Der Body der Anfrage ist zu groß. -
404
not_found— Nicht gefunden. -
403
limit_reached— Das Limit des Abonnements ist erreicht. -
409
invalid_state_transition— Diese Aktion ist im aktuellen Status nicht möglich. -
409
conflict— Dies steht im Widerspruch zum aktuellen Zustand. -
422
idempotency_key_reused— Dieser Idempotency-Key wurde bereits für eine andere Anfrage verwendet. -
409
idempotency_in_progress— Eine Anfrage mit diesem Idempotency-Key wird noch verarbeitet.
Angebot löschen
/api/v1/quotes/{quote}
Löscht das Angebot. Nur ein Entwurf kann weg: Ein gesendetes Angebot bleibt bestehen.
- Scope
-
quotes.delete— Angebote löschen - Erforderliche Funktion
quotes
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die ID (UUID) des Angebots. |
Beispielanfrage
curl -X DELETE "https://app.klantly.com/api/v1/quotes/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', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/quotes/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/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Antwort 200
{
"data": {
"object": "note",
"id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
"deleted": true
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden. -
409
invalid_state_transition— Diese Aktion ist im aktuellen Status nicht möglich.
Das Objekt
Alle Felder sind immer vorhanden; ein Feld ohne Wert ist null.
| Feld | Typ | Beschreibung |
|---|---|---|
object |
string | Immer "quote". |
id |
string (uuid) | Eindeutige id (UUID). |
number |
string | Angebotsnummer, zum Beispiel OFF-00042; vergibt Klantly selbst. |
status |
string | draft (Entwurf), pending_review (zur Prüfung), sent (gesendet), viewed (angesehen), accepted (angenommen), rejected (abgelehnt), expired (abgelaufen) oder completed (abgeschlossen). einer von: draft, pending_review, sent, viewed, accepted, rejected, expired, completed |
title |
string | Titel des Angebots. Beim Erstellen erforderlich. kann leer sein (null) |
description |
string | Kurze Beschreibung; steht über den Positionen. kann leer sein (null) |
customer_id |
string (uuid) | Der Kunde. Beim Erstellen erforderlich; Name, Adresse und Kontaktdaten kommen vom Kunden. kann leer sein (null) |
customer |
object | Die Kundendaten auf dem Angebot, wie sie beim Erstellen waren. |
customer.type |
string | individual (Privatperson) oder business (Unternehmen). kann leer sein (null) |
customer.name |
string | Name; bei einem Unternehmen der Firmenname. kann leer sein (null) |
customer.email |
string | E-Mail-Adresse. kann leer sein (null) |
customer.phone |
string | Telefonnummer. kann leer sein (null) |
customer.address |
string | Straße und Hausnummer. kann leer sein (null) |
customer.postal_code |
string | Postleitzahl. kann leer sein (null) |
customer.city |
string | Ort. kann leer sein (null) |
customer.country |
string | Land. kann leer sein (null) |
customer.company_name |
string | Firmenname. kann leer sein (null) |
customer.vat_number |
string | USt-IdNr. kann leer sein (null) |
customer.coc_number |
string | Handelsregisternummer. kann leer sein (null) |
custom_answers |
array<object> | Antworten auf eigene Fragen, die der Kunde im Konfigurator ausgefüllt hat, zum Beispiel „Wann möchten Sie es genießen?“. Eine leere Liste, wenn es keine gibt. Nur lesbar. |
custom_answers.key |
string | Schlüssel des Feldes im Konfigurator. |
custom_answers.question |
string | Die Frage, wie sie im Formular stand. |
custom_answers.answer |
string | Die Antwort des Kunden. |
configurator_selections |
array<object> | Die Produktauswahl aus dem Konfigurator, pro Option: die gewählten Werte oder die eingegebene Zahl. Eine leere Liste, wenn es keine gibt. Nur lesbar. |
configurator_selections.option_id |
string | ID der Option im Konfigurator. |
configurator_selections.name |
string | Name der Option, zum Beispiel „Dachmaterial". |
configurator_selections.values |
array<object> | Die gewählten Werte (bei einer Auswahloption). |
configurator_selections.values.id |
string | ID des Werts im Konfigurator. |
configurator_selections.values.label |
string | Name des Werts, zum Beispiel „Glas". |
configurator_selections.number |
number | Die eingegebene Zahl (bei einem Zahlenfeld, zum Beispiel die Breite). kann leer sein (null) |
deal_id |
string (uuid) | Der Deal auf dem Pipeline-Board, zu dem dieses Angebot gehört, oder null. kann leer sein (null) |
pipeline_stage_id |
string | Die Phase, in der das Angebot steht, oder null. kann leer sein (null) |
language |
string | Sprache des Angebots: nl, en, de oder fr. einer von: nl, en, de, fr |
currency |
string | Immer "EUR". |
quote_date |
string (date) | Angebotsdatum (JJJJ-MM-TT). kann leer sein (null) |
valid_until |
string (date) | Gültig bis einschließlich (JJJJ-MM-TT). Ohne Angabe gilt die Standardfrist des Unternehmens. kann leer sein (null) |
intro_text |
string | Einleitung über den Positionen. kann leer sein (null) |
outro_text |
string | Schlusstext unter den Positionen. kann leer sein (null) |
terms_conditions |
string | Bedingungen auf dem Angebot. kann leer sein (null) |
notes |
string | Anmerkungen für den Kunden. kann leer sein (null) |
hide_line_amounts |
boolean | Blendet die Beträge je Position aus; der Kunde sieht nur die Summe. |
discount_percentage |
string | Rabatt auf die Summe, in Prozent. |
discount_amount |
string | Rabatt auf die Summe, als Betrag. |
discount_description |
string | Warum der Rabatt gewährt wurde; steht auf dem Angebot. kann leer sein (null) |
subtotal |
string | Summe ohne MwSt., als Text mit zwei Dezimalstellen. |
tax_amount |
string | MwSt.-Betrag. |
total |
string | Summe inklusive MwSt. |
sent_at |
string (date-time) | Wann das Angebot an den Kunden gesendet wurde. kann leer sein (null) |
viewed_at |
string (date-time) | Wann der Kunde es geöffnet hat. kann leer sein (null) |
accepted_at |
string (date-time) | Wann der Kunde zugestimmt hat. kann leer sein (null) |
rejected_at |
string (date-time) | Wann der Kunde abgelehnt hat. kann leer sein (null) |
invoice_id |
string (uuid) | Die aus diesem Angebot erstellte Rechnung, oder null. kann leer sein (null) |
items |
array<object> | Die Positionen (maximal 200). Beim Senden: eine Liste mit je Position name (erforderlich) und optional type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate und is_taxable. Sie ersetzen alle bestehenden Positionen. |
items.object |
string | Immer "quote_item". |
items.id |
string (uuid) | Eindeutige id der Position (UUID). |
items.type |
string | product, service, discount oder fee. einer von: product, service, discount, fee |
items.name |
string | Name der Position. |
items.description |
string | Beschreibung unter der Position. kann leer sein (null) |
items.sku |
string | Artikelnummer. kann leer sein (null) |
items.quantity |
string | Menge. |
items.unit |
string | Einheit, zum Beispiel Stück oder Stunde. kann leer sein (null) |
items.unit_price |
string | Preis je Einheit, ohne MwSt. |
items.unit_price_incl |
string | Preis pro Einheit inklusive MwSt., wie eingegeben. Ist er gesetzt, wird die Position von diesem Betrag aus berechnet (unit_price und line_total werden daraus abgeleitet), und die Summe stimmt auf den Cent mit dem eingegebenen Preis überein. null = die Position wird von unit_price aus berechnet. kann leer sein (null) |
items.discount_percentage |
string | Rabatt auf diese Position, in Prozent. |
items.discount_amount |
string | Rabatt auf diese Position, als Betrag. |
items.discount_description |
string | Warum der Rabatt auf diese Position gewährt wurde. kann leer sein (null) |
items.line_total |
string | Positionssumme ohne MwSt., nach Rabatt. |
items.line_total_incl |
string | Positionssumme inklusive MwSt., nach Rabatt. Nur gesetzt, wenn die Position von unit_price_incl aus berechnet wird; sonst null. kann leer sein (null) |
items.is_taxable |
boolean | Ob diese Position für die MwSt. zählt. |
items.tax_rate |
string | MwSt.-Satz dieser Position, in Prozent. |
created_at |
string (date-time) | Wann das Angebot erstellt wurde. |
updated_at |
string (date-time) | Wann das Angebot zuletzt geändert wurde. |