Référence de l'API
Devis
Les devis avec leurs lignes et leurs pièces jointes : les rédiger, les modifier tant qu'ils sont des brouillons, les envoyer par e-mail au client, enregistrer l'issue, récupérer le PDF et en faire une facture.
Endpoints
-
GET
/quotesLister les devis -
GET
/quotes/{quote}Récupérer un devis -
POST
/quotesCréer un devis -
PATCH
/quotes/{quote}Modifier un devis -
POST
/quotes/{quote}/sendEnvoyer un devis -
POST
/quotes/{quote}/acceptAccepter un devis -
POST
/quotes/{quote}/rejectRefuser un devis -
GET
/quotes/{quote}/pdfDevis en PDF -
POST
/quotes/{quote}/invoiceTransformer un devis en facture -
DELETE
/quotes/{quote}Supprimer un devis
Lister les devis
/api/v1/quotes
Une liste de devis, les plus récents d'abord, avec leurs lignes. Filtrez par statut, client ou date de modification. Avec filter[status]=sent et filter[updated_since], vous récupérez ce qui a été envoyé depuis votre dernière synchronisation.
- Scope
-
quotes.read— Lire les devis, avec leurs lignes, leurs montants et les coordonnées du client - Fonctionnalité requise
quotes
Paramètres de requête
| Nom | Type | Description |
|---|---|---|
limit
|
integer | Nombre de résultats par page. de 1 à 100 · par défaut : 50 |
cursor
|
string | Le next_cursor ou prev_cursor de meta dans la réponse précédente. |
sort
|
string | Le champ sur lequel trier ; un signe moins devant signifie décroissant. Les champs que vous pouvez choisir et la valeur par défaut sont indiqués avec ce paramètre. l'une des valeurs : -created_at, created_at, -updated_at, updated_at · par défaut : -created_at |
filter[status]
|
string | Uniquement les devis avec ce statut, par exemple sent ou accepted. l'une des valeurs : draft, pending_review, sent, viewed, accepted, rejected, expired, completed |
filter[customer_id]
|
string (uuid) | Uniquement ce qui appartient à ce client. |
filter[updated_since]
|
string (date-time) | Uniquement ce qui a changé depuis ce moment : ISO 8601 avec fuseau horaire, par exemple 2026-09-14T10:15:00Z. Pratique pour synchroniser. Prévoyez un chevauchement de quelques minutes avec votre moment précédent, car l'horloge de votre système et celle de Klantly ne sont pas exactement synchronisées, et car une modification juste après votre moment précédent tomberait sinon de justesse en dehors. La limite est inclusive : un élément modifié exactement à cet instant est inclus. |
Exemple de requête
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"]Réponse 200
La réponse est une liste avec pagination par curseur : data contient les objets, meta la pagination.
{
"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
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
422
validation_failed— Les données saisies ne sont pas valides.
Récupérer un devis
/api/v1/quotes/{quote}
Un devis par id, avec ses lignes et ses montants. La réponse contient un ETag que vous pouvez renvoyer dans If-Match lors d'une modification.
- Scope
-
quotes.read— Lire les devis, avec leurs lignes, leurs montants et les coordonnées du client - Fonctionnalité requise
quotes
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Exemple de requête
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"]Réponse 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"
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
404
not_found— Introuvable.
Créer un devis
/api/v1/quotes
Crée un devis pour un client, toujours en brouillon. Klantly attribue le numéro et calcule les totaux ; le nom, l'adresse et les coordonnées proviennent du client. Envoyez-le ensuite avec Envoyer un devis. Si le client n'a pas encore de deal ouvert, Klantly en crée un silencieusement (deal_id dans la réponse) ; un POST /deals séparé pour ce client renvoie ensuite 409.
- Scope
-
quotes.write— Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client - Fonctionnalité requise
quotes
Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.
Corps (JSON)
| Champ | Type | Description |
|---|---|---|
customer_id
obligatoire
|
string (uuid) | Le client. Obligatoire à la création ; le nom, l'adresse et les coordonnées proviennent du client. |
title
obligatoire
|
string | Titre du devis. Obligatoire à la création. au maximum 255 caractères |
items
obligatoire
|
array | Les lignes (200 au maximum). À l'envoi : une liste avec, par ligne, name (obligatoire) et éventuellement type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate et is_taxable. Elles remplacent toutes les lignes existantes. |
description
facultatif
|
string | Brève description ; affichée au-dessus des lignes. peut être vide (null) · au maximum 20000 caractères |
language
facultatif
|
string | Langue du devis : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr |
quote_date
facultatif
|
string (date) | Date du devis (AAAA-MM-JJ). |
valid_until
facultatif
|
string (date) | Valable jusqu'au (AAAA-MM-JJ) inclus. Sans indication, le délai par défaut de l'entreprise s'applique. |
intro_text
facultatif
|
string | Introduction au-dessus des lignes. peut être vide (null) · au maximum 20000 caractères |
outro_text
facultatif
|
string | Texte de clôture sous les lignes. peut être vide (null) · au maximum 20000 caractères |
terms_conditions
facultatif
|
string | Conditions figurant sur le devis. peut être vide (null) · au maximum 20000 caractères |
notes
facultatif
|
string | Remarques pour le client. peut être vide (null) · au maximum 20000 caractères |
hide_line_amounts
facultatif
|
boolean | Masque les montants par ligne sur le devis ; le client ne voit que le total. |
discount_percentage
facultatif
|
number | Remise sur le total, en pourcentage. de 0 à 100 |
discount_amount
facultatif
|
number | Remise sur le total, en montant. de 0 à 9999999 |
discount_description
facultatif
|
string | Pourquoi la remise a été accordée ; figure sur le devis. peut être vide (null) · au maximum 500 caractères |
Exemple de requête
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"]Réponse 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"
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
422
validation_failed— Les données saisies ne sont pas valides. -
422
unknown_field— Les données contiennent un champ inconnu. -
415
unsupported_media_type— Ce format n'est pas pris en charge. -
413
payload_too_large— Le corps de la requête est trop volumineux. -
403
limit_reached— La limite de l'abonnement est atteinte. -
422
idempotency_key_reused— Cette Idempotency-Key a déjà été utilisée pour une autre requête. -
409
idempotency_in_progress— Une requête avec cette Idempotency-Key est encore en cours.
Modifier un devis
/api/v1/quotes/{quote}
Ne modifie que les champs que vous envoyez. items remplace toutes les lignes en bloc, et ces lignes reçoivent de nouveaux ids. Seul un brouillon peut encore changer : une fois le devis envoyé, le client l'a vu et vous obtenez 409.
- Scope
-
quotes.write— Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client - Fonctionnalité requise
quotes
Envoyez l'ETag dans If-Match : vous n'écraserez jamais par erreur une version plus récente.
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Corps (JSON)
| Champ | Type | Description |
|---|---|---|
title
facultatif
|
string | Titre du devis. Obligatoire à la création. au maximum 255 caractères |
description
facultatif
|
string | Brève description ; affichée au-dessus des lignes. peut être vide (null) · au maximum 20000 caractères |
language
facultatif
|
string | Langue du devis : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr |
quote_date
facultatif
|
string (date) | Date du devis (AAAA-MM-JJ). |
valid_until
facultatif
|
string (date) | Valable jusqu'au (AAAA-MM-JJ) inclus. Sans indication, le délai par défaut de l'entreprise s'applique. |
intro_text
facultatif
|
string | Introduction au-dessus des lignes. peut être vide (null) · au maximum 20000 caractères |
outro_text
facultatif
|
string | Texte de clôture sous les lignes. peut être vide (null) · au maximum 20000 caractères |
terms_conditions
facultatif
|
string | Conditions figurant sur le devis. peut être vide (null) · au maximum 20000 caractères |
notes
facultatif
|
string | Remarques pour le client. peut être vide (null) · au maximum 20000 caractères |
hide_line_amounts
facultatif
|
boolean | Masque les montants par ligne sur le devis ; le client ne voit que le total. |
discount_percentage
facultatif
|
number | Remise sur le total, en pourcentage. de 0 à 100 |
discount_amount
facultatif
|
number | Remise sur le total, en montant. de 0 à 9999999 |
discount_description
facultatif
|
string | Pourquoi la remise a été accordée ; figure sur le devis. peut être vide (null) · au maximum 500 caractères |
items
facultatif
|
array | Les lignes (200 au maximum). À l'envoi : une liste avec, par ligne, name (obligatoire) et éventuellement type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate et is_taxable. Elles remplacent toutes les lignes existantes. |
Exemple de requête
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"]Réponse 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"
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
422
validation_failed— Les données saisies ne sont pas valides. -
422
unknown_field— Les données contiennent un champ inconnu. -
415
unsupported_media_type— Ce format n'est pas pris en charge. -
413
payload_too_large— Le corps de la requête est trop volumineux. -
404
not_found— Introuvable. -
412
precondition_failed— L'enregistrement a été modifié entre-temps. -
409
invalid_state_transition— Cette action n'est pas possible dans le statut actuel.
Envoyer un devis
/api/v1/quotes/{quote}/send
Envoie le devis par e-mail au client avec le modèle de notification de l'entreprise, passe le statut à envoyé et enregistre l'heure. Votre message personnel apparaît dans l'e-mail. Un nouvel envoi après un refus remet le devis sur envoyé.
- Scope
-
quotes.send— Envoyer les devis par e-mail aux clients - Fonctionnalité requise
quotes
Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Corps (JSON)
| Champ | Type | Description |
|---|---|---|
message
facultatif
|
string | Message personnel dans l'e-mail au client. peut être vide (null) · au maximum 5000 caractères |
Exemple de requête
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"]Réponse 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"
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
422
validation_failed— Les données saisies ne sont pas valides. -
422
unknown_field— Les données contiennent un champ inconnu. -
415
unsupported_media_type— Ce format n'est pas pris en charge. -
413
payload_too_large— Le corps de la requête est trop volumineux. -
404
not_found— Introuvable. -
403
forbidden— Accès refusé. -
409
invalid_state_transition— Cette action n'est pas possible dans le statut actuel. -
422
idempotency_key_reused— Cette Idempotency-Key a déjà été utilisée pour une autre requête. -
409
idempotency_in_progress— Une requête avec cette Idempotency-Key est encore en cours.
Accepter un devis
/api/v1/quotes/{quote}/accept
Enregistre l'accord du client, par exemple après une validation en dehors de Klantly. Un devis accepté ne peut plus être refusé.
- Scope
-
quotes.write— Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client - Fonctionnalité requise
quotes
Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Exemple de requête
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"]Réponse 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"
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
404
not_found— Introuvable. -
409
invalid_state_transition— Cette action n'est pas possible dans le statut actuel. -
422
idempotency_key_reused— Cette Idempotency-Key a déjà été utilisée pour une autre requête. -
409
idempotency_in_progress— Une requête avec cette Idempotency-Key est encore en cours.
Refuser un devis
/api/v1/quotes/{quote}/reject
Enregistre le refus du client. Vous pouvez ensuite envoyer un devis révisé, ce qui le remet sur envoyé.
- Scope
-
quotes.write— Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client - Fonctionnalité requise
quotes
Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Exemple de requête
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"]Réponse 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"
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
404
not_found— Introuvable. -
409
invalid_state_transition— Cette action n'est pas possible dans le statut actuel. -
422
idempotency_key_reused— Cette Idempotency-Key a déjà été utilisée pour une autre requête. -
409
idempotency_in_progress— Une requête avec cette Idempotency-Key est encore en cours.
Devis en PDF
/api/v1/quotes/{quote}/pdf
Renvoie le devis en PDF, tel que le client le reçoit : à la charte de l'entreprise et dans la langue du devis. La première fois, Klantly le génère (cela peut prendre quelques secondes) ; ensuite il vient du cache jusqu'à ce que le devis change. Compte dans la limite renforcée.
- Scope
-
quotes.read— Lire les devis, avec leurs lignes, leurs montants et les coordonnées du client - Fonctionnalité requise
quotes
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Exemple de requête
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)Réponse 200
La réponse est le fichier lui-même (application/pdf), pas du JSON. Content-Disposition contient le nom du fichier.
Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
404
not_found— Introuvable.
Transformer un devis en facture
/api/v1/quotes/{quote}/invoice
Crée un brouillon de facture à partir d'un devis accepté, comme le bouton de l'application : client, lignes, remise et textes sont repris. Sans dates, la facture reçoit la date du jour et le délai de paiement de l'entreprise. Un devis a au plus une facture : une deuxième requête renvoie 409 conflict avec l'id de la facture existante dans invoice_id. Nécessite la fonctionnalité et le scope des factures.
- Scope
-
invoices.write— Créer des factures (aussi à partir d'un devis) et les modifier (brouillons uniquement), les annuler, enregistrer des paiements, les créditer et enregistrer un remboursement - Fonctionnalité requise
quotes, invoices
Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Corps (JSON)
| Champ | Type | Description |
|---|---|---|
invoice_date
facultatif
|
string (date) | Date de facture (AAAA-MM-JJ). |
due_date
facultatif
|
string (date) | Date d'échéance (AAAA-MM-JJ). Sans indication, c'est la date de facture plus le délai de paiement. |
payment_term_days
facultatif
|
integer | Délai de paiement en jours. Sans indication, le délai par défaut de l'entreprise s'applique. de 0 à 365 |
Exemple de requête
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"]Réponse 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"
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
422
validation_failed— Les données saisies ne sont pas valides. -
422
unknown_field— Les données contiennent un champ inconnu. -
415
unsupported_media_type— Ce format n'est pas pris en charge. -
413
payload_too_large— Le corps de la requête est trop volumineux. -
404
not_found— Introuvable. -
403
limit_reached— La limite de l'abonnement est atteinte. -
409
invalid_state_transition— Cette action n'est pas possible dans le statut actuel. -
409
conflict— Ceci est en conflit avec l'état actuel. -
422
idempotency_key_reused— Cette Idempotency-Key a déjà été utilisée pour une autre requête. -
409
idempotency_in_progress— Une requête avec cette Idempotency-Key est encore en cours.
Supprimer un devis
/api/v1/quotes/{quote}
Supprime le devis. Seul un brouillon peut être supprimé : un devis envoyé reste.
- Scope
-
quotes.delete— Supprimer des devis - Fonctionnalité requise
quotes
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Exemple de requête
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"]Réponse 200
{
"data": {
"object": "note",
"id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
"deleted": true
}
}Erreurs possibles
-
403
insufficient_scope— Cette clé API n'a pas accès à cette action. -
404
not_found— Introuvable. -
409
invalid_state_transition— Cette action n'est pas possible dans le statut actuel.
L'objet
Tous les champs sont toujours présents ; un champ sans valeur vaut null.
| Champ | Type | Description |
|---|---|---|
object |
string | Toujours "quote". |
id |
string (uuid) | Id unique (UUID). |
number |
string | Numéro de devis, par exemple OFF-00042 ; attribué par Klantly. |
status |
string | draft (brouillon), pending_review (à vérifier), sent (envoyé), viewed (consulté), accepted (accepté), rejected (refusé), expired (expiré) ou completed (terminé). l'une des valeurs : draft, pending_review, sent, viewed, accepted, rejected, expired, completed |
title |
string | Titre du devis. Obligatoire à la création. peut être vide (null) |
description |
string | Brève description ; affichée au-dessus des lignes. peut être vide (null) |
customer_id |
string (uuid) | Le client. Obligatoire à la création ; le nom, l'adresse et les coordonnées proviennent du client. peut être vide (null) |
customer |
object | Les coordonnées du client sur le devis, telles qu'elles étaient à la création. |
customer.type |
string | individual (particulier) ou business (entreprise). peut être vide (null) |
customer.name |
string | Nom ; pour une entreprise, la raison sociale. peut être vide (null) |
customer.email |
string | Adresse e-mail. peut être vide (null) |
customer.phone |
string | Numéro de téléphone. peut être vide (null) |
customer.address |
string | Rue et numéro. peut être vide (null) |
customer.postal_code |
string | Code postal. peut être vide (null) |
customer.city |
string | Ville. peut être vide (null) |
customer.country |
string | Pays. peut être vide (null) |
customer.company_name |
string | Raison sociale. peut être vide (null) |
customer.vat_number |
string | Numéro de TVA. peut être vide (null) |
customer.coc_number |
string | Numéro d'immatriculation. peut être vide (null) |
custom_answers |
array<object> | Réponses aux questions propres de l'entreprise que le client a remplies dans le configurateur, par exemple « Quand voulez-vous en profiter ? ». Une liste vide s'il n'y en a pas. Lecture seule. |
custom_answers.key |
string | Clé du champ dans le configurateur. |
custom_answers.question |
string | La question, telle qu'elle figurait dans le formulaire. |
custom_answers.answer |
string | La réponse du client. |
configurator_selections |
array<object> | Les choix de produit du configurateur, par option : la ou les valeurs choisies ou le nombre saisi. Une liste vide s’il n’y en a pas. En lecture seule. |
configurator_selections.option_id |
string | Id de l’option dans le configurateur. |
configurator_selections.name |
string | Nom de l’option, par exemple « Matériau de toit ». |
configurator_selections.values |
array<object> | Les valeurs choisies (pour une option à choix). |
configurator_selections.values.id |
string | Id de la valeur dans le configurateur. |
configurator_selections.values.label |
string | Nom de la valeur, par exemple « Verre ». |
configurator_selections.number |
number | Le nombre saisi (pour un champ numérique, par exemple la largeur). peut être vide (null) |
deal_id |
string (uuid) | L'affaire du pipeline à laquelle ce devis appartient, ou null. peut être vide (null) |
pipeline_stage_id |
string | L'étape dans laquelle se trouve le devis, ou null. peut être vide (null) |
language |
string | Langue du devis : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr |
currency |
string | Toujours "EUR". |
quote_date |
string (date) | Date du devis (AAAA-MM-JJ). peut être vide (null) |
valid_until |
string (date) | Valable jusqu'au (AAAA-MM-JJ) inclus. Sans indication, le délai par défaut de l'entreprise s'applique. peut être vide (null) |
intro_text |
string | Introduction au-dessus des lignes. peut être vide (null) |
outro_text |
string | Texte de clôture sous les lignes. peut être vide (null) |
terms_conditions |
string | Conditions figurant sur le devis. peut être vide (null) |
notes |
string | Remarques pour le client. peut être vide (null) |
hide_line_amounts |
boolean | Masque les montants par ligne sur le devis ; le client ne voit que le total. |
discount_percentage |
string | Remise sur le total, en pourcentage. |
discount_amount |
string | Remise sur le total, en montant. |
discount_description |
string | Pourquoi la remise a été accordée ; figure sur le devis. peut être vide (null) |
subtotal |
string | Total hors TVA, sous forme de texte à deux décimales. |
tax_amount |
string | Montant de la TVA. |
total |
string | Total TTC. |
sent_at |
string (date-time) | Quand le devis a été envoyé au client. peut être vide (null) |
viewed_at |
string (date-time) | Quand le client l'a ouvert. peut être vide (null) |
accepted_at |
string (date-time) | Quand le client a donné son accord. peut être vide (null) |
rejected_at |
string (date-time) | Quand le client a refusé. peut être vide (null) |
invoice_id |
string (uuid) | La facture créée à partir de ce devis, ou null. peut être vide (null) |
items |
array<object> | Les lignes (200 au maximum). À l'envoi : une liste avec, par ligne, name (obligatoire) et éventuellement type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate et is_taxable. Elles remplacent toutes les lignes existantes. |
items.object |
string | Toujours "quote_item". |
items.id |
string (uuid) | Id unique de la ligne (UUID). |
items.type |
string | product, service, discount ou fee. l'une des valeurs : product, service, discount, fee |
items.name |
string | Nom de la ligne. |
items.description |
string | Description sous la ligne. peut être vide (null) |
items.sku |
string | Référence article. peut être vide (null) |
items.quantity |
string | Quantité. |
items.unit |
string | Unité, par exemple pièce ou heure. peut être vide (null) |
items.unit_price |
string | Prix unitaire, hors TVA. |
items.unit_price_incl |
string | Prix unitaire TVA comprise, tel que saisi. S'il est renseigné, la ligne est calculée à partir de ce montant (unit_price et line_total en sont déduits) et le total correspond au centime près au prix saisi. null = la ligne est calculée à partir de unit_price. peut être vide (null) |
items.discount_percentage |
string | Remise sur cette ligne, en pourcentage. |
items.discount_amount |
string | Remise sur cette ligne, en montant. |
items.discount_description |
string | Pourquoi la remise sur cette ligne a été accordée. peut être vide (null) |
items.line_total |
string | Total de la ligne hors TVA, après remise. |
items.line_total_incl |
string | Total de la ligne TVA comprise, après remise. Renseigné uniquement si la ligne est calculée à partir de unit_price_incl ; sinon null. peut être vide (null) |
items.is_taxable |
boolean | Si cette ligne compte pour la TVA. |
items.tax_rate |
string | Taux de TVA de cette ligne, en pourcentage. |
created_at |
string (date-time) | Quand le devis a été créé. |
updated_at |
string (date-time) | Quand le devis a été modifié pour la dernière fois. |