Bookings that never collide.
One API for developers.

Availability across resources, schedules and time zones. Bookings that cannot double book, policies frozen at the sale, payments on your own Stripe, signed webhooks, a CLI and an MCP server. One API, and open source.

16 bookings · 2 holds · 1 block · 1 refused synthetic data · capacity enforced by Postgres

The layer under your booking app.
What it does today.

Availability

Slots or continuous ranges, computed across schedules, exceptions, blocks and bookings. explain names the booking, hold or block behind every instant it refuses.

  • Time zones and DST handled
  • Every rejection explained
  • Resources, groups and capacity
explain at 11:30, a match on any of three courts
instantcoderesourceheld by
11:30 occupied Court 1 bk_0198…
11:30 occupied Court 2 hold_0198…
11:30 blocked Court 3 blk_0198…

Bookings without double booking

Hold a slot for a few minutes, then book it, or book it directly. Capacity is enforced by Postgres, so two requests for the last unit get one 201 and one 409.

two POST /v1/bookings, one slot 201 confirmed 409 slot_unavailable

Policies and lifecycle

Confirm, check in, complete, cancel, no-show, reschedule: one transition matrix, held by the engine as data. A paid booking that is cancelled is refunded by the policy frozen into it at the sale.

Which action is legal from which status. An empty cell is 409 invalid_transition.
confirmcancelrescheduleno_showcheck_incomplete
pending allowed allowed allowed
confirmed allowed allowed allowed allowed allowed
in_progress allowed allowed allowed

Payments on your own Stripe

A deposit or the full price, charged on your own Stripe account through Stripe Connect. The money goes to your account, and Bookrail never sees or stores a Stripe key of yours.

$ bookrail stripe connect # Open the Stripe authorisation page and wait for it to be completed.

Webhooks and events

Every change is an event in an ordered log. Deliveries are signed with HMAC and retried on a fixed ladder, 8 attempts in all.

  1. 3s
  2. 30s
  3. 5m
  4. 30m
  5. 2h
  6. 12h
  7. 24h

CLI and config as code

What is bookable lives in bookrail.config.ts, versioned with your app. push, pull and diff keep the project and the file in step.

$ bookrail diff [test] differences: 2 to update, 5 unchanged update schedule club_hours Club hours rulesupdate service match Match duration_options

MCP server

40 tools for a coding agent, over the Model Context Protocol. Test by default, and every irreversible tool answers with a preview until it is confirmed.

bookrail_availabilitybookrail_booking_createbookrail_config_push and 37 more

Open source

The engine, the API, the SDK, the CLI and the MCP server, in one public repository. Read how concurrency and time zones are handled before you trust them.

Apache-2.0 github.com/bookrail-dev/bookrail

See why a slot is not available
before your users ask.

POST /v1/availability · explain: true 18 slots · 5 instants refused
Every instant the engine refused on the synthetic day, one row per court.
Instant (Europe/Rome) Code Resource Held by Why
08:30 occupied Court 1 bk_0198f0c2a1b47e2e9a1c000000000002 Court 1 is already taken during the booking window.
08:30 occupied Court 2 bk_0198f0c2a1b47e2e9a1c000000000006 Court 2 is already taken during the booking window.
08:30 blocked Court 3 blk_0198f0c2a1b47e2e9a1c00000000000a Court 3 is blocked during the booking window.
09:00 occupied Court 1 bk_0198f0c2a1b47e2e9a1c000000000002 Court 1 is already taken during the booking window.
09:00 occupied Court 2 bk_0198f0c2a1b47e2e9a1c000000000006 Court 2 is already taken during the booking window.
09:00 blocked Court 3 blk_0198f0c2a1b47e2e9a1c00000000000a Court 3 is blocked during the booking window.
09:30 occupied Court 1 bk_0198f0c2a1b47e2e9a1c000000000002 Court 1 is already taken during the booking window.
09:30 occupied Court 2 bk_0198f0c2a1b47e2e9a1c000000000006 Court 2 is already taken during the booking window.
09:30 blocked Court 3 blk_0198f0c2a1b47e2e9a1c00000000000a Court 3 is blocked during the booking window.
11:30 occupied Court 1 bk_0198f0c2a1b47e2e9a1c000000000003 Court 1 is already taken during the booking window.
11:30 occupied Court 2 hold_0198f0c2a1b47e2e9a1c000000000007 Court 2 is already taken during the booking window.
11:30 blocked Court 3 blk_0198f0c2a1b47e2e9a1c00000000000a Court 3 is blocked during the booking window.
13:30 occupied Court 1 bk_0198f0c2a1b47e2e9a1c000000000004 Court 1 is already taken during the booking window.
13:30 occupied Court 2 hold_0198f0c2a1b47e2e9a1c000000000007 Court 2 is already taken during the booking window.
13:30 occupied Court 3 bk_0198f0c2a1b47e2e9a1c00000000000b Court 3 is already taken during the booking window.

occupied 11, blocked 4. What the availability engine answers, at build time, about the day the grid above draws: a match of 60 minutes on any of the three courts. Synthetic data.

POST /v1/bookings 201 Created
{  "id": "bk_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c",  "object": "booking",  "status": "confirmed",  "service_id": "svc_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c",  "customer_id": "cus_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c",  "start": "2026-09-08T07:00:00Z",  "end": "2026-09-08T08:00:00Z",  "duration_minutes": 60,  "timezone": "Europe/Rome",  "quantity": 1,  "price": { "amount": 2500, "currency": "EUR" },  "policy_snapshot": { "cancellation": [{ "before_minutes": 1440, "refund_percent": 100 }] },  "allocations": [{ "object": "booking_allocation", "resource_id": "res_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c", "role": "court", "capacity_used": 1 }],  "created_at": "2026-09-07T10:12:04Z"}

14 of the 43 fields a booking always carries, built from the Booking schema of the OpenAPI document. The other 29 are in the API reference.

POST https://example.com/hooks/bookrail booking.created
Content-Type: application/json; charset=utf-8User-Agent: Bookrail-Webhooks/1.0 (+https://bookrail.dev/docs/webhooks; 2026-09-01)Bookrail-Signature: t=1788775924,v1=84f5a2b4654fad4b47e4dddc12d9111d3016fe2a105ef84738b7296282fd0493Bookrail-Event-Id: evt_0198f0c2a1b47e2e9a1c0f4d5e6a7b8cBookrail-Webhook-Id: wh_0198f0c2a1b47e2e9a1c0f4d5e6a7b8cBookrail-Delivery-Id: whd_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c {  "id": "evt_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c",  "object": "event",  "type": "booking.created",  "occurred_at": "2026-09-07T10:12:04Z",  "api_version": "2026-09-01",  "seq": 1,  "actor": { "type": "api", "id": "key_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c" },  "data": { "object": { "id": "bk_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c", "object": "booking", "status": "confirmed", "service_id": "svc_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c", "customer_id": "cus_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c", "start": "2026-09-08T07:00:00Z", "end": "2026-09-08T08:00:00Z", "duration_minutes": 60, "timezone": "Europe/Rome", "quantity": 1, "price": { "amount": 2500, "currency": "EUR" }, "policy_snapshot": { "cancellation": [{ "before_minutes": 1440, "refund_percent": 100 }] }, "allocations": [{ "object": "booking_allocation", "resource_id": "res_0198f0c2a1b47e2e9a1c0f4d5e6a7b8c", "role": "court", "capacity_used": 1 }], "created_at": "2026-09-07T10:12:04Z" }, "previous": null },  "environment": "test",  "created_at": "2026-09-07T10:12:04Z"}

The event is the object GET /v1/events returns, with the booking of the previous tab in it. It travels as one compact line of JSON, and Bookrail-Signature is the HMAC of those bytes, computed at build time with a synthetic secret by the function your receiver verifies it with.

Use Bookrail with
the Node SDK

The same calls as the quickstart, read from it when this page was built.

import Bookrail from '@bookrail/node';

const bookrail = new Bookrail(process.env.BOOKRAIL_SECRET_KEY!);
const service = 'svc_01a08041186277d699e4cf0ac77eec54';

const { slots } = await bookrail.availability.list({
  service_id: service,
  from: '2026-09-15T00:00:00+02:00',
  to: '2026-09-16T00:00:00+02:00',
  timezone: 'Europe/Rome',
});
console.log(`${slots.length} slots, first at ${slots[0].start}`);

const params = {
  service_id: service,
  start: slots[0].start,
  duration_minutes: slots[0].duration_minutes,
  customer: { email: 'ada@example.com', name: 'Ada Lovelace' },
};
const options = { idempotencyKey: 'quickstart-demo-1' };

const booking = await bookrail.bookings.create(params, options);
console.log(`${booking.id} ${booking.status} ${booking.start} -> ${booking.end}`);

// The same call again, with the same key: no second booking, and the API says so.
const again = await bookrail.bookings.create(params, options).withResponse();
console.log(`retry -> ${again.data.id} replayed=${again.response.idempotentReplayed}`);
Read the docs for the Node SDK
curl -s https://api.bookrail.dev/v1/availability \
  -H "Authorization: Bearer $BOOKRAIL_SECRET_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"service_id":"svc_01a08041186277d699e4cf0ac77eec54",
       "from":"2026-09-16T00:00:00+02:00",
       "to":"2026-09-17T00:00:00+02:00",
       "timezone":"Europe/Rome"}'

curl -s https://api.bookrail.dev/v1/bookings \
  -H "Authorization: Bearer $BOOKRAIL_SECRET_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-curl-1' \
  -d '{"service_id":"svc_01a08041186277d699e4cf0ac77eec54",
       "start":"2026-09-16T06:00:00Z",
       "duration_minutes":60,
       "customer":{"email":"ada@example.com","name":"Ada Lovelace"}}'
Read the docs for plain HTTP
npx bookrail availability \
  --service svc_01a08041186277d699e4cf0ac77eec54 \
  --from 2026-09-14T00:00:00+02:00 \
  --to   2026-09-15T00:00:00+02:00

npx bookrail bookings create \
  --service svc_01a08041186277d699e4cf0ac77eec54 \
  --start 2026-09-14T18:00:00+02:00 \
  --duration 60 \
  --customer-email ada@example.com \
  --customer-name "Ada Lovelace"
Read the docs for the CLI
npx bookrail mcp install --client claude-code   # or cursor, vscode, windsurf, generic

# then the agent calls two tools of the server: ask, and book
{
  "name": "bookrail_availability",
  "arguments": {
    "service_id": "svc_01a08041186277d699e4cf0ac77eec54",
    "from": "2026-09-16T00:00:00+02:00",
    "to": "2026-09-17T00:00:00+02:00"
  }
}
{
  "name": "bookrail_booking_create",
  "arguments": {
    "service_id": "svc_01a08041186277d699e4cf0ac77eec54",
    "start": "2026-09-16T06:00:00Z",
    "customer_email": "ada@example.com"
  }
}
Read the docs for MCP

Start from your vertical
with a ready configuration.

See all templates

padel

Sports courts (padel, tennis, five a side)

Sports courts: capacity one, two match lengths, payment up front.

resources
  • Court 1
  • Court 2
services
  • Match 60 min or 90 min
npx bookrail init --template padel

salon

Salon, barber, beauty studio

Hair salon: stylists with skills, a treatment cabin, a colour with cleaning time.

resources
  • Anna
  • Bruno
  • Nadia
  • Cabin 1
services
  • Cut 30 min
  • Colour 90 min
  • Manicure 45 min
npx bookrail init --template salon

gym

Gyms and studios (yoga, pilates, crossfit)

Group classes: the seats live on the class, the instructor is taken whole.

npx bookrail init --template gym

rental

Rental (cars, bikes, boats, equipment)

Rental: continuous ranges from one day to thirty, with cleaning time after each.

npx bookrail init --template rental

restaurant

Restaurants

Restaurant: covers on tables, two services a day, large parties split over tables.

npx bookrail init --template restaurant

clinic

Healthcare (medical practices, physiotherapy, vets, dentists)

Healthcare: a scan needs a doctor, a room and the machine at the same time.

npx bookrail init --template clinic

And 4 more, coworking, tours, tutoring and empty. bookrail init --help lists every one of them.

Designed to be driven
by a coding agent.

A CLI with --json everywhere, an MCP server with 40 tools, every documentation page as plain markdown at the same URL with .md on the end, and an OpenAPI document generated from the very schemas that validate each request.

$ npx bookrail mcp install --client claude-code[test] wrote ./.mcp.json (Claude Code (project scope, committed with the repository))$ npx bookrail availability --service svc_0198f0c2a1b47e2e9a1c0000000000a1 --from 2026-09-08T08:00:00+02:00 --to 2026-09-08T10:00:00+02:00 --explain[test] 1 slot(s) for svc_0198f0c2a1b47e2e9a1c0000000000a1, times shown in Europe/Rome start (UTC)               local             end (UTC)                 min  cap  price      rule  resources------------------------  ----------------  ------------------------  ---  ---  ---------  ----  ------------------------------------2026-09-08T06:00:00.000Z  2026-09-08 08:00  2026-09-08T07:00:00.000Z  60   1    30.00 EUR  base  res_0198f0c2a1b47e2e9a1c000000000005 3 instant(s) rejected: occupied 6, blocked 3 local instant     code      resource                              why----------------  --------  ------------------------------------  ---------------------------------------------------2026-09-08 08:30  occupied  res_0198f0c2a1b47e2e9a1c000000000001  Court 1 is already taken during the booking window.2026-09-08 08:30  occupied  res_0198f0c2a1b47e2e9a1c000000000005  Court 2 is already taken during the booking window.2026-09-08 08:30  blocked   res_0198f0c2a1b47e2e9a1c000000000009  Court 3 is blocked during the booking window.2026-09-08 09:00  occupied  res_0198f0c2a1b47e2e9a1c000000000001  Court 1 is already taken during the booking window.2026-09-08 09:00  occupied  res_0198f0c2a1b47e2e9a1c000000000005  Court 2 is already taken during the booking window.2026-09-08 09:00  blocked   res_0198f0c2a1b47e2e9a1c000000000009  Court 3 is blocked during the booking window.2026-09-08 09:30  occupied  res_0198f0c2a1b47e2e9a1c000000000001  Court 1 is already taken during the booking window.2026-09-08 09:30  occupied  res_0198f0c2a1b47e2e9a1c000000000005  Court 2 is already taken during the booking window.2026-09-08 09:30  blocked   res_0198f0c2a1b47e2e9a1c000000000009  Court 3 is blocked during the booking window.

Printed by the compiled CLI when this page was built. The second command was answered by the availability engine through the API's own serializer, about the synthetic day of the grid.

Free until it counts.
Pay as you grow.

See pricing
€0
to start: the Free plan has the whole API
1,000
live bookings a month included on Free
€29
a month for Pro, with 5,000 bookings included
3 cents
for each booking after that, on Pro

The test environment: free, never counted. Prices exclude VAT.

Join the discussion

Ask how to model your case, propose a change, or name a booking case the documentation does not cover yet. The conversation is public, on GitHub.

Open source. Read it before you trust it.

Apache-2.0: the engine, the API, the SDK, the CLI and the MCP server, in one public repository.

Book your first slot
in about ten minutes.