API Reference · v1.4.2

Cragside API

Cragside is a scheduling platform for climbing gyms. This API exposes read access to a gym's bookings, members, classes and payments. All responses are JSON, all timestamps are UTC ISO 8601 (2026-06-11T14:02:31Z), and all list endpoints share the same pagination and incremental-sync semantics.

This instance serves the demo dataset of Cragside Boulderhalle (Freiburg). The records are fictional; the API behaviour is real.

Authentication

Every /v1/* request needs a bearer token in the Authorization header. This demo instance ships with the read-only sandbox token cragside_read_demo_7f3kqp (operators can override it with the CRAGSIDE_READ_TOKEN environment variable).

curl -H "Authorization: Bearer cragside_read_demo_7f3kqp" \
  "$BASE/v1/bookings?limit=2"

A missing or wrong token returns 401:

{"error": "unauthorized", "hint": "Authorization: Bearer <read token>"}

Pagination & incremental sync

All four list endpoints accept the same query parameters:

ParamTypeDescription
limitintegerRows per page. Default 100, maximum 500.
updated_sinceISO 8601Only rows with updated_at strictly greater than this timestamp. Pass it on the first request of a sync; the cursor carries it forward.
cursorstringOpaque pagination token from meta.next_cursor. Pass it back verbatim; it pins the snapshot, the offset and the updated_since filter, so a paging loop is stable even while new rows arrive.

Rows are sorted by (updated_at, id) ascending. Every response has the shape:

{
  "data": [ ... ],
  "meta": { "count": 100, "next_cursor": "eyJvIjoxMDAs..." }
}

meta.count is the number of rows in this page. meta.next_cursor is present when more rows exist and null on the last page.

Incremental sync recipe

  1. Full load: GET /v1/bookings?limit=500, follow next_cursor until it is null.
  2. Remember the largest updated_at you saw.
  3. Periodically: GET /v1/bookings?updated_since=<that timestamp>, again following next_cursor.

updated_at changes whenever a row changes (a booking is cancelled or checked in, a payment settles or is refunded, a membership freezes), so an updated_since poll returns both newly created rows and recently updated old rows — upsert on the primary key.

curl -H "Authorization: Bearer cragside_read_demo_7f3kqp" \
  "$BASE/v1/bookings?updated_since=2026-06-11T06:00:00Z&limit=500"

GET /v1/bookings

One row per booked session: open-gym slots (class_id is null, roughly 60%) and class reservations.

FieldTypeExample
booking_idstring, primary key"bk_104233"
member_idstring"mem_0412"
member_emailstring"lena.krause73@web.de"
class_idstring or null"cls_17"
roomstring"boulder-1" — one of boulder-1, boulder-2, lead-wall, training-room, spire
starts_atISO 8601"2026-06-11T18:30:00Z"
party_sizeinteger 1–42
statusstring"confirmed" — one of confirmed, cancelled, checked-in, waitlist
created_atISO 8601"2026-06-11T17:55:12Z"
updated_atISO 8601, cursor field"2026-06-11T18:34:08Z"
curl -H "Authorization: Bearer cragside_read_demo_7f3kqp" "$BASE/v1/bookings?limit=1"

{
  "data": [
    {
      "booking_id": "bk_100001",
      "member_id": "mem_0683",
      "member_email": "paula.winkler@gmx.de",
      "class_id": null,
      "room": "boulder-2",
      "starts_at": "2026-03-01T08:20:00Z",
      "party_size": 1,
      "status": "confirmed",
      "created_at": "2026-03-01T08:11:42Z",
      "updated_at": "2026-03-01T08:11:42Z"
    }
  ],
  "meta": { "count": 1, "next_cursor": "eyJvIjoxLCJhIjoi..." }
}

GET /v1/members

FieldTypeExample
member_idstring, primary key"mem_0042"
namestring"Jonas Weber"
emailstring"jonas.weber14@gmail.com"
planstring"monthly" — one of monthly, annual, punch-card, student
joined_ondate"2024-11-03"
statusstring"active" — one of active, frozen, cancelled
updated_atISO 8601, cursor field"2026-03-01T00:00:00Z"

GET /v1/classes

The weekly class schedule.

FieldTypeExample
class_idstring, primary key"cls_07"
namestring"Lead Climbing 101"
roomstring"lead-wall"
weekdaystring"Thu"Mon through Sun
timestring HH:MM"18:30"
capacityinteger12
instructorstring"Katja"
updated_atISO 8601, cursor field"2026-03-01T00:00:00Z"

GET /v1/payments

Day passes, class fees and other point-of-sale charges. Most bookings produce a payment a few minutes after they are created.

FieldTypeExample
payment_idstring, primary key"pay_203117"
member_idstring"mem_0412"
amountstring, decimal euros"12.50"
methodstring"card" — one of card, sepa, cash, app
statusstring"settled" — one of settled, pending, refunded
created_atISO 8601"2026-06-11T18:01:09Z"
updated_atISO 8601, cursor field"2026-06-11T18:42:55Z"

Other endpoints

EndpointAuthDescription
GET /noneAPI root: name, version, endpoint list, docs URL.
GET /docsnoneThis page.
GET /homenoneProduct landing page with a demo "Add a booking" button.
POST /demo/add-bookingnoneCreates one demo booking with created_at = updated_at = now and returns it. Handy for verifying that an incremental sync picks up new rows. Kept in memory only.
GET /healthznone{"ok": true}

Rate notes

Cragside · Scheduling for climbing gyms · demo dataset, fictional records.