Référence de l'API
Bons d’intervention
Des bons d’intervention avec lignes et check-list : les créer depuis un ERP par exemple, les modifier, changer leur statut et récupérer ceux qui sont terminés.
Endpoints
-
GET
/work-ordersLister les bons d’intervention -
GET
/work-orders/{work_order}Récupérer un bon d’intervention -
POST
/work-ordersCréer un bon d’intervention -
PATCH
/work-orders/{work_order}Modifier un bon d’intervention -
POST
/work-orders/{work_order}/statusChanger le statut d’un bon d’intervention -
DELETE
/work-orders/{work_order}Supprimer un bon d’intervention
Lister les bons d’intervention
/api/v1/work-orders
Une liste de bons d’intervention, du plus récent au plus ancien, avec lignes, check-list et photos. Filtrez par statut, client, utilisateur, rendez-vous ou date de modification, ou recherchez avec q dans le numéro, le nom du client et le titre. Avec filter[status]=completed et filter[updated_since], vous récupérez le travail terminé.
- Scope
-
work_orders.read— Lire les bons d'intervention, avec le nom, l'adresse et les coordonnées du client - Fonctionnalité requise
work_orders
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. |
q
|
string | Recherche dans le numéro du bon d’intervention, le nom du client et le titre. au moins 2 caractères · au maximum 100 caractères |
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 bons d’intervention de ce statut. l'une des valeurs : draft, planned, in_progress, completed, invoiced, cancelled |
filter[customer_id]
|
string (uuid) | Uniquement ce qui appartient à ce client. |
filter[assigned_user_id]
|
string | Uniquement ce qui est assigné à cet utilisateur (l’id de Lister les utilisateurs). |
filter[appointment_id]
|
string (uuid) | Uniquement les bons d’intervention de ce rendez-vous. |
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/work-orders?filter[status]=completed&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', 'work-orders', [
'query' => [
'filter[status]' => 'completed',
'sort' => '-updated_at',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/work-orders?filter[status]=completed&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/work-orders",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
params={
"filter[status]": "completed",
"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": "work_order",
"id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
"number": "WB-2026-00042",
"status": "planned",
"title": "Onderhoud cv-ketel",
"type": "onderhoud",
"location": null,
"description": null,
"work_performed": null,
"customer_notes": 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"
},
"deal_id": null,
"appointment_id": null,
"quote_id": null,
"invoice_id": null,
"assigned_user_id": "usr_0k3j9x21m4zq8p",
"language": "nl",
"currency": "EUR",
"subtotal": "90.00",
"tax_amount": "18.90",
"total": "108.90",
"scheduled_at": "2026-10-01T08:00:00Z",
"started_at": null,
"completed_at": null,
"sent_at": null,
"signature": {
"is_signed": false,
"signed_by_name": null,
"signed_at": null
},
"items": [
{
"object": "work_order_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
"type": "labor",
"name": "Arbeid",
"description": null,
"sku": null,
"quantity": "2.00",
"unit": "uur",
"unit_price": "45.00",
"discount_percentage": "0.00",
"discount_amount": "0.00",
"line_total": "90.00",
"is_taxable": true,
"tax_rate": "21.00",
"minutes": 120
}
],
"checklist": [
{
"object": "work_order_checklist_item",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
"label": "Druk gecontroleerd",
"checked": false,
"required": true,
"note": null
}
],
"photos": [],
"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 bon d’intervention
/api/v1/work-orders/{work_order}
Un bon d’intervention par id, avec lignes, check-list, photos (avec lien de téléchargement) et l’état de la signature.
- Scope
-
work_orders.read— Lire les bons d'intervention, avec le nom, l'adresse et les coordonnées du client - Fonctionnalité requise
work_orders
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
work_order obligatoire |
string (uuid) | L’id (UUID) du bon d’intervention. |
Exemple de requête
curl "https://app.klantly.com/api/v1/work-orders/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', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/work-orders/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/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Réponse 200
{
"data": {
"object": "work_order",
"id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
"number": "WB-2026-00042",
"status": "planned",
"title": "Onderhoud cv-ketel",
"type": "onderhoud",
"location": null,
"description": null,
"work_performed": null,
"customer_notes": 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"
},
"deal_id": null,
"appointment_id": null,
"quote_id": null,
"invoice_id": null,
"assigned_user_id": "usr_0k3j9x21m4zq8p",
"language": "nl",
"currency": "EUR",
"subtotal": "90.00",
"tax_amount": "18.90",
"total": "108.90",
"scheduled_at": "2026-10-01T08:00:00Z",
"started_at": null,
"completed_at": null,
"sent_at": null,
"signature": {
"is_signed": false,
"signed_by_name": null,
"signed_at": null
},
"items": [
{
"object": "work_order_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
"type": "labor",
"name": "Arbeid",
"description": null,
"sku": null,
"quantity": "2.00",
"unit": "uur",
"unit_price": "45.00",
"discount_percentage": "0.00",
"discount_amount": "0.00",
"line_total": "90.00",
"is_taxable": true,
"tax_rate": "21.00",
"minutes": 120
}
],
"checklist": [
{
"object": "work_order_checklist_item",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
"label": "Druk gecontroleerd",
"checked": false,
"required": true,
"note": null
}
],
"photos": [],
"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 bon d’intervention
/api/v1/work-orders
Crée un bon d’intervention pour un client, en brouillon ou planifié. Klantly lui attribue un numéro et calcule les totaux ; le nom, l’adresse et les coordonnées viennent du client.
- Scope
-
work_orders.write— Créer et modifier des bons d'intervention, et changer leur statut (terminer peut envoyer une demande d'avis) - Fonctionnalité requise
work_orders
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 viennent du client. |
status
facultatif
|
string | draft (brouillon), planned (planifié), in_progress (en cours), completed (terminé), invoiced (facturé) ou cancelled (annulé). À la création : draft ou planned. l'une des valeurs : draft, planned |
title
facultatif
|
string | Titre. peut être vide (null) · au maximum 255 caractères |
type
facultatif
|
string | Type de travail, par exemple installation, réparation ou entretien. peut être vide (null) · au maximum 64 caractères |
location
facultatif
|
string | Lieu de l’intervention, s’il ne s’agit pas de l’adresse du client. peut être vide (null) · au maximum 255 caractères |
description
facultatif
|
string | Description du travail ou de la réclamation. peut être vide (null) · au maximum 20000 caractères |
work_performed
facultatif
|
string | Ce qui a été fait. peut être vide (null) · au maximum 20000 caractères |
customer_notes
facultatif
|
string | Remarques pour le client ; figurent sur le bon. peut être vide (null) · au maximum 20000 caractères |
scheduled_at
facultatif
|
string (date-time) | Date prévue de l’intervention (UTC). En entrée : ISO 8601 avec fuseau horaire. peut être vide (null) |
assigned_user_id
facultatif
|
string | Le technicien ou l’utilisateur qui réalise le travail, ou null. peut être vide (null) |
appointment_id
facultatif
|
string (uuid) | Le rendez-vous auquel le bon appartient, ou null. peut être vide (null) |
quote_id
facultatif
|
string (uuid) | Le devis dont provient le bon, ou null. peut être vide (null) |
language
facultatif
|
string | Langue du bon : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr |
items
facultatif
|
array | Les lignes (200 au maximum). En entrée : une liste avec par ligne name (obligatoire) et en option type, description, sku, quantity, unit, unit_price, discount_percentage, discount_amount, tax_rate, is_taxable et minutes ; la liste remplace toutes les lignes. |
checklist
facultatif
|
array | La check-list (200 points au maximum). En entrée : par point label (obligatoire) et en option checked, required et note ; la liste remplace toute la check-list. |
Exemple de requête
curl -X POST "https://app.klantly.com/api/v1/work-orders" \
-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": "Onderhoud cv-ketel",
"status": "planned",
"scheduled_at": "2026-10-01T08:00:00Z",
"items": [
{
"type": "labor",
"name": "Arbeid",
"quantity": 2,
"unit_price": "45.00"
}
]
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'work-orders', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
'json' => [
'customer_id' => '9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70',
'title' => 'Onderhoud cv-ketel',
'status' => 'planned',
'scheduled_at' => '2026-10-01T08:00:00Z',
'items' => [
0 => [
'type' => 'labor',
'name' => 'Arbeid',
'quantity' => 2,
'unit_price' => '45.00',
],
],
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/work-orders', {
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": "Onderhoud cv-ketel",
"status": "planned",
"scheduled_at": "2026-10-01T08:00:00Z",
"items": [
{
"type": "labor",
"name": "Arbeid",
"quantity": 2,
"unit_price": "45.00"
}
]
}),
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/work-orders",
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": "Onderhoud cv-ketel",
"status": "planned",
"scheduled_at": "2026-10-01T08:00:00Z",
"items": [
{
"type": "labor",
"name": "Arbeid",
"quantity": 2,
"unit_price": "45.00"
}
]
},
)
data = response.json()["data"]Réponse 201
{
"data": {
"object": "work_order",
"id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
"number": "WB-2026-00042",
"status": "planned",
"title": "Onderhoud cv-ketel",
"type": "onderhoud",
"location": null,
"description": null,
"work_performed": null,
"customer_notes": 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"
},
"deal_id": null,
"appointment_id": null,
"quote_id": null,
"invoice_id": null,
"assigned_user_id": "usr_0k3j9x21m4zq8p",
"language": "nl",
"currency": "EUR",
"subtotal": "90.00",
"tax_amount": "18.90",
"total": "108.90",
"scheduled_at": "2026-10-01T08:00:00Z",
"started_at": null,
"completed_at": null,
"sent_at": null,
"signature": {
"is_signed": false,
"signed_by_name": null,
"signed_at": null
},
"items": [
{
"object": "work_order_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
"type": "labor",
"name": "Arbeid",
"description": null,
"sku": null,
"quantity": "2.00",
"unit": "uur",
"unit_price": "45.00",
"discount_percentage": "0.00",
"discount_amount": "0.00",
"line_total": "90.00",
"is_taxable": true,
"tax_rate": "21.00",
"minutes": 120
}
],
"checklist": [
{
"object": "work_order_checklist_item",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
"label": "Druk gecontroleerd",
"checked": false,
"required": true,
"note": null
}
],
"photos": [],
"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. -
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 bon d’intervention
/api/v1/work-orders/{work_order}
Ne modifie que les champs envoyés. items et checklist remplacent l’ensemble des lignes et de la check-list ; les lignes reçoivent de nouveaux id. Une ligne et le total ne peuvent pas dépasser 99 999 999,99. Un bon d’intervention facturé ne peut plus être modifié.
- Scope
-
work_orders.write— Créer et modifier des bons d'intervention, et changer leur statut (terminer peut envoyer une demande d'avis) - Fonctionnalité requise
work_orders
Envoyez l'ETag dans If-Match : vous n'écraserez jamais par erreur une version plus récente.
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
work_order obligatoire |
string (uuid) | L’id (UUID) du bon d’intervention. |
Corps (JSON)
| Champ | Type | Description |
|---|---|---|
title
facultatif
|
string | Titre. peut être vide (null) · au maximum 255 caractères |
type
facultatif
|
string | Type de travail, par exemple installation, réparation ou entretien. peut être vide (null) · au maximum 64 caractères |
location
facultatif
|
string | Lieu de l’intervention, s’il ne s’agit pas de l’adresse du client. peut être vide (null) · au maximum 255 caractères |
description
facultatif
|
string | Description du travail ou de la réclamation. peut être vide (null) · au maximum 20000 caractères |
work_performed
facultatif
|
string | Ce qui a été fait. peut être vide (null) · au maximum 20000 caractères |
customer_notes
facultatif
|
string | Remarques pour le client ; figurent sur le bon. peut être vide (null) · au maximum 20000 caractères |
scheduled_at
facultatif
|
string (date-time) | Date prévue de l’intervention (UTC). En entrée : ISO 8601 avec fuseau horaire. peut être vide (null) |
assigned_user_id
facultatif
|
string | Le technicien ou l’utilisateur qui réalise le travail, ou null. peut être vide (null) |
appointment_id
facultatif
|
string (uuid) | Le rendez-vous auquel le bon appartient, ou null. peut être vide (null) |
quote_id
facultatif
|
string (uuid) | Le devis dont provient le bon, ou null. peut être vide (null) |
language
facultatif
|
string | Langue du bon : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr |
items
facultatif
|
array | Les lignes (200 au maximum). En entrée : une liste avec par ligne name (obligatoire) et en option type, description, sku, quantity, unit, unit_price, discount_percentage, discount_amount, tax_rate, is_taxable et minutes ; la liste remplace toutes les lignes. |
checklist
facultatif
|
array | La check-list (200 points au maximum). En entrée : par point label (obligatoire) et en option checked, required et note ; la liste remplace toute la check-list. |
Exemple de requête
curl -X PATCH "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"work_performed": "Filter vervangen en de ketel ontlucht."
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('PATCH', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', [
'json' => [
'work_performed' => 'Filter vervangen en de ketel ontlucht.',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"work_performed": "Filter vervangen en de ketel ontlucht."
}),
});
const { data } = await response.json();import os
import requests
response = requests.patch(
"https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
json={
"work_performed": "Filter vervangen en de ketel ontlucht."
},
)
data = response.json()["data"]Réponse 200
{
"data": {
"object": "work_order",
"id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
"number": "WB-2026-00042",
"status": "planned",
"title": "Onderhoud cv-ketel",
"type": "onderhoud",
"location": null,
"description": null,
"work_performed": null,
"customer_notes": 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"
},
"deal_id": null,
"appointment_id": null,
"quote_id": null,
"invoice_id": null,
"assigned_user_id": "usr_0k3j9x21m4zq8p",
"language": "nl",
"currency": "EUR",
"subtotal": "90.00",
"tax_amount": "18.90",
"total": "108.90",
"scheduled_at": "2026-10-01T08:00:00Z",
"started_at": null,
"completed_at": null,
"sent_at": null,
"signature": {
"is_signed": false,
"signed_by_name": null,
"signed_at": null
},
"items": [
{
"object": "work_order_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
"type": "labor",
"name": "Arbeid",
"description": null,
"sku": null,
"quantity": "2.00",
"unit": "uur",
"unit_price": "45.00",
"discount_percentage": "0.00",
"discount_amount": "0.00",
"line_total": "90.00",
"is_taxable": true,
"tax_rate": "21.00",
"minutes": 120
}
],
"checklist": [
{
"object": "work_order_checklist_item",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
"label": "Druk gecontroleerd",
"checked": false,
"required": true,
"note": null
}
],
"photos": [],
"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.
Changer le statut d’un bon d’intervention
/api/v1/work-orders/{work_order}/status
Passe le bon d’intervention en brouillon, planifié, en cours, terminé ou annulé. Pour en cours et terminé, started_at et completed_at sont renseignés. Comme dans l’application, terminer peut envoyer une demande d’avis au client si l’entreprise l’a configuré. Seul Klantly passe un bon au statut facturé. Si les paramètres exigent une signature, un bon non signé ne peut pas passer à terminé (invalid_state_transition) ; cela vaut aussi à la création.
- Scope
-
work_orders.write— Créer et modifier des bons d'intervention, et changer leur statut (terminer peut envoyer une demande d'avis) - Fonctionnalité requise
work_orders
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 |
|---|---|---|
work_order obligatoire |
string (uuid) | L’id (UUID) du bon d’intervention. |
Corps (JSON)
| Champ | Type | Description |
|---|---|---|
status
obligatoire
|
string | draft (brouillon), planned (planifié), in_progress (en cours), completed (terminé), invoiced (facturé) ou cancelled (annulé). À la création : draft ou planned. l'une des valeurs : draft, planned, in_progress, completed, cancelled |
Exemple de requête
curl -X POST "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/status" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
-d '{
"status": "completed"
}'$client = new \GuzzleHttp\Client([
'base_uri' => 'https://app.klantly.com/api/v1/',
'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);
$response = $client->request('POST', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/status', [
'headers' => [
'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
],
'json' => [
'status' => 'completed',
],
]);
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/status', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
},
body: JSON.stringify({
"status": "completed"
}),
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/status",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
"Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
},
json={
"status": "completed"
},
)
data = response.json()["data"]Réponse 200
{
"data": {
"object": "work_order",
"id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
"number": "WB-2026-00042",
"status": "planned",
"title": "Onderhoud cv-ketel",
"type": "onderhoud",
"location": null,
"description": null,
"work_performed": null,
"customer_notes": 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"
},
"deal_id": null,
"appointment_id": null,
"quote_id": null,
"invoice_id": null,
"assigned_user_id": "usr_0k3j9x21m4zq8p",
"language": "nl",
"currency": "EUR",
"subtotal": "90.00",
"tax_amount": "18.90",
"total": "108.90",
"scheduled_at": "2026-10-01T08:00:00Z",
"started_at": null,
"completed_at": null,
"sent_at": null,
"signature": {
"is_signed": false,
"signed_by_name": null,
"signed_at": null
},
"items": [
{
"object": "work_order_item",
"id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
"type": "labor",
"name": "Arbeid",
"description": null,
"sku": null,
"quantity": "2.00",
"unit": "uur",
"unit_price": "45.00",
"discount_percentage": "0.00",
"discount_amount": "0.00",
"line_total": "90.00",
"is_taxable": true,
"tax_rate": "21.00",
"minutes": 120
}
],
"checklist": [
{
"object": "work_order_checklist_item",
"id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
"label": "Druk gecontroleerd",
"checked": false,
"required": true,
"note": null
}
],
"photos": [],
"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. -
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.
Supprimer un bon d’intervention
/api/v1/work-orders/{work_order}
Supprime le bon d’intervention. Un bon d’intervention facturé ne peut pas être supprimé.
- Scope
-
work_orders.delete— Supprimer des bons d'intervention - Fonctionnalité requise
work_orders
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
work_order obligatoire |
string (uuid) | L’id (UUID) du bon d’intervention. |
Exemple de requête
curl -X DELETE "https://app.klantly.com/api/v1/work-orders/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', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/work-orders/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/work-orders/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 « work_order ». |
id |
string (uuid) | Id unique (UUID). |
number |
string | Numéro du bon, par exemple WB-2026-00042 ; attribué par Klantly. |
status |
string | draft (brouillon), planned (planifié), in_progress (en cours), completed (terminé), invoiced (facturé) ou cancelled (annulé). À la création : draft ou planned. l'une des valeurs : draft, planned, in_progress, completed, invoiced, cancelled |
title |
string | Titre. peut être vide (null) |
type |
string | Type de travail, par exemple installation, réparation ou entretien. peut être vide (null) |
location |
string | Lieu de l’intervention, s’il ne s’agit pas de l’adresse du client. peut être vide (null) |
description |
string | Description du travail ou de la réclamation. peut être vide (null) |
work_performed |
string | Ce qui a été fait. peut être vide (null) |
customer_notes |
string | Remarques pour le client ; figurent sur le bon. peut être vide (null) |
customer_id |
string (uuid) | Le client. Obligatoire à la création ; le nom, l’adresse et les coordonnées viennent du client. peut être vide (null) |
customer |
object | Les données du client sur le bon, 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) |
deal_id |
string (uuid) | Le deal du tableau pipeline, ou null. peut être vide (null) |
appointment_id |
string (uuid) | Le rendez-vous auquel le bon appartient, ou null. peut être vide (null) |
quote_id |
string (uuid) | Le devis dont provient le bon, ou null. peut être vide (null) |
invoice_id |
string (uuid) | La facture du bon, ou null. peut être vide (null) |
assigned_user_id |
string | Le technicien ou l’utilisateur qui réalise le travail, ou null. peut être vide (null) |
language |
string | Langue du bon : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr |
currency |
string | Toujours « EUR ». |
subtotal |
string | Total hors TVA, sous forme de texte avec deux décimales. |
tax_amount |
string | Montant de la TVA. |
total |
string | Total TVA comprise. |
scheduled_at |
string (date-time) | Date prévue de l’intervention (UTC). En entrée : ISO 8601 avec fuseau horaire. peut être vide (null) |
started_at |
string (date-time) | Début de l’intervention. peut être vide (null) |
completed_at |
string (date-time) | Fin de l’intervention. peut être vide (null) |
sent_at |
string (date-time) | Date d’envoi du bon au client. peut être vide (null) |
signature |
object | La signature du client (données uniquement, pas d’image). |
signature.is_signed |
boolean | Indique si le bon est signé. |
signature.signed_by_name |
string | Nom du signataire. peut être vide (null) |
signature.signed_at |
string (date-time) | Date de la signature. peut être vide (null) |
items |
array<object> | Les lignes (200 au maximum). En entrée : une liste avec par ligne name (obligatoire) et en option type, description, sku, quantity, unit, unit_price, discount_percentage, discount_amount, tax_rate, is_taxable et minutes ; la liste remplace toutes les lignes. |
items.object |
string | Toujours « work_order_item ». |
items.id |
string (uuid) | Id de la ligne. Change à chaque envoi de items (les lignes sont remplacées). |
items.type |
string | labor (main-d’œuvre), material (matériel) ou other. l'une des valeurs : labor, material, other |
items.name |
string | Libellé sur le bon. |
items.description |
string | Précision. 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 heure ou pièce. peut être vide (null) |
items.unit_price |
string | Prix unitaire hors TVA. |
items.discount_percentage |
string | Remise en pourcentage. |
items.discount_amount |
string | Remise fixe sur la ligne hors TVA ; ne compte que si discount_percentage vaut 0. |
items.line_total |
string | Total de la ligne hors TVA ; calculé par Klantly. |
items.is_taxable |
boolean | Indique si la ligne est soumise à la TVA. |
items.tax_rate |
string | Taux de TVA en pourcentage (21 par défaut, même si l’entreprise applique un autre taux : envoyez-le alors). |
items.minutes |
integer | Minutes travaillées, pour la main-d’œuvre. peut être vide (null) |
checklist |
array<object> | La check-list (200 points au maximum). En entrée : par point label (obligatoire) et en option checked, required et note ; la liste remplace toute la check-list. |
checklist.object |
string | Toujours « work_order_checklist_item ». |
checklist.id |
string (uuid) | Id du point. |
checklist.label |
string | Le point, par exemple « Pression contrôlée ». |
checklist.checked |
boolean | Coché. |
checklist.required |
boolean | Doit obligatoirement être coché. |
checklist.note |
string | Remarque sur le point. peut être vide (null) |
photos |
array<object> | Les photos du bon d'intervention, chacune avec un lien de téléchargement signé. Vous les ajoutez et les supprimez avec Téléverser une photo de bon d'intervention et Supprimer une photo de bon d'intervention. |
photos.object |
string | Toujours « work_order_photo ». |
photos.id |
string (uuid) | Id de la photo. |
photos.kind |
string | before (avant), after (après) ou other. peut être vide (null) · l'une des valeurs : before, after, other |
photos.caption |
string | Légende. peut être vide (null) |
photos.download_url |
string (uri) | Un lien signé vers la photo, valable un quart d'heure et ouvrable sans clé API. |
photos.created_at |
string (date-time) | Téléversée le (UTC). |
created_at |
string (date-time) | Créé le (UTC). |
updated_at |
string (date-time) | Dernière modification (UTC). |