API-Referenz
Angebote
Angebote mit ihren Positionen: erstellen, ändern solange sie Entwurf sind, per E-Mail an den Kunden senden und das Ergebnis festhalten.
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 -
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 | Sortierung nach created_at oder updated_at; ein Minuszeichen davor sortiert absteigend. 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. |
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
},
"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
},
"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.
- 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
},
"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
},
"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
},
"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
},
"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
},
"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 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) |
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. |