API-Referenz
Anhänge
Dateien zu einem Angebot und Fotos zu einem Arbeitsauftrag: abrufen, hochladen und löschen. Der Download läuft über einen signierten Link, der fünfzehn Minuten gültig ist und keinen API-Schlüssel braucht.
Endpunkte
-
GET
/quotes/{quote}/attachmentsAnhänge abrufen -
POST
/quotes/{quote}/attachmentsAnhang hochladen -
DELETE
/quotes/{quote}/attachments/{attachment}Anhang löschen -
GET
/work-orders/{work_order}/photosFotos des Arbeitsauftrags abrufen -
POST
/work-orders/{work_order}/photosFoto zum Arbeitsauftrag hochladen -
DELETE
/work-orders/{work_order}/photos/{photo}Foto des Arbeitsauftrags löschen
Anhänge abrufen
/api/v1/quotes/{quote}/attachments
Alle Anhänge dieses Angebots, älteste zuerst, jeweils mit einem signierten Download-Link.
- Scope
-
quotes.read— Angebote lesen, mit Positionen, Beträgen und den Kundendaten darauf - Erforderliche Funktion
quotes
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die id (UUID) des Angebots. |
Beispielanfrage
curl "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/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"]Antwort 200
Die Antwort ist eine Liste mit Cursor-Paginierung: data enthält die Objekte, meta die Paginierung.
{
"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
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden.
Anhang hochladen
/api/v1/quotes/{quote}/attachments
Fügt dem Angebot eine Datei hinzu. Senden Sie sie als multipart/form-data im Feld file — zusammen mit dem Arbeitsauftrag-Foto ist das der einzige Endpunkt, der kein JSON erwartet. Erlaubt sind pdf, jpg, jpeg, png, gif, webp, doc, docx, xls und xlsx, bis 10 MB pro Datei und 20 Anhänge pro Angebot; darüber folgt 403 limit_reached. Der Dateityp wird am Inhalt geprüft, nicht am Namen.
- Scope
-
quotes.write— Angebote erstellen und bearbeiten (nur Entwürfe) sowie im Namen des Kunden annehmen oder ablehnen - Erforderliche Funktion
quotes
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die id (UUID) des Angebots. |
Body (JSON)
| Feld | Typ | Beschreibung |
|---|---|---|
file
erforderlich
|
string (binary) | Die Datei selbst, als multipart/form-data. Bis 10 MB; erlaubt sind pdf, jpg, jpeg, png, gif, webp, doc, docx, xls und xlsx. |
description
optional
|
string | Eigene Beschreibung des Anhangs. kann leer sein (null) · höchstens 255 Zeichen |
visible_to_customer
optional
|
boolean | True, wenn der Kunde den Anhang mit dem Angebot erhält. |
Beispielanfrage
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"]Antwort 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"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
422
validation_failed— Die Eingabe ist ungültig. -
422
unknown_field— Die Eingabe enthält ein unbekanntes Feld. -
415
unsupported_media_type— Dieses Format wird nicht unterstützt. -
413
payload_too_large— Der Body der Anfrage ist zu groß. -
404
not_found— Nicht gefunden. -
403
limit_reached— Das Limit des Abonnements ist erreicht. -
413
payload_too_large— Der Body der Anfrage ist zu groß. -
415
unsupported_media_type— Dieses Format wird nicht unterstützt.
Anhang löschen
/api/v1/quotes/{quote}/attachments/{attachment}
Löscht den Anhang und die Datei selbst. Das lässt sich nicht rückgängig machen.
- Scope
-
quotes.write— Angebote erstellen und bearbeiten (nur Entwürfe) sowie im Namen des Kunden annehmen oder ablehnen - Erforderliche Funktion
quotes
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
quote erforderlich |
string (uuid) | Die id (UUID) des Angebots. |
attachment erforderlich |
string (uuid) | Die id (UUID) des Anhangs. |
Beispielanfrage
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"]Antwort 200
{
"data": {
"object": "note",
"id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
"deleted": true
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden.
Fotos des Arbeitsauftrags abrufen
/api/v1/work-orders/{work_order}/photos
Alle Fotos dieses Arbeitsauftrags in der Reihenfolge, in der sie darauf stehen, jeweils mit einem signierten Download-Link.
- Scope
-
work_orders.read— Arbeitsaufträge lesen, mit Name, Adresse und Kontaktdaten des Kunden - Erforderliche Funktion
work_orders
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
work_order erforderlich |
string (uuid) | Die ID (UUID) des Arbeitsauftrags. |
Beispielanfrage
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"]Antwort 200
Die Antwort ist eine Liste mit Cursor-Paginierung: data enthält die Objekte, meta die Paginierung.
{
"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
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden.
Foto zum Arbeitsauftrag hochladen
/api/v1/work-orders/{work_order}/photos
Fügt dem Arbeitsauftrag ein Foto hinzu; es erscheint auch im PDF. Senden Sie es als multipart/form-data im Feld file. Nur Bilder (jpg, jpeg, png, gif, webp), bis 10 MB pro Foto und 30 Fotos pro Arbeitsauftrag. Mit kind geben Sie an, ob das Foto von vor oder nach der Arbeit ist.
- Scope
-
work_orders.write— Arbeitsaufträge erstellen und bearbeiten sowie den Status ändern (Abschließen kann eine Bewertungsanfrage senden) - Erforderliche Funktion
work_orders
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
work_order erforderlich |
string (uuid) | Die ID (UUID) des Arbeitsauftrags. |
Body (JSON)
| Feld | Typ | Beschreibung |
|---|---|---|
file
erforderlich
|
string (binary) | Das Foto selbst, als multipart/form-data. Bis 10 MB; erlaubt sind jpg, jpeg, png, gif und webp. |
kind
optional
|
string | before (vor der Arbeit), after (danach) oder other. einer von: before, after, other |
caption
optional
|
string | Bildunterschrift zum Foto. kann leer sein (null) · höchstens 255 Zeichen |
Beispielanfrage
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"]Antwort 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"
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
422
validation_failed— Die Eingabe ist ungültig. -
422
unknown_field— Die Eingabe enthält ein unbekanntes Feld. -
415
unsupported_media_type— Dieses Format wird nicht unterstützt. -
413
payload_too_large— Der Body der Anfrage ist zu groß. -
404
not_found— Nicht gefunden. -
403
limit_reached— Das Limit des Abonnements ist erreicht. -
413
payload_too_large— Der Body der Anfrage ist zu groß. -
415
unsupported_media_type— Dieses Format wird nicht unterstützt.
Foto des Arbeitsauftrags löschen
/api/v1/work-orders/{work_order}/photos/{photo}
Löscht das Foto und die Datei selbst. Das lässt sich nicht rückgängig machen.
- Scope
-
work_orders.write— Arbeitsaufträge erstellen und bearbeiten sowie den Status ändern (Abschließen kann eine Bewertungsanfrage senden) - Erforderliche Funktion
work_orders
Pfadparameter
| Name | Typ | Beschreibung |
|---|---|---|
work_order erforderlich |
string (uuid) | Die ID (UUID) des Arbeitsauftrags. |
photo erforderlich |
string (uuid) | Die id (UUID) des Fotos. |
Beispielanfrage
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"]Antwort 200
{
"data": {
"object": "note",
"id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
"deleted": true
}
}Mögliche Fehler
-
403
insufficient_scope— Dieser API-Schlüssel hat keinen Zugriff auf diese Aktion. -
404
not_found— Nicht gefunden.
Das Objekt
Alle Felder sind immer vorhanden; ein Feld ohne Wert ist null.
| Feld | Typ | Beschreibung |
|---|---|---|
object |
string | Immer "attachment". |
id |
string (uuid) | Id des Anhangs. |
quote_id |
string (uuid) | Das Angebot, zu dem der Anhang gehört. |
name |
string | Der Dateiname, wie er hochgeladen wurde. kann leer sein (null) |
mime_type |
string | Der Dateityp, etwa application/pdf. kann leer sein (null) |
size |
integer | Die Größe in Bytes. kann leer sein (null) |
description |
string | Eigene Beschreibung des Anhangs. kann leer sein (null) |
visible_to_customer |
boolean | True, wenn der Kunde den Anhang mit dem Angebot erhält. |
download_url |
string (uri) | Ein signierter Link zur Datei, fünfzehn Minuten gültig und ohne API-Schlüssel zu öffnen. Nach Ablauf einfach neu abrufen. |
created_at |
string (date-time) | Wann der Anhang hinzugefügt wurde (UTC). |