Klantly Developers

API reference

Appointments

Appointments in the calendar: schedule, reschedule, confirm, cancel and complete them, and send the customer a message.

Endpoints

List appointments

GET /api/v1/appointments

A list of appointments, newest first. Filter by status, customer, user, start time or modification date. Sort by starts_at for calendar order; appointments without a date (an invitation) are then left out.

Scope
appointments.read — Read appointments (with the customer's name, email and phone), appointment types and availability
Required feature
appointments

Query parameters

NameTypeDescription
limit integer Number of results per page. from 1 to 100 · default: 50
cursor string The next_cursor or prev_cursor from meta of the previous response.
sort string Sort by created_at, updated_at or starts_at; a minus sign in front means descending. With starts_at, appointments without a date are left out. one of: -created_at, created_at, -updated_at, updated_at, -starts_at, starts_at · default: -created_at
filter[status] string Only appointments with this status: pending (not confirmed yet), confirmed, cancelled or completed. one of: pending, confirmed, cancelled, completed
filter[customer_id] string (uuid) Only what belongs to this customer.
filter[user_id] string Only appointments of this user (the id from List users).
filter[starts_from] string (date-time) Only appointments that start at or after this moment: ISO 8601 with a time zone.
filter[starts_until] string (date-time) Only appointments that start before this moment: ISO 8601 with a time zone.
filter[updated_since] string (date-time) Only what changed since this moment: ISO 8601 with a time zone, for example 2026-09-14T10:15:00Z. Useful for synchronising.

Example request

cURL
curl "https://app.klantly.com/api/v1/appointments?filter[starts_from]=2026-10-01T00%3A00%3A00Z&sort=starts_at" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('GET', 'appointments', [
    'query' => [
        'filter[starts_from]' => '2026-10-01T00:00:00Z',
        'sort' => 'starts_at',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments?filter[starts_from]=2026-10-01T00%3A00%3A00Z&sort=starts_at', {
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
  },
});

const { data } = await response.json();
Python
import os

import requests

response = requests.get(
    "https://app.klantly.com/api/v1/appointments",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
    params={
        "filter[starts_from]": "2026-10-01T00:00:00Z",
        "sort": "starts_at"
    },
)
data = response.json()["data"]

Response 200

The response is a list with cursor pagination: data contains the objects, meta the pagination.

Example response
{
  "data": [
    {
      "object": "appointment",
      "id": "9d3f7d83-4f8b-4a0e-9d5c-6b7f8a9bacb4",
      "title": "Inmeten veranda",
      "description": null,
      "status": "confirmed",
      "starts_at": "2026-10-01T08:00:00Z",
      "ends_at": "2026-10-01T09:00:00Z",
      "all_day": false,
      "location": "Dorpsstraat 1, Utrecht",
      "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
      "contact": {
        "name": "Jan de Vries",
        "email": "jan@example.com",
        "phone": "+31 6 12345678"
      },
      "user_id": "usr_0k3j9x21m4zq8p",
      "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5",
      "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
      "quote_id": null,
      "invoice_id": null,
      "notes": null,
      "cancellation_reason": null,
      "confirmed_at": "2026-09-14T10:15:00Z",
      "cancelled_at": null,
      "rescheduled_at": null,
      "created_at": "2026-09-14T10:15:00Z",
      "updated_at": "2026-09-14T10:15:00Z"
    }
  ],
  "meta": {
    "limit": 50,
    "next_cursor": "eyJpZCI6IjlkM2Y2YzFlIn0",
    "prev_cursor": null
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Retrieve an appointment

GET /api/v1/appointments/{appointment}

One appointment by id. The response includes an ETag you can send in If-Match when updating.

Scope
appointments.read — Read appointments (with the customer's name, email and phone), appointment types and availability
Required feature
appointments

Path parameters

NameTypeDescription
appointment required string (uuid) The id (UUID) of the appointment.

Example request

cURL
curl "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('GET', 'appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
  },
});

const { data } = await response.json();
Python
import os

import requests

response = requests.get(
    "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "appointment",
    "id": "9d3f7d83-4f8b-4a0e-9d5c-6b7f8a9bacb4",
    "title": "Inmeten veranda",
    "description": null,
    "status": "confirmed",
    "starts_at": "2026-10-01T08:00:00Z",
    "ends_at": "2026-10-01T09:00:00Z",
    "all_day": false,
    "location": "Dorpsstraat 1, Utrecht",
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "contact": {
      "name": "Jan de Vries",
      "email": "jan@example.com",
      "phone": "+31 6 12345678"
    },
    "user_id": "usr_0k3j9x21m4zq8p",
    "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5",
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "quote_id": null,
    "invoice_id": null,
    "notes": null,
    "cancellation_reason": null,
    "confirmed_at": "2026-09-14T10:15:00Z",
    "cancelled_at": null,
    "rescheduled_at": null,
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Schedule an appointment

POST /api/v1/appointments

Schedules an appointment with a customer; name, email and phone come from the customer. Without ends_at the appointment lasts as long as its appointment type, otherwise the default duration from the appointment settings. Klantly does not check availability here: your planning is leading. The API itself sends the customer no email; use Send the customer a message for that. If the company has automations on "appointment scheduled", those do run, just like for an appointment in the calendar.

Scope
appointments.write — Create, update, confirm, cancel and complete appointments (the company's automations run along)
Required feature
appointments

Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.

Body (JSON)

FieldTypeDescription
customer_id required string (uuid) The customer of the appointment. Required when creating.
title optional string Title of the appointment. can be empty (null) · at most 255 characters · required without appointment_type_id
description optional string Description. can be empty (null) · at most 2000 characters
location optional string Location, for example the address of the customer. can be empty (null) · at most 255 characters
starts_at required string (date-time) Start (UTC). Empty for an invitation where the customer still picks a time. As input: ISO 8601 with a time zone.
ends_at optional string (date-time) End (UTC). Without ends_at when creating: the duration of the appointment type or the default duration. can be empty (null)
all_day optional boolean An all-day appointment. Times in the response are in UTC: convert back to the company's time zone (Europe/Amsterdam) for the date, otherwise an appointment starting at 00:00 falls on the day before.
appointment_type_id optional string (uuid) The appointment type, or null. can be empty (null)
user_id optional string The user who has the appointment, or null. can be empty (null)
status optional string pending (not confirmed yet), confirmed, cancelled or completed. When creating: pending or confirmed (default). one of: pending, confirmed
notes optional string Internal note on the appointment. can be empty (null) · at most 2000 characters

Example request

cURL
curl -X POST "https://app.klantly.com/api/v1/appointments" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
  -d '{
  "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
  "title": "Inmeten veranda",
  "starts_at": "2026-10-01T10:00:00+02:00",
  "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5"
}'
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('POST', 'appointments', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
    'json' => [
        'customer_id' => '9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70',
        'title' => 'Inmeten veranda',
        'starts_at' => '2026-10-01T10:00:00+02:00',
        'appointment_type_id' => '9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
  },
  body: JSON.stringify({
  "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
  "title": "Inmeten veranda",
  "starts_at": "2026-10-01T10:00:00+02:00",
  "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5"
}),
});

const { data } = await response.json();
Python
import os

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/appointments",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
        "Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    },
    json={
        "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
        "title": "Inmeten veranda",
        "starts_at": "2026-10-01T10:00:00+02:00",
        "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5"
    },
)
data = response.json()["data"]

Response 201

Example response
{
  "data": {
    "object": "appointment",
    "id": "9d3f7d83-4f8b-4a0e-9d5c-6b7f8a9bacb4",
    "title": "Inmeten veranda",
    "description": null,
    "status": "confirmed",
    "starts_at": "2026-10-01T08:00:00Z",
    "ends_at": "2026-10-01T09:00:00Z",
    "all_day": false,
    "location": "Dorpsstraat 1, Utrecht",
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "contact": {
      "name": "Jan de Vries",
      "email": "jan@example.com",
      "phone": "+31 6 12345678"
    },
    "user_id": "usr_0k3j9x21m4zq8p",
    "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5",
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "quote_id": null,
    "invoice_id": null,
    "notes": null,
    "cancellation_reason": null,
    "confirmed_at": "2026-09-14T10:15:00Z",
    "cancelled_at": null,
    "rescheduled_at": null,
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Update an appointment

PATCH /api/v1/appointments/{appointment}

Changes only the fields you send. A new starts_at is a reschedule: rescheduled_at is set, and without ends_at the duration stays the same. If an appointment without a date gets a starts_at, ends_at is added as when creating; an ends_at without a start time is not possible (422). To change the status, use confirm, cancel or complete.

Scope
appointments.write — Create, update, confirm, cancel and complete appointments (the company's automations run along)
Required feature
appointments

Send the ETag in If-Match and you will never accidentally overwrite a newer version.

Path parameters

NameTypeDescription
appointment required string (uuid) The id (UUID) of the appointment.

Body (JSON)

FieldTypeDescription
title optional string Title of the appointment. at most 255 characters
description optional string Description. can be empty (null) · at most 2000 characters
location optional string Location, for example the address of the customer. can be empty (null) · at most 255 characters
starts_at optional string (date-time) Start (UTC). Empty for an invitation where the customer still picks a time. As input: ISO 8601 with a time zone.
ends_at optional string (date-time) End (UTC). Without ends_at when creating: the duration of the appointment type or the default duration.
all_day optional boolean An all-day appointment. Times in the response are in UTC: convert back to the company's time zone (Europe/Amsterdam) for the date, otherwise an appointment starting at 00:00 falls on the day before.
appointment_type_id optional string (uuid) The appointment type, or null. can be empty (null)
user_id optional string The user who has the appointment, or null. can be empty (null)
notes optional string Internal note on the appointment. can be empty (null) · at most 2000 characters

Example request

cURL
curl -X PATCH "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "starts_at": "2026-10-02T09:00:00Z"
}'
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('PATCH', 'appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', [
    'json' => [
        'starts_at' => '2026-10-02T09:00:00Z',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
  "starts_at": "2026-10-02T09:00:00Z"
}),
});

const { data } = await response.json();
Python
import os

import requests

response = requests.patch(
    "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
    json={
        "starts_at": "2026-10-02T09:00:00Z"
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "appointment",
    "id": "9d3f7d83-4f8b-4a0e-9d5c-6b7f8a9bacb4",
    "title": "Inmeten veranda",
    "description": null,
    "status": "confirmed",
    "starts_at": "2026-10-01T08:00:00Z",
    "ends_at": "2026-10-01T09:00:00Z",
    "all_day": false,
    "location": "Dorpsstraat 1, Utrecht",
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "contact": {
      "name": "Jan de Vries",
      "email": "jan@example.com",
      "phone": "+31 6 12345678"
    },
    "user_id": "usr_0k3j9x21m4zq8p",
    "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5",
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "quote_id": null,
    "invoice_id": null,
    "notes": null,
    "cancellation_reason": null,
    "confirmed_at": "2026-09-14T10:15:00Z",
    "cancelled_at": null,
    "rescheduled_at": null,
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Confirm an appointment

POST /api/v1/appointments/{appointment}/confirm

Sets an appointment to confirmed. If it is already confirmed, nothing changes.

Scope
appointments.write — Create, update, confirm, cancel and complete appointments (the company's automations run along)
Required feature
appointments

Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.

Path parameters

NameTypeDescription
appointment required string (uuid) The id (UUID) of the appointment.

Example request

cURL
curl -X POST "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/confirm" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f"
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('POST', 'appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/confirm', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/confirm', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
  },
});

const { data } = await response.json();
Python
import os

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/confirm",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
        "Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "appointment",
    "id": "9d3f7d83-4f8b-4a0e-9d5c-6b7f8a9bacb4",
    "title": "Inmeten veranda",
    "description": null,
    "status": "confirmed",
    "starts_at": "2026-10-01T08:00:00Z",
    "ends_at": "2026-10-01T09:00:00Z",
    "all_day": false,
    "location": "Dorpsstraat 1, Utrecht",
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "contact": {
      "name": "Jan de Vries",
      "email": "jan@example.com",
      "phone": "+31 6 12345678"
    },
    "user_id": "usr_0k3j9x21m4zq8p",
    "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5",
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "quote_id": null,
    "invoice_id": null,
    "notes": null,
    "cancellation_reason": null,
    "confirmed_at": "2026-09-14T10:15:00Z",
    "cancelled_at": null,
    "rescheduled_at": null,
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Cancel an appointment

POST /api/v1/appointments/{appointment}/cancel

Cancels the appointment, optionally with a reason. A completed appointment can no longer be cancelled. Automations on "appointment cancelled" run, just like in the calendar.

Scope
appointments.write — Create, update, confirm, cancel and complete appointments (the company's automations run along)
Required feature
appointments

Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.

Path parameters

NameTypeDescription
appointment required string (uuid) The id (UUID) of the appointment.

Body (JSON)

FieldTypeDescription
reason optional string The reason for cancelling (optional). can be empty (null) · at most 500 characters

Example request

cURL
curl -X POST "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/cancel" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
  -d '{
  "reason": "Klant is verhinderd"
}'
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('POST', 'appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/cancel', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
    'json' => [
        'reason' => 'Klant is verhinderd',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/cancel', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
  },
  body: JSON.stringify({
  "reason": "Klant is verhinderd"
}),
});

const { data } = await response.json();
Python
import os

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/cancel",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
        "Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    },
    json={
        "reason": "Klant is verhinderd"
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "appointment",
    "id": "9d3f7d83-4f8b-4a0e-9d5c-6b7f8a9bacb4",
    "title": "Inmeten veranda",
    "description": null,
    "status": "confirmed",
    "starts_at": "2026-10-01T08:00:00Z",
    "ends_at": "2026-10-01T09:00:00Z",
    "all_day": false,
    "location": "Dorpsstraat 1, Utrecht",
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "contact": {
      "name": "Jan de Vries",
      "email": "jan@example.com",
      "phone": "+31 6 12345678"
    },
    "user_id": "usr_0k3j9x21m4zq8p",
    "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5",
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "quote_id": null,
    "invoice_id": null,
    "notes": null,
    "cancellation_reason": null,
    "confirmed_at": "2026-09-14T10:15:00Z",
    "cancelled_at": null,
    "rescheduled_at": null,
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Complete an appointment

POST /api/v1/appointments/{appointment}/complete

Completes the appointment. If the company's lead conversion setting is "appointment completed", a lead becomes a customer, just like in the calendar; with the other settings it stays a lead.

Scope
appointments.write — Create, update, confirm, cancel and complete appointments (the company's automations run along)
Required feature
appointments

Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.

Path parameters

NameTypeDescription
appointment required string (uuid) The id (UUID) of the appointment.

Example request

cURL
curl -X POST "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/complete" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f"
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('POST', 'appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/complete', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/complete', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
  },
});

const { data } = await response.json();
Python
import os

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/complete",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
        "Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "appointment",
    "id": "9d3f7d83-4f8b-4a0e-9d5c-6b7f8a9bacb4",
    "title": "Inmeten veranda",
    "description": null,
    "status": "confirmed",
    "starts_at": "2026-10-01T08:00:00Z",
    "ends_at": "2026-10-01T09:00:00Z",
    "all_day": false,
    "location": "Dorpsstraat 1, Utrecht",
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "contact": {
      "name": "Jan de Vries",
      "email": "jan@example.com",
      "phone": "+31 6 12345678"
    },
    "user_id": "usr_0k3j9x21m4zq8p",
    "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5",
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "quote_id": null,
    "invoice_id": null,
    "notes": null,
    "cancellation_reason": null,
    "confirmed_at": "2026-09-14T10:15:00Z",
    "cancelled_at": null,
    "rescheduled_at": null,
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Send the customer a message

POST /api/v1/appointments/{appointment}/notify

Emails the customer a confirmation, reschedule, cancellation or reminder, using the template the company set up in Klantly. The message must match the status of the appointment. If the company has switched that template off, you get 409 and nothing is sent. Requires the appointments.send scope.

Scope
appointments.send — Email appointment messages to customers
Required feature
appointments

Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.

Path parameters

NameTypeDescription
appointment required string (uuid) The id (UUID) of the appointment.

Body (JSON)

FieldTypeDescription
message required string Which message: confirmation, reschedule, cancellation or reminder. one of: confirmation, reschedule, cancellation, reminder

Example request

cURL
curl -X POST "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/notify" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
  -d '{
  "message": "confirmation"
}'
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('POST', 'appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/notify', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
    'json' => [
        'message' => 'confirmation',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/notify', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
  },
  body: JSON.stringify({
  "message": "confirmation"
}),
});

const { data } = await response.json();
Python
import os

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/notify",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
        "Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    },
    json={
        "message": "confirmation"
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "appointment",
    "id": "9d3f7d83-4f8b-4a0e-9d5c-6b7f8a9bacb4",
    "title": "Inmeten veranda",
    "description": null,
    "status": "confirmed",
    "starts_at": "2026-10-01T08:00:00Z",
    "ends_at": "2026-10-01T09:00:00Z",
    "all_day": false,
    "location": "Dorpsstraat 1, Utrecht",
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "contact": {
      "name": "Jan de Vries",
      "email": "jan@example.com",
      "phone": "+31 6 12345678"
    },
    "user_id": "usr_0k3j9x21m4zq8p",
    "appointment_type_id": "9d3f7ea4-5a9c-4b1f-8e6d-7c8a9bacbdc5",
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "quote_id": null,
    "invoice_id": null,
    "notes": null,
    "cancellation_reason": null,
    "confirmed_at": "2026-09-14T10:15:00Z",
    "cancelled_at": null,
    "rescheduled_at": null,
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Delete an appointment

DELETE /api/v1/appointments/{appointment}

Deletes the appointment permanently, including from the linked Google Calendar. To just call it off, cancel it instead.

Scope
appointments.delete — Delete appointments
Required feature
appointments

Path parameters

NameTypeDescription
appointment required string (uuid) The id (UUID) of the appointment.

Example request

cURL
curl -X DELETE "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('DELETE', 'appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
  },
});

const { data } = await response.json();
Python
import os

import requests

response = requests.delete(
    "https://app.klantly.com/api/v1/appointments/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "note",
    "id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
    "deleted": true
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

The object

All fields are always present; a field without a value is null.

FieldTypeDescription
object string Always "appointment".
id string (uuid) Unique id (UUID).
title string Title of the appointment.
description string Description. can be empty (null)
status string pending (not confirmed yet), confirmed, cancelled or completed. When creating: pending or confirmed (default). one of: pending, confirmed, cancelled, completed
starts_at string (date-time) Start (UTC). Empty for an invitation where the customer still picks a time. As input: ISO 8601 with a time zone. can be empty (null)
ends_at string (date-time) End (UTC). Without ends_at when creating: the duration of the appointment type or the default duration. can be empty (null)
all_day boolean An all-day appointment. Times in the response are in UTC: convert back to the company's time zone (Europe/Amsterdam) for the date, otherwise an appointment starting at 00:00 falls on the day before.
location string Location, for example the address of the customer. can be empty (null)
customer_id string (uuid) The customer of the appointment. Required when creating. can be empty (null)
contact object The contact details the appointment was made with.
contact.name string Name. can be empty (null)
contact.email string Email address; messages to the customer go here. can be empty (null)
contact.phone string Phone number. can be empty (null)
user_id string The user who has the appointment, or null. can be empty (null)
appointment_type_id string (uuid) The appointment type, or null. can be empty (null)
deal_id string (uuid) The deal on the pipeline board the appointment belongs to (Klantly links this itself). can be empty (null)
quote_id string (uuid) The linked quote, or null. can be empty (null)
invoice_id string (uuid) The linked invoice, or null. can be empty (null)
notes string Internal note on the appointment. can be empty (null)
cancellation_reason string Why the appointment was cancelled, or null. can be empty (null)
confirmed_at string (date-time) When the appointment was confirmed. can be empty (null)
cancelled_at string (date-time) When the appointment was cancelled. can be empty (null)
rescheduled_at string (date-time) When the appointment was last rescheduled. can be empty (null)
created_at string (date-time) Created at (UTC).
updated_at string (date-time) Last updated at (UTC).