Developers

Take bookings from your own website.

A small REST API for reading availability and writing bookings. Everything goes through the same engine the app itself uses, so the API cannot create a booking the board would have refused.

https://calendula.crawlink.dev/api/v1

Getting a key

In the app, go to Settings → API keys and create one. Pick the narrowest set of scopes that does the job, and give each integration its own key so one can be revoked without breaking the others.

The key is shown once. Only its SHA-256 is stored, so nobody — including us — can recover it afterwards. Lose it and you revoke it and issue another.

Authentication

Send the key as a bearer token. X-Api-Key works too, for clients that cannot set an Authorization header.

Authorization: Bearer ck_<organization>_<secret>

The organization id is part of the key. That is what lets a request find your data without you having to send an account identifier alongside it.

Scopes

calendars.read Read calendars — List calendars, services and locations.
availability.read Read availability — Ask which times are free on a given day.
bookings.read Read bookings — List and read bookings, including customer contact details.
bookings.write Create and change bookings — Take new bookings and move existing ones.
bookings.cancel Cancel bookings — Cancel a booking. Separate from writing, because it is destructive.
bookings.override Book outside opening hours — Ignore opening hours and slot boundaries. Only for systems recording walk-ins.
customers.read Read customers — List and read the customer directory.
customers.write Create and change customers — Add customers and edit their details.

Endpoints

GET /health calendars.read

Confirms the key works and reports which organization it belongs to.

GET /calendars calendars.read

Calendars and locations, with the slot length, buffers and notice each one enforces.

GET /services calendars.read

Services, their durations and prices, and which calendars deliver them.

GET /availability availability.read

Free times for one calendar. `days` walks forward, so a month view is one call.

GET /bookings bookings.read

Bookings in a date range, filterable by calendar and status.

POST /bookings bookings.write

Takes a booking through the same availability engine the app uses.

GET /bookings/{id} bookings.read

One booking.

PATCH /bookings/{id} bookings.write

Moves a booking, edits its details, or changes its status.

DELETE /bookings/{id} bookings.cancel

Cancels. The record is kept.

GET /customers customers.read

The customer directory.

POST /customers customers.write

Creates a customer, or updates one when `id` is given.

Finding free times

curl "https://calendula.crawlink.dev/api/v1/availability?calendarId=CAL_ID&date=2026-09-01&days=7" \
  -H "Authorization: Bearer ck_ORG_SECRET"

Each day comes back with its open windows and the individual starts on offer. Every slot carries both a local time and an absolute startsAt instant, so a caller in another timezone never has to redo the conversion.

Taking a booking

curl -X POST "https://calendula.crawlink.dev/api/v1/bookings" \
  -H "Authorization: Bearer ck_ORG_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "calendarId": "CAL_ID",
    "serviceId":  "SERVICE_ID",
    "date":       "2026-09-01",
    "start":      "18:30",
    "customerName":  "Asha Menon",
    "customerPhone": "+919876543210"
  }'

start takes either "18:30" or minutes from midnight. If the slot has gone since you read the availability, the response is 409 with slot_unavailable — re-read availability and offer the customer another time rather than retrying the same one.

Errors

Every failure is JSON with a stable error.code — branch on the code, not the message, which may be reworded.

{
  "error": {
    "code": "slot_unavailable",
    "message": "That time is already booked. Please pick another time."
  }
}
missing_key 401 — no key was sent
invalid_key 401 — unknown, revoked or expired
missing_scope 403 — the key lacks the scope for this call
out_of_scope 403 — the key is limited to other calendars
not_found 404 — no such booking or calendar
slot_unavailable 409 — the time is no longer free
rate_limited 429 — too many requests this minute
invalid_body 400 — the request body was malformed

Rate limit

120 requests a minute per key by default. It exists to stop a runaway script, not to meter usage — if a legitimate integration needs more, ask and we will raise it.