API-referentie
Bijlagen
Bestanden bij een offerte en foto's bij een werkbon: opvragen, uploaden en verwijderen. Downloaden gaat via een ondertekende link die een kwartier geldig is en geen API-sleutel nodig heeft.
Endpoints
-
GET
/quotes/{quote}/attachmentsBijlagen opvragen -
POST
/quotes/{quote}/attachmentsBijlage uploaden -
DELETE
/quotes/{quote}/attachments/{attachment}Bijlage verwijderen -
GET
/work-orders/{work_order}/photosWerkbonfoto's opvragen -
POST
/work-orders/{work_order}/photosWerkbonfoto uploaden -
DELETE
/work-orders/{work_order}/photos/{photo}Werkbonfoto verwijderen
Bijlagen opvragen
/api/v1/quotes/{quote}/attachments
Alle bijlagen bij deze offerte, oudste eerst, met per bestand een ondertekende downloadlink.
- Scope
-
quotes.read— Offertes lezen, met regels, bedragen en de klantgegevens erop - Vereiste functie
quotes
Padparameters
| Naam | Type | Omschrijving |
|---|---|---|
quote verplicht |
string (uuid) | De id (UUID) van de offerte. |
Voorbeeldverzoek
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"]Antwoord 200
Het antwoord is een lijst met cursorpaginering: data bevat de objecten, meta de paginering.
{
"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
}
}Mogelijke fouten
-
403
insufficient_scope— Deze API-sleutel heeft geen toegang tot deze actie. -
404
not_found— Niet gevonden.
Bijlage uploaden
/api/v1/quotes/{quote}/attachments
Voegt een bestand toe aan de offerte. Verstuur het als multipart/form-data met het veld file — dit is samen met de werkbonfoto het enige endpoint dat geen JSON verwacht. Toegestaan zijn pdf, jpg, jpeg, png, gif, webp, doc, docx, xls en xlsx, tot 10 MB per bestand en 20 bijlagen per offerte; daarboven volgt 403 limit_reached. Het bestandstype wordt op de inhoud gecontroleerd, niet op de naam.
- Scope
-
quotes.write— Offertes aanmaken en wijzigen (alleen concepten), en accepteren of afwijzen namens de klant - Vereiste functie
quotes
Padparameters
| Naam | Type | Omschrijving |
|---|---|---|
quote verplicht |
string (uuid) | De id (UUID) van de offerte. |
Body (JSON)
| Veld | Type | Omschrijving |
|---|---|---|
file
verplicht
|
string (binary) | Het bestand zelf, als multipart/form-data. Maximaal 10 MB; toegestaan zijn pdf, jpg, jpeg, png, gif, webp, doc, docx, xls en xlsx. |
description
optioneel
|
string | Eigen omschrijving bij de bijlage. kan leeg zijn (null) · maximaal 255 tekens |
visible_to_customer
optioneel
|
boolean | True als de klant de bijlage bij de offerte krijgt. |
Voorbeeldverzoek
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"]Antwoord 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"
}
}Mogelijke fouten
-
403
insufficient_scope— Deze API-sleutel heeft geen toegang tot deze actie. -
422
validation_failed— De invoer is ongeldig. -
422
unknown_field— De invoer bevat een onbekend veld. -
415
unsupported_media_type— Dit formaat wordt niet ondersteund. -
413
payload_too_large— De body van het verzoek is te groot. -
404
not_found— Niet gevonden. -
403
limit_reached— De limiet van het abonnement is bereikt. -
413
payload_too_large— De body van het verzoek is te groot. -
415
unsupported_media_type— Dit formaat wordt niet ondersteund.
Bijlage verwijderen
/api/v1/quotes/{quote}/attachments/{attachment}
Verwijdert de bijlage en het bestand zelf. Dit kan niet ongedaan worden gemaakt.
- Scope
-
quotes.write— Offertes aanmaken en wijzigen (alleen concepten), en accepteren of afwijzen namens de klant - Vereiste functie
quotes
Padparameters
| Naam | Type | Omschrijving |
|---|---|---|
quote verplicht |
string (uuid) | De id (UUID) van de offerte. |
attachment verplicht |
string (uuid) | De id (UUID) van de bijlage. |
Voorbeeldverzoek
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"]Antwoord 200
{
"data": {
"object": "note",
"id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
"deleted": true
}
}Mogelijke fouten
-
403
insufficient_scope— Deze API-sleutel heeft geen toegang tot deze actie. -
404
not_found— Niet gevonden.
Werkbonfoto's opvragen
/api/v1/work-orders/{work_order}/photos
Alle foto's bij deze werkbon, in de volgorde waarin ze op de werkbon staan, met per foto een ondertekende downloadlink.
- Scope
-
work_orders.read— Werkbonnen lezen, met naam, adres en contactgegevens van de klant - Vereiste functie
work_orders
Padparameters
| Naam | Type | Omschrijving |
|---|---|---|
work_order verplicht |
string (uuid) | De id (UUID) van de werkbon. |
Voorbeeldverzoek
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"]Antwoord 200
Het antwoord is een lijst met cursorpaginering: data bevat de objecten, meta de paginering.
{
"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
}
}Mogelijke fouten
-
403
insufficient_scope— Deze API-sleutel heeft geen toegang tot deze actie. -
404
not_found— Niet gevonden.
Werkbonfoto uploaden
/api/v1/work-orders/{work_order}/photos
Voegt een foto toe aan de werkbon; hij komt ook in de pdf. Verstuur hem als multipart/form-data met het veld file. Alleen afbeeldingen (jpg, jpeg, png, gif, webp), tot 10 MB per foto en 30 foto’s per werkbon. Met kind geef je aan of de foto van vóór of ná het werk is.
- Scope
-
work_orders.write— Werkbonnen aanmaken en wijzigen, en de status aanpassen (afronden kan een reviewverzoek sturen) - Vereiste functie
work_orders
Padparameters
| Naam | Type | Omschrijving |
|---|---|---|
work_order verplicht |
string (uuid) | De id (UUID) van de werkbon. |
Body (JSON)
| Veld | Type | Omschrijving |
|---|---|---|
file
verplicht
|
string (binary) | De foto zelf, als multipart/form-data. Maximaal 10 MB; toegestaan zijn jpg, jpeg, png, gif en webp. |
kind
optioneel
|
string | before (vóór het werk), after (erna) of other. een van: before, after, other |
caption
optioneel
|
string | Bijschrift bij de foto. kan leeg zijn (null) · maximaal 255 tekens |
Voorbeeldverzoek
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"]Antwoord 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"
}
}Mogelijke fouten
-
403
insufficient_scope— Deze API-sleutel heeft geen toegang tot deze actie. -
422
validation_failed— De invoer is ongeldig. -
422
unknown_field— De invoer bevat een onbekend veld. -
415
unsupported_media_type— Dit formaat wordt niet ondersteund. -
413
payload_too_large— De body van het verzoek is te groot. -
404
not_found— Niet gevonden. -
403
limit_reached— De limiet van het abonnement is bereikt. -
413
payload_too_large— De body van het verzoek is te groot. -
415
unsupported_media_type— Dit formaat wordt niet ondersteund.
Werkbonfoto verwijderen
/api/v1/work-orders/{work_order}/photos/{photo}
Verwijdert de foto en het bestand zelf. Dit kan niet ongedaan worden gemaakt.
- Scope
-
work_orders.write— Werkbonnen aanmaken en wijzigen, en de status aanpassen (afronden kan een reviewverzoek sturen) - Vereiste functie
work_orders
Padparameters
| Naam | Type | Omschrijving |
|---|---|---|
work_order verplicht |
string (uuid) | De id (UUID) van de werkbon. |
photo verplicht |
string (uuid) | De id (UUID) van de foto. |
Voorbeeldverzoek
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"]Antwoord 200
{
"data": {
"object": "note",
"id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
"deleted": true
}
}Mogelijke fouten
-
403
insufficient_scope— Deze API-sleutel heeft geen toegang tot deze actie. -
404
not_found— Niet gevonden.
Het object
Alle velden zijn altijd aanwezig; een veld zonder waarde is null.
| Veld | Type | Omschrijving |
|---|---|---|
object |
string | Altijd "attachment". |
id |
string (uuid) | Id van de bijlage. |
quote_id |
string (uuid) | De offerte waar de bijlage bij hoort. |
name |
string | De bestandsnaam zoals hij werd geüpload. kan leeg zijn (null) |
mime_type |
string | Het bestandstype, zoals application/pdf. kan leeg zijn (null) |
size |
integer | De grootte in bytes. kan leeg zijn (null) |
description |
string | Eigen omschrijving bij de bijlage. kan leeg zijn (null) |
visible_to_customer |
boolean | True als de klant de bijlage bij de offerte krijgt. |
download_url |
string (uri) | Een ondertekende link naar het bestand, een kwartier geldig en zonder API-sleutel te openen. Vraag hem opnieuw op als hij verlopen is. |
created_at |
string (date-time) | Wanneer de bijlage werd toegevoegd (UTC). |