Afspraken vanuit je eigen planning
Zet afspraken uit je eigen planningssoftware in de agenda van Klantly, houd ze gelijk en laat Klantly de klant een bevestiging sturen.
Plan je in een eigen systeem, bijvoorbeeld een planbord voor je monteurs? Met de API zet je die afspraken in de agenda van Klantly. Je ziet ze dan terug bij de klant en op je pipelinebord, en je klant krijgt dezelfde bevestigingen en herinneringen als bij een afspraak die je in Klantly zelf maakt.
Wat je nodig hebt
Een API-sleutel met deze scopes:
| Scope | Waarvoor |
|---|---|
customers.read |
De klant opzoeken op e-mailadres. |
customers.write |
Een klant aanmaken die nog niet bestaat. |
appointments.read |
Afspraken en vrije tijdsloten opvragen. |
appointments.write |
Afspraken inplannen, verzetten, annuleren en afronden. |
appointments.send |
De klant een bevestiging, verzetting, annulering of herinnering mailen. |
De functie Afspraken moet actief zijn voor je bedrijf.
Stap 1: de klant
Een afspraak hoort altijd bij een klant; naam, e-mailadres en telefoon komen van hem. Zoek de klant op met filter[email] of maak hem aan, zoals in Websiteformulier naar lead. Bewaar zijn id in je eigen systeem, dan hoef je hem de volgende keer niet meer op te zoeken.
Stap 2: de afspraak inplannen
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": "Inmeten veranda", "starts_at": "2026-10-06T09:00:00+02:00", "ends_at": "2026-10-06T10:30:00+02:00", "location": "Dorpsstraat 12, Utrecht"}'- Stuur tijden altijd mét tijdzone, zoals
+02:00ofZ. In het antwoord staan ze in UTC. - Laat je
ends_atweg, dan duurt de afspraak zo lang als het afspraaktype inappointment_type_id, of anders de standaardduur uit je afspraakinstellingen. - Zet de id uit je eigen planning in de
Idempotency-Key. Probeert je server het na een time-out opnieuw, dan ontstaat er toch maar één afspraak. - Bewaar de
iduit het antwoord naast je eigen id. Die heb je nodig om de afspraak later bij te werken.
Een nieuwe afspraak staat op confirmed. Moet hij nog bevestigd worden, stuur dan "status": "pending" mee. Klantly controleert bij het inplannen geen beschikbaarheid: jouw planning is leidend.
Let op
De API stuurt de klant bij het inplannen nog geen mail. Dat doe je bewust, in stap 4. Let op: heeft je bedrijf automations op "afspraak ingepland" (bijvoorbeeld een WhatsApp-bevestiging), dan lopen die wél, net als bij een afspraak in de agenda. Zet ze even uit als je je planning voor het eerst inleest.
Stap 3: verzetten, annuleren en afronden
Verandert de tijd in je planning, stuur dan alleen de nieuwe tijd mee:
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"}'Zonder ends_at blijft de duur gelijk, en rescheduled_at laat zien dat de afspraak is verzet. Gaat een afspraak niet door, annuleer hem dan in plaats van hem te verwijderen: dan blijft hij bij de klant zichtbaar.
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": "Klant is ziek"}'Is het bezoek geweest, rond de afspraak dan af met POST /appointments/{id}/complete. Staat de omzetting van leads in je instellingen op "afspraak afgerond", dan wordt een lead daarbij klant, net als in de agenda.
Stap 4: de klant op de hoogte brengen
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 mailt met het sjabloon dat je in Klantly hebt ingesteld. Het bericht moet passen bij de status van de afspraak:
message |
Kan bij status |
|---|---|
confirmation |
confirmed |
reschedule |
pending of confirmed |
cancellation |
cancelled |
reminder |
confirmed |
Past het niet, heeft de afspraak geen datum, heeft de klant geen e-mailadres of heeft je bedrijf het sjabloon uitgezet, dan krijg je 409 met de code invalid_state_transition. In detail staat waarom.
De andere kant op: boekingen uit Klantly
Klanten kunnen ook zelf boeken via je boekingspagina. Wil je die in je planning zien, luister dan met een webhook naar appointment.created, appointment.updated, appointment.confirmed, appointment.cancelled, appointment.completed en appointment.deleted. Een statuswijziging komt als eigen event, niet als appointment.updated. Elk event bevat de hele afspraak.
Die events komen ook binnen voor de afspraken die je zelf via de API hebt ingepland. Herken ze aan de id die je in stap 2 hebt bewaard, anders staan ze straks dubbel in je planning.
Vrije tijden opvragen
Wil je in je eigen systeem zien waar volgens Klantly nog plek is, vraag dan de vrije tijdsloten op:
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"Dat zijn bijna dezelfde tijdsloten als op je boekingspagina: werktijden, pauzes, geblokkeerde dagen, hoe ver vooruit er geboekt mag worden en de afspraken die er al staan tellen mee. Alleen je Google-agenda telt hier niet mee, en de tijden gelden voor het hele bedrijf, niet per medewerker. Je vraagt maximaal 31 dagen tegelijk op.
Alles samen
use GuzzleHttp\Client;
/**
* $klantly is een Guzzle-client met de basis-URL en je API-sleutel, $job een afspraak uit je eigen
* planning. Geeft de id van de afspraak in Klantly terug: bewaar die bij de 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'],
];
// Nieuw: inplannen en de klant een bevestiging sturen.
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'];
}
// Bestaand: bijwerken, en bij een nieuwe tijd de klant laten weten dat de afspraak is verzet.
$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 is een afspraak uit je eigen planning; start en end zijn ISO 8601 mét tijdzone.
// Geeft de id van de afspraak in Klantly terug: bewaar die bij de job.
export async function syncAppointment(job) {
const body = { title: job.title, starts_at: job.start, ends_at: job.end, location: job.address };
// Nieuw: inplannen en de klant een bevestiging sturen.
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;
}
// Bestaand: bijwerken, en bij een nieuwe tijd de klant laten weten dat de afspraak is verzet.
await klantly('PATCH', `appointments/${job.klantlyId}`, { body });
if (job.timeChanged) {
await klantly('POST', `appointments/${job.klantlyId}/notify`, { body: { message: 'reschedule' } });
}
return job.klantlyId;
}Fouten opvangen
422metvalidation_failed: bijvoorbeeld een tijd zonder tijdzone. Inerrorsstaat per veld wat er mis is.409metinvalid_state_transition: bijvoorbeeld een geannuleerde afspraak afronden, of een bericht dat niet bij de status past.429metrate_limited: wacht het aantal seconden uitRetry-Afteren probeer het opnieuw. Lees je je planning voor het eerst in, verdeel de verzoeken dan over de tijd. Zie Rate limits.
Laatst bijgewerkt op 15 september 2026