Référence de l'API
Pièces jointes
Fichiers liés à un devis et photos liées à un bon de travail : consulter, téléverser et supprimer. Le téléchargement passe par un lien signé, valable un quart d'heure et sans clé API.
Endpoints
-
GET
/quotes/{quote}/attachmentsConsulter les pièces jointes -
POST
/quotes/{quote}/attachmentsTéléverser une pièce jointe -
DELETE
/quotes/{quote}/attachments/{attachment}Supprimer une pièce jointe -
GET
/work-orders/{work_order}/photosConsulter les photos du bon de travail -
POST
/work-orders/{work_order}/photosTéléverser une photo de bon de travail -
DELETE
/work-orders/{work_order}/photos/{photo}Supprimer une photo de bon de travail
Consulter les pièces jointes
/api/v1/quotes/{quote}/attachments
Toutes les pièces jointes de ce devis, les plus anciennes d’abord, chacune avec un lien de téléchargement signé.
- 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/attachments" \
-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/attachments');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/attachments', {
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/attachments",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
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": "attachment",
"id": "9d3f9e56-af10-4123-b345-e6f7a8b9c0d5",
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"name": "technische-tekening.pdf",
"mime_type": "application/pdf",
"size": 248000,
"description": "Tekening van de dakopbouw",
"visible_to_customer": true,
"download_url": "https://app.klantly.com/api/files/quote-attachment/9d3f9e56-af10-4123-b345-e6f7a8b9c0d5?expires=1789000000&signature=8f1c…",
"created_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. -
404
not_found— Introuvable.
Téléverser une pièce jointe
/api/v1/quotes/{quote}/attachments
Ajoute un fichier au devis. Envoyez-le en multipart/form-data dans le champ file — avec la photo de bon de travail, c'est le seul endpoint qui n'attend pas du JSON. Sont autorisés pdf, jpg, jpeg, png, gif, webp, doc, docx, xls et xlsx, jusqu'à 10 Mo par fichier et 20 pièces jointes par devis ; au-delà, vous recevez 403 limit_reached. Le type de fichier est vérifié d'après le contenu, pas d'après le nom.
- Scope
-
quotes.write— Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client - Fonctionnalité requise
quotes
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
Corps (JSON)
| Champ | Type | Description |
|---|---|---|
file
obligatoire
|
string (binary) | Le fichier lui-même, en multipart/form-data. 10 Mo maximum ; sont autorisés pdf, jpg, jpeg, png, gif, webp, doc, docx, xls et xlsx. |
description
facultatif
|
string | Votre description de la pièce jointe. peut être vide (null) · au maximum 255 caractères |
visible_to_customer
facultatif
|
boolean | True si le client reçoit la pièce jointe avec le devis. |
Exemple de requête
curl -X POST "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/attachments" \
-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('POST', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/attachments');
$data = json_decode((string) $response->getBody(), true)['data'];const response = await fetch('https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/attachments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
const { data } = await response.json();import os
import requests
response = requests.post(
"https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/attachments",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Réponse 201
{
"data": {
"object": "attachment",
"id": "9d3f9e56-af10-4123-b345-e6f7a8b9c0d5",
"quote_id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
"name": "technische-tekening.pdf",
"mime_type": "application/pdf",
"size": 248000,
"description": "Tekening van de dakopbouw",
"visible_to_customer": true,
"download_url": "https://app.klantly.com/api/files/quote-attachment/9d3f9e56-af10-4123-b345-e6f7a8b9c0d5?expires=1789000000&signature=8f1c…",
"created_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. -
413
payload_too_large— Le corps de la requête est trop volumineux. -
415
unsupported_media_type— Ce format n'est pas pris en charge.
Supprimer une pièce jointe
/api/v1/quotes/{quote}/attachments/{attachment}
Supprime la pièce jointe et le fichier lui-même. Cette action est irréversible.
- Scope
-
quotes.write— Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client - Fonctionnalité requise
quotes
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
quote obligatoire |
string (uuid) | L'id (UUID) du devis. |
attachment obligatoire |
string (uuid) | L’id (UUID) de la pièce jointe. |
Exemple de requête
curl -X DELETE "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/attachments/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/attachments/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/attachments/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/attachments/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.
Consulter les photos du bon de travail
/api/v1/work-orders/{work_order}/photos
Toutes les photos de ce bon de travail, dans l’ordre où elles y figurent, chacune avec un lien de téléchargement signé.
- 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/photos" \
-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/photos');
$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/photos', {
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/photos",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
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_photo",
"id": "9d3f9f67-b021-4234-c456-f7a8b9c0d1e6",
"work_order_id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
"kind": "after",
"caption": "Dakgoot na montage",
"download_url": "https://app.klantly.com/api/files/quote-attachment/9d3f9e56-af10-4123-b345-e6f7a8b9c0d5?expires=1789000000&signature=8f1c…",
"created_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. -
404
not_found— Introuvable.
Téléverser une photo de bon de travail
/api/v1/work-orders/{work_order}/photos
Ajoute une photo au bon de travail ; elle figure aussi dans le PDF. Envoyez-la en multipart/form-data dans le champ file. Images uniquement (jpg, jpeg, png, gif, webp), jusqu'à 10 Mo par photo et 30 photos par bon de travail. Avec kind, indiquez si la photo date d'avant ou d'après l'intervention.
- 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
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
work_order obligatoire |
string (uuid) | L’id (UUID) du bon d’intervention. |
Corps (JSON)
| Champ | Type | Description |
|---|---|---|
file
obligatoire
|
string (binary) | La photo elle-même, en multipart/form-data. 10 Mo maximum ; sont autorisés jpg, jpeg, png, gif et webp. |
kind
facultatif
|
string | before (avant l'intervention), after (après) ou other. l'une des valeurs : before, after, other |
caption
facultatif
|
string | Légende de la photo. peut être vide (null) · au maximum 255 caractères |
Exemple de requête
curl -X POST "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/photos" \
-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('POST', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/photos');
$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/photos', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
},
});
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/photos",
headers={
"Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
},
)
data = response.json()["data"]Réponse 201
{
"data": {
"object": "work_order_photo",
"id": "9d3f9f67-b021-4234-c456-f7a8b9c0d1e6",
"work_order_id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
"kind": "after",
"caption": "Dakgoot na montage",
"download_url": "https://app.klantly.com/api/files/quote-attachment/9d3f9e56-af10-4123-b345-e6f7a8b9c0d5?expires=1789000000&signature=8f1c…",
"created_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. -
413
payload_too_large— Le corps de la requête est trop volumineux. -
415
unsupported_media_type— Ce format n'est pas pris en charge.
Supprimer une photo de bon de travail
/api/v1/work-orders/{work_order}/photos/{photo}
Supprime la photo et le fichier lui-même. Cette action est irréversible.
- 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
Paramètres de chemin
| Nom | Type | Description |
|---|---|---|
work_order obligatoire |
string (uuid) | L’id (UUID) du bon d’intervention. |
photo obligatoire |
string (uuid) | L’id (UUID) de la photo. |
Exemple de requête
curl -X DELETE "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/photos/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/photos/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/photos/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/photos/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.
L'objet
Tous les champs sont toujours présents ; un champ sans valeur vaut null.
| Champ | Type | Description |
|---|---|---|
object |
string | Toujours "attachment". |
id |
string (uuid) | Id de la pièce jointe. |
quote_id |
string (uuid) | Le devis auquel la pièce jointe appartient. |
name |
string | Le nom du fichier tel qu’il a été téléversé. peut être vide (null) |
mime_type |
string | Le type de fichier, par exemple application/pdf. peut être vide (null) |
size |
integer | La taille en octets. peut être vide (null) |
description |
string | Votre description de la pièce jointe. peut être vide (null) |
visible_to_customer |
boolean | True si le client reçoit la pièce jointe avec le devis. |
download_url |
string (uri) | Un lien signé vers le fichier, valable un quart d'heure et ouvrable sans clé API. Redemandez-le une fois expiré. |
created_at |
string (date-time) | Quand la pièce jointe a été ajoutée (UTC). |