Termine aus Ihrer eigenen Planung
Übertragen Sie Termine aus Ihrer eigenen Planungssoftware in den Klantly-Kalender, halten Sie sie aktuell und lassen Sie Klantly dem Kunden eine Bestätigung senden.
Planen Sie in einem eigenen System, zum Beispiel auf einer Plantafel für Ihre Techniker? Mit der API übertragen Sie diese Termine in den Kalender von Klantly. Sie sehen sie dann beim Kunden und auf Ihrem Pipeline-Board, und Ihr Kunde erhält dieselben Bestätigungen und Erinnerungen wie bei einem Termin, den Sie in Klantly selbst anlegen.
Was Sie brauchen
Einen API-Schlüssel mit diesen Scopes:
| Scope | Wofür |
|---|---|
customers.read |
Den Kunden anhand der E-Mail-Adresse suchen. |
customers.write |
Einen Kunden anlegen, der noch nicht existiert. |
appointments.read |
Termine und freie Zeitfenster abrufen. |
appointments.write |
Termine planen, verschieben, absagen und abschließen. |
appointments.send |
Dem Kunden eine Bestätigung, Verschiebung, Absage oder Erinnerung per E-Mail senden. |
Die Funktion Termine muss für Ihr Unternehmen aktiv sein.
Schritt 1: der Kunde
Ein Termin gehört immer zu einem Kunden; Name, E-Mail-Adresse und Telefon kommen vom Kunden. Suchen Sie den Kunden mit filter[email] oder legen Sie ihn an, wie in Website-Formular zum Lead. Speichern Sie seine id in Ihrem eigenen System, dann müssen Sie ihn beim nächsten Mal nicht erneut suchen.
Schritt 2: den Termin planen
curl -X POST "https://app.klantly.com/api/v1/appointments" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: planning-4711" \
-d '{"customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70", "title": "Aufmaß Terrassendach", "starts_at": "2026-10-06T09:00:00+02:00", "ends_at": "2026-10-06T10:30:00+02:00", "location": "Dorfstraße 12, Köln"}'- Senden Sie Zeiten immer mit Zeitzone, etwa
+02:00oderZ. In der Antwort stehen sie in UTC. - Lassen Sie
ends_atweg, dauert der Termin so lange wie die Terminart inappointment_type_id, sonst die Standarddauer aus Ihren Termineinstellungen. - Setzen Sie die ID aus Ihrer eigenen Planung in den
Idempotency-Key. Versucht Ihr Server es nach einem Timeout erneut, entsteht trotzdem nur ein Termin. - Speichern Sie die
idaus der Antwort neben Ihrer eigenen ID. Sie brauchen sie, um den Termin später zu bearbeiten.
Ein neuer Termin steht auf confirmed. Muss er noch bestätigt werden, senden Sie "status": "pending" mit. Klantly prüft beim Planen keine Verfügbarkeit: Ihre Planung ist maßgeblich.
Hinweis
Die API sendet dem Kunden beim Planen keine E-Mail. Das tun Sie bewusst, in Schritt 4. Achtung: Hat Ihr Unternehmen Automationen auf „Termin geplant“ (etwa eine WhatsApp-Bestätigung), laufen diese wie bei einem Termin im Kalender. Schalten Sie sie kurz aus, wenn Sie Ihre Planung zum ersten Mal einlesen.
Schritt 3: verschieben, absagen und abschließen
Ändert sich die Zeit in Ihrer Planung, senden Sie nur die neue Zeit:
curl -X PATCH "https://app.klantly.com/api/v1/appointments/0b6f3a52-7c1d-4e8a-9b2f-5d4c3b2a1f09" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"starts_at": "2026-10-08T13:00:00+02:00"}'Ohne ends_at bleibt die Dauer gleich, und rescheduled_at zeigt, dass der Termin verschoben wurde. Findet ein Termin nicht statt, sagen Sie ihn ab, statt ihn zu löschen: So bleibt er beim Kunden sichtbar.
curl -X POST "https://app.klantly.com/api/v1/appointments/0b6f3a52-7c1d-4e8a-9b2f-5d4c3b2a1f09/cancel" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Kunde ist krank"}'Hat der Besuch stattgefunden, schließen Sie den Termin mit POST /appointments/{id}/complete ab. Steht Ihre Lead-Umwandlung auf „Termin abgeschlossen“, wird ein Lead dabei zum Kunden, genau wie im Kalender.
Schritt 4: den Kunden informieren
curl -X POST "https://app.klantly.com/api/v1/appointments/0b6f3a52-7c1d-4e8a-9b2f-5d4c3b2a1f09/notify" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: planning-4711-confirmation" \
-d '{"message": "confirmation"}'Klantly sendet die E-Mail mit der Vorlage, die Sie in Klantly eingerichtet haben. Die Nachricht muss zum Status des Termins passen:
message |
Möglich bei Status |
|---|---|
confirmation |
confirmed |
reschedule |
pending oder confirmed |
cancellation |
cancelled |
reminder |
confirmed |
Passt sie nicht, hat der Termin kein Datum, hat der Kunde keine E-Mail-Adresse oder hat Ihr Unternehmen die Vorlage ausgeschaltet, erhalten Sie 409 mit dem Code invalid_state_transition. In detail steht der Grund.
In die andere Richtung: Buchungen aus Klantly
Kunden können auch selbst über Ihre Buchungsseite buchen. Um diese in Ihrer Planung zu sehen, hören Sie mit einem Webhook auf appointment.created, appointment.updated, appointment.confirmed, appointment.cancelled, appointment.completed und appointment.deleted. Eine Statusänderung kommt als eigenes Event, nicht als appointment.updated. Jedes Event enthält den ganzen Termin.
Diese Events kommen auch für die Termine, die Sie selbst über die API geplant haben. Erkennen Sie sie an der id, die Sie in Schritt 2 gespeichert haben, sonst stehen sie doppelt in Ihrer Planung.
Freie Zeitfenster abrufen
Möchten Sie in Ihrem eigenen System sehen, wo laut Klantly noch Platz ist, rufen Sie die freien Zeitfenster ab:
curl --globoff "https://app.klantly.com/api/v1/availability?date_from=2026-10-06&date_to=2026-10-10&duration_minutes=90" \
-H "Authorization: Bearer $KLANTLY_API_KEY"Das sind fast dieselben Zeitfenster wie auf Ihrer Buchungsseite: Arbeitszeiten, Pausen, gesperrte Tage, wie weit im Voraus gebucht werden darf und die bereits gebuchten Termine zählen mit. Nur Ihr Google-Kalender zählt hier nicht, und die Zeiten gelten für das ganze Unternehmen, nicht pro Mitarbeiter. Sie rufen höchstens 31 Tage auf einmal ab.
Alles zusammen
use GuzzleHttp\Client;
/**
* $klantly ist ein Guzzle-Client mit der Basis-URL und Ihrem API-Schlüssel, $job ein Termin aus Ihrer
* eigenen Planung. Gibt die ID des Termins in Klantly zurück: Speichern Sie sie beim Job.
*/
function syncAppointment(Client $klantly, array $job): string
{
$body = [
'title' => $job['title'],
'starts_at' => $job['start']->format(DATE_ATOM),
'ends_at' => $job['end']->format(DATE_ATOM),
'location' => $job['address'],
];
// Neu: planen und dem Kunden eine Bestätigung senden.
if ($job['klantly_id'] === null) {
$appointment = json_decode((string) $klantly->post('appointments', [
'headers' => ['Idempotency-Key' => "planning-{$job['id']}"],
'json' => $body + ['customer_id' => $job['klantly_customer_id']],
])->getBody(), true)['data'];
$klantly->post("appointments/{$appointment['id']}/notify", [
'headers' => ['Idempotency-Key' => "planning-{$job['id']}-confirmation"],
'json' => ['message' => 'confirmation'],
]);
return $appointment['id'];
}
// Vorhanden: bearbeiten und bei einer neuen Zeit dem Kunden die Verschiebung mitteilen.
$klantly->patch("appointments/{$job['klantly_id']}", ['json' => $body]);
if ($job['time_changed']) {
$klantly->post("appointments/{$job['klantly_id']}/notify", [
'json' => ['message' => 'reschedule'],
]);
}
return $job['klantly_id'];
}const BASE_URL = 'https://app.klantly.com/api/v1';
async function klantly(method, path, { body, idempotencyKey } = {}) {
const response = await fetch(`${BASE_URL}/${path}`, {
method,
headers: {
Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
...(body ? { 'Content-Type': 'application/json' } : {}),
...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const json = await response.json();
if (!response.ok) throw new Error(json.detail ?? json.title);
return json;
}
// job ist ein Termin aus Ihrer eigenen Planung; start und end sind ISO 8601 mit Zeitzone.
// Gibt die ID des Termins in Klantly zurück: Speichern Sie sie beim Job.
export async function syncAppointment(job) {
const body = { title: job.title, starts_at: job.start, ends_at: job.end, location: job.address };
// Neu: planen und dem Kunden eine Bestätigung senden.
if (!job.klantlyId) {
const { data } = await klantly('POST', 'appointments', {
body: { ...body, customer_id: job.klantlyCustomerId },
idempotencyKey: `planning-${job.id}`,
});
await klantly('POST', `appointments/${data.id}/notify`, {
body: { message: 'confirmation' },
idempotencyKey: `planning-${job.id}-confirmation`,
});
return data.id;
}
// Vorhanden: bearbeiten und bei einer neuen Zeit dem Kunden die Verschiebung mitteilen.
await klantly('PATCH', `appointments/${job.klantlyId}`, { body });
if (job.timeChanged) {
await klantly('POST', `appointments/${job.klantlyId}/notify`, { body: { message: 'reschedule' } });
}
return job.klantlyId;
}Fehler abfangen
422mitvalidation_failed: zum Beispiel eine Zeit ohne Zeitzone. Inerrorssteht pro Feld, was falsch ist.409mitinvalid_state_transition: zum Beispiel einen abgesagten Termin abschließen oder eine Nachricht, die nicht zum Status passt.429mitrate_limited: Warten Sie die Sekunden ausRetry-Afterab und versuchen Sie es erneut. Lesen Sie Ihre Planung zum ersten Mal ein, verteilen Sie die Anfragen über die Zeit. Siehe Rate limits.
Zuletzt aktualisiert am 15. September 2026