Ontwikkelaars

De API van Qouh

Koppel je CRM, website of boekhoudpakket: lees afspraaktypes, vrije tijden, afspraken en klanten, boek en verplaats bezoeken op de tijden die je aanbiedt, en ontvang elke wijziging als ondertekende webhook.

Basis-URL
https://qouh.com/api

Download het OpenAPI-bestandDe naslag hieronder is in het Engels, net als de API zelf.

Aan de slag

  1. Maak een sleutel aan via Instellen → Integraties (op Starter, Pro en tijdens de proefperiode). Sleutels beginnen met qh_ en worden één keer getoond.
  2. Test hem: GET /v1/me antwoordt met de naam van je organisatie.
  3. Bouw verder met de naslag hieronder, of koppel Zapier zonder code te schrijven.
curl "https://qouh.com/api/v1/me" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY"

Sleutels en rechten

Stuur de sleutel mee in de header Authorization: Bearer van elk verzoek, via https. Een sleutel kan alleen wat je toestond toen je hem maakte:

  • bookings:read: Afspraken en vrije tijden bekijken
  • bookings:write: Afspraken maken, verplaatsen en annuleren
  • customers:read: Klanten bekijken
  • customers:write: Klanten toevoegen en bijwerken
  • webhooks:manage: Webhooks instellen (voor Zapier)

Elke sleutel mag 120 verzoeken per minuut doen; daarboven antwoordt de API met 429 en een header Retry-After. Een sleutel die je intrekt op de pagina Integraties werkt meteen niet meer.

Tijden en pagina’s

Tijden zijn ISO 8601 in UTC. Lijsten komen per pagina: geef de nextCursor uit het antwoord mee als cursor tot die null is.

Fouten

Een fout antwoordt met een HTTP-status en een body met een vaste code en een leesbare message:

{
  "error": {
    "code": "slot_unavailable",
    "message": "This time is not offered (any more). Pick one from GET /v1/slots."
  }
}
  • 400 The query or body is not valid (invalid_request).
  • 401 No key, or a key that is not valid or was revoked (unauthorized).
  • 403 The plan has no API (plan_required) or the key lacks the permission (insufficient_scope).
  • 404 Not found, or it belongs to another organization (not_found).
  • 409 The time is not offered (slot_unavailable), the booking is cancelled or has started (not_changeable), another customer has the email (email_taken), the webhook limit is reached (limit_reached), or a request with this Idempotency-Key is still running (idempotency_in_progress).
  • 422 The address could not be found (address_not_found), or the Idempotency-Key was used for another request (idempotency_key_reused).
  • 429 More than 120 requests a minute with this key (rate_limited); see Retry-After.
  • 503 Address lookup or routing is unavailable for a moment (unavailable).

Veilig opnieuw proberen

Schrijfverzoeken nemen een optionele header Idempotency-Key (bijvoorbeeld een UUID). Dezelfde sleutel opnieuw binnen 24 uur krijgt het eerste antwoord terug, gemarkeerd met Idempotent-Replayed: true, zodat een nieuwe poging nooit twee keer boekt.

Webhooks

Voeg een endpoint toe via Instellen → Integraties, of abonneer via de API, om afspraken en klanten te volgen terwijl ze veranderen. Elke gebeurtenis wordt als JSON gePOST met deze headers. Een mislukte levering wordt ongeveer een dag opnieuw geprobeerd; een endpoint dat 3 dagen blijft falen wordt uitgezet en de eigenaars krijgen een e-mail.

VeldTypeOmschrijving
webhook-id
verplicht
stringThe event’s id (msg_…), the same on every retry: use it to skip repeats.
webhook-timestamp
verplicht
stringWhen this attempt was sent, in seconds since 1970 (UTC).
webhook-signature
verplicht
stringSpace-separated v1,<base64> signatures: HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<body> keyed with the endpoint’s secret (the part after whsec_, base64-decoded). After “Roll secret” the old secret signs too for 24 hours.

Controleer de handtekening (Standard Webhooks) voor je een gebeurtenis vertrouwt, weiger tijdstempels die meer dan 5 minuten afwijken, en gebruik webhook-id om herhalingen over te slaan. In Node.js:

import { createHmac, timingSafeEqual } from 'node:crypto'

// secret: the whsec_… shown when you added the endpoint
// rawBody: the request body exactly as received, before parsing
function verify(secret, headers, rawBody) {
  const id = headers['webhook-id']
  const timestamp = headers['webhook-timestamp']
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
  const key = Buffer.from(secret.slice('whsec_'.length), 'base64')
  const expected = Buffer.from(
    createHmac('sha256', key).update(id + '.' + timestamp + '.' + rawBody).digest('base64'),
  )
  return headers['webhook-signature'].split(' ').some((part) => {
    const given = Buffer.from(part.slice('v1,'.length))
    return part.startsWith('v1,') && given.length === expected.length && timingSafeEqual(given, expected)
  })
}

Zapier

Onze Zapier-app is voorlopig privé: vraag ons een uitnodiging via de contactpagina. Koppel hem met een sleutel met “Volledige toegang”. Zaps kunnen starten bij een nieuwe, gewijzigde of geannuleerde afspraak of een nieuwe klant, en kunnen een klant toevoegen of bijwerken, een klant zoeken en de eerste vrije tijd boeken.

Naslag

Elk verzoek dat de API aanneemt. Paden zijn relatief tot de basis-URL.

Je sleutel

The key and its organization

get/v1/me

Elke geldige sleutel mag dit aanroepen.

Any valid key may call this. Use it to test a connection.

Antwoord

200 met Me. Fouten: 401, 403, 429.

Voorbeeld
curl "https://qouh.com/api/v1/me" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY"

Afspraaktypes

List appointment types

get/v1/appointment-types

Recht: bookings:read (Afspraken en vrije tijden bekijken)

Every appointment type, active or not, sorted by name.

Queryparameters
VeldTypeOmschrijving
locale
optioneel
stringLanguage of appointment type names (default: the organization’s language).
Een van:ennlfr
Antwoord

200 met AppointmentTypeList. Fouten: 400, 401, 403, 429.

Voorbeeld
curl "https://qouh.com/api/v1/appointment-types" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY"

Vrije tijden

Find bookable times

get/v1/slots

Recht: bookings:read (Afspraken en vrije tijden bekijken)

The times an active appointment type can be booked at a place, as the booking page offers them: working hours, existing visits, busy time and the drive between visits are taken into account. Give lat and lng, or an address to look up in the organization's country. Sorted by time; rank 0 fits the route best.

Queryparameters
VeldTypeOmschrijving
type
verplicht
integerThe appointment type’s id.
lat
optioneel
numberThe visit’s latitude (with lng).
lng
optioneel
numberThe visit’s longitude (with lat).
address
optioneel
stringThe visit’s address, looked up when lat and lng are not given.
from
optioneel
string (date-time)Start of the search (default: now).
to
optioneel
string (date-time)End of the search (default: a week after from; at most 31 days).
Antwoord

200 met SlotList. Fouten: 400, 401, 403, 404, 422, 429, 503.

Voorbeeld
curl "https://qouh.com/api/v1/slots?type=12&address=Grote%20Markt%201%2C%202000%20Antwerpen" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY"

Afspraken

List bookings

get/v1/bookings

Recht: bookings:read (Afspraken en vrije tijden bekijken)

Bookings sorted by start time, a page at a time. Follow nextCursor for the next page.

Queryparameters
VeldTypeOmschrijving
status
optioneel
stringOne status or a comma-separated list: pending, confirmed, completed, cancelled, no_show.
from
optioneel
string (date-time)Bookings starting at or after this time.
to
optioneel
string (date-time)Bookings starting before this time.
updatedSince
optioneel
string (date-time)Bookings changed at or after this time (to sync changes).
customer
optioneel
integerOnly this customer’s bookings.
locale
optioneel
stringLanguage of appointment type names (default: the organization’s language).
Een van:ennlfr
limit
optioneel
integerItems per page, 1–100.
cursor
optioneel
stringThe nextCursor of the previous page.
Antwoord

200 met BookingList. Fouten: 400, 401, 403, 429.

Voorbeeld
curl "https://qouh.com/api/v1/bookings?status=confirmed&limit=20" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY"

Get a booking

get/v1/bookings/:id

Recht: bookings:read (Afspraken en vrije tijden bekijken)

Padparameters
VeldTypeOmschrijving
id
verplicht
integerThe id.
Queryparameters
VeldTypeOmschrijving
locale
optioneel
stringLanguage of appointment type names (default: the organization’s language).
Een van:ennlfr
Antwoord

200 met Booking. Fouten: 400, 401, 403, 404, 429.

Voorbeeld
curl "https://qouh.com/api/v1/bookings/123" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY"

Make a booking

post/v1/bookings

Recht: bookings:write (Afspraken maken, verplaatsen en annuleren)

Books a time from GET /v1/slots for the customer, as the booking page would: the engine picks who comes and checks the time is still offered. The customer is matched by email (or added) and gets the confirmation email.

JSON-body
VeldTypeOmschrijving
appointmentType
verplicht
integerThe appointment type’s id.
start
verplicht
string (date-time)The start, as a time from GET /v1/slots. ISO 8601 with an offset or Z.
customer
verplicht
object
customer.name
verplicht
string
customer.email
verplicht
string (email)Customers are matched by email address (any case).
customer.phone
optioneel
stringStored with its country code (+32470123456) when it can be read as a number of the organization’s country or with a country code; otherwise as sent.
address
verplicht
stringWhere the visit takes place.
location
optioneel
objectCoordinates (WGS 84). Without them the address is looked up.
location.lat
verplicht
number
location.lng
verplicht
number
locale
optioneel
stringThe language for the customer’s emails (default: the organization’s).
Een van:ennlfr
Antwoord

201 met Booking. Fouten: 400, 401, 403, 404, 409, 422, 429, 503.

Voorbeeld
curl -X POST "https://qouh.com/api/v1/bookings" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @body.json

Cancel a booking

post/v1/bookings/:id/cancel

Recht: bookings:write (Afspraken maken, verplaatsen en annuleren)

Cancels a booking that has not started. The customer and the rep are told.

Padparameters
VeldTypeOmschrijving
id
verplicht
integerThe id.
Antwoord

200 met Booking. Fouten: 400, 401, 403, 404, 409, 422, 429.

Voorbeeld
curl -X POST "https://qouh.com/api/v1/bookings/123/cancel" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Move a booking

post/v1/bookings/:id/reschedule

Recht: bookings:write (Afspraken maken, verplaatsen en annuleren)

Moves a booking that has not started to another time, same person and address, if that time is offered. The customer gets the new time and a new link to manage it.

Padparameters
VeldTypeOmschrijving
id
verplicht
integerThe id.
JSON-body
VeldTypeOmschrijving
start
verplicht
string (date-time)The start, as a time from GET /v1/slots. ISO 8601 with an offset or Z.
Antwoord

200 met Booking. Fouten: 400, 401, 403, 404, 409, 422, 429, 503.

Voorbeeld
curl -X POST "https://qouh.com/api/v1/bookings/123/reschedule" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @body.json

Klanten

List customers

get/v1/customers

Recht: customers:read (Klanten bekijken)

Customers in the order they were added, a page at a time. Pass email to find one customer.

Queryparameters
VeldTypeOmschrijving
email
optioneel
stringFind the customer with this email address.
updatedSince
optioneel
string (date-time)Customers changed at or after this time (to sync changes).
limit
optioneel
integerItems per page, 1–100.
cursor
optioneel
stringThe nextCursor of the previous page.
Antwoord

200 met CustomerList. Fouten: 400, 401, 403, 429.

Voorbeeld
curl "https://qouh.com/api/v1/customers?email=jan%40example.com" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY"

Get a customer

get/v1/customers/:id

Recht: customers:read (Klanten bekijken)

Padparameters
VeldTypeOmschrijving
id
verplicht
integerThe id.
Antwoord

200 met Customer. Fouten: 401, 403, 404, 429.

Voorbeeld
curl "https://qouh.com/api/v1/customers/123" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY"

Add or update a customer

post/v1/customers

Recht: customers:write (Klanten toevoegen en bijwerken)

Adds the customer (201), or updates the one with this email (200).

JSON-body
VeldTypeOmschrijving
email
verplicht
string (email)Customers are matched by email address (any case).
name
optioneel
string
phone
optioneel
string | nullStored with its country code (+32470123456) when it can be read as a number of the organization’s country or with a country code; otherwise as sent. Null clears it.
address
optioneel
stringThe customer’s address. Without location it is looked up in the background (location is null meanwhile).
location
optioneel
objectCoordinates (WGS 84). Without them the address is looked up.
location.lat
verplicht
number
location.lng
verplicht
number
locale
optioneel
stringThe language emails to this customer are sent in.
Een van:ennlfr
Antwoord

200 / 201 met Customer. Fouten: 400, 401, 403, 409, 422, 429.

Voorbeeld
curl -X POST "https://qouh.com/api/v1/customers" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @body.json

Update a customer

patch/v1/customers/:id

Recht: customers:write (Klanten toevoegen en bijwerken)

Padparameters
VeldTypeOmschrijving
id
verplicht
integerThe id.
JSON-body
VeldTypeOmschrijving
email
optioneel
string (email)Customers are matched by email address (any case).
name
optioneel
string
phone
optioneel
string | nullStored with its country code (+32470123456) when it can be read as a number of the organization’s country or with a country code; otherwise as sent. Null clears it.
address
optioneel
stringThe customer’s address. Without location it is looked up in the background (location is null meanwhile).
location
optioneel
objectCoordinates (WGS 84). Without them the address is looked up.
location.lat
verplicht
number
location.lng
verplicht
number
locale
optioneel
stringThe language emails to this customer are sent in.
Een van:ennlfr
Antwoord

200 met Customer. Fouten: 400, 401, 403, 404, 409, 422, 429.

Voorbeeld
curl -X PATCH "https://qouh.com/api/v1/customers/123" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @body.json

Webhook-abonnementen

Subscribe a webhook

post/v1/webhooks

Recht: webhooks:manage (Webhooks instellen (voor Zapier))

Adds a webhook endpoint for the given events (REST hooks, as Zapier uses them) and answers its signing secret once. An organization may have 50 endpoints added through the API (409 limit_reached beyond). An endpoint that answers 410 Gone is removed.

JSON-body
VeldTypeOmschrijving
url
verplicht
stringWhere to POST the events: https, on a public address.
events
verplicht
string[]The events to send there.
description
optioneel
stringShown on the Integrations page.
Antwoord

201 met WebhookSubscription. Fouten: 400, 401, 403, 409, 422, 429.

Voorbeeld
curl -X POST "https://qouh.com/api/v1/webhooks" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @body.json

Unsubscribe a webhook

delete/v1/webhooks/:id

Recht: webhooks:manage (Webhooks instellen (voor Zapier))

Removes an endpoint added through the API, with its delivery log. Endpoints added on the Integrations page are not found here.

Padparameters
VeldTypeOmschrijving
id
verplicht
integerThe id.
Antwoord

200 met WebhookDeleted. Fouten: 400, 401, 403, 404, 409, 422, 429.

Voorbeeld
curl -X DELETE "https://qouh.com/api/v1/webhooks/123" \
  -H "Authorization: Bearer $ROUTE_TRIM_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Objecten

Waarmee de API antwoordt. Een veld dat leeg kan zijn heeft null in zijn type.

Location

VeldTypeOmschrijving
lat
verplicht
number
lng
verplicht
number

AppointmentType

VeldTypeOmschrijving
id
verplicht
integer
name
verplicht
string
description
verplicht
string | null
slug
verplicht
string
durationMinutes
verplicht
integer
bookingWindowDays
verplicht
integerHow many days ahead customers may book.
minNoticeHours
verplicht
integerThe shortest notice customers may book with.
active
verplicht
booleanInactive types take no bookings and have no slots.
bookingUrl
verplicht
string (uri)The public booking page for this type.

Customer

VeldTypeOmschrijving
id
verplicht
integer
name
verplicht
string
email
verplicht
string
phone
verplicht
string | null
address
verplicht
string | null
location
verplicht
Location | nullNull while the address is not placed on the map.
locale
verplicht
stringThe language emails to this customer are sent in.
Een van:ennlfr
createdAt
verplicht
string (date-time)When the customer was added. ISO 8601, in UTC.
updatedAt
verplicht
string (date-time)When the customer last changed. ISO 8601, in UTC.

Booking

VeldTypeOmschrijving
id
verplicht
integer
status
verplicht
string
Een van:pendingconfirmedcompletedcancelledno_show
start
verplicht
string (date-time)The start of the visit. ISO 8601, in UTC.
end
verplicht
string (date-time)The end of the visit. ISO 8601, in UTC.
address
verplicht
stringWhere the visit takes place.
location
verplicht
Location
appointmentType
verplicht
object
appointmentType.id
verplicht
integer
appointmentType.name
verplicht
string
rep
verplicht
objectThe person who makes the visit.
rep.id
verplicht
integer
rep.name
verplicht
string | null
customer
verplicht
Customer
source
verplicht
stringWhere the booking came from.
Een van:bookingPagemaintenanceInviteapicrm
locale
verplicht
stringThe language the customer booked in.
Een van:ennlfr
cancelledAt
verplicht
string (date-time) | null
createdAt
verplicht
string (date-time)When the booking was made. ISO 8601, in UTC.
updatedAt
verplicht
string (date-time)When the booking last changed. ISO 8601, in UTC.

Slot

VeldTypeOmschrijving
start
verplicht
string (date-time)The start of the visit. ISO 8601, in UTC.
end
verplicht
string (date-time)The end of the visit. ISO 8601, in UTC.
rank
verplicht
integerHow well the time fits the route: 0 is the best fit.

BookingList

VeldTypeOmschrijving
data
verplicht
Booking[]
nextCursor
verplicht
string | nullPass as cursor to get the next page; null on the last page.

CustomerList

VeldTypeOmschrijving
data
verplicht
Customer[]
nextCursor
verplicht
string | nullPass as cursor to get the next page; null on the last page.

SlotList

VeldTypeOmschrijving
timeZone
verplicht
stringThe organization’s time zone (IANA), for showing the times.
data
verplicht
Slot[]

Me

VeldTypeOmschrijving
organization
verplicht
object
organization.id
verplicht
integer
organization.name
verplicht
string
organization.slug
verplicht
string
key
verplicht
object
key.id
verplicht
integer
key.label
verplicht
string
key.prefix
verplicht
string
key.scopes
verplicht
string[]

Error

VeldTypeOmschrijving
error
verplicht
object
error.code
verplicht
stringunauthorized, plan_required, insufficient_scope, rate_limited, not_found, invalid_request, address_not_found, slot_unavailable, not_changeable, email_taken, limit_reached, idempotency_key_reused, idempotency_in_progress, unavailable or internal_error.
error.message
verplicht
string

BookingEvent

VeldTypeOmschrijving
type
verplicht
string
Een van:booking.createdbooking.updatedbooking.cancelled
timestamp
verplicht
string (date-time)When the event happened. ISO 8601, in UTC.
data
verplicht
Booking
change
optioneel
stringOnly on booking.updated: rescheduled (a new time, by the customer or the API), moved (staff re-planned the day) or reassigned (another person makes the visit).
Een van:rescheduledmovedreassigned

CustomerEvent

VeldTypeOmschrijving
type
verplicht
string
Een van:customer.createdcustomer.updated
timestamp
verplicht
string (date-time)When the event happened. ISO 8601, in UTC.
data
verplicht
Customer

TestEvent

VeldTypeOmschrijving
type
verplicht
string
Een van:webhook.test
timestamp
verplicht
string (date-time)When the event happened. ISO 8601, in UTC.
data
verplicht
object
data.endpointId
verplicht
integer
data.message
verplicht
string

WebhookSubscription

VeldTypeOmschrijving
id
verplicht
integer
url
verplicht
string
events
verplicht
string[]
description
verplicht
string | null
status
verplicht
string
Een van:activedisabled
secret
verplicht
stringThe signing secret (whsec_…) for webhook-signature. Only shown in this answer.
createdAt
verplicht
string (date-time)When it was added. ISO 8601, in UTC.

WebhookDeleted

VeldTypeOmschrijving
id
verplicht
integer
deleted
verplicht
boolean
Een van:true

Webhook-gebeurtenissen

De gebeurtenissen waarop een endpoint zich kan abonneren, met het object dat elk ervan meestuurt.