Slotly API, as shipped in the download
Slotly — API & agent guide (Own It 3.0)
Two machine doors, one set of rules. Everything below authenticates with a bearer key created in Settings → API & Webhooks (apk_…, shown once, revocable any time). A key carries its owner's role — viewer keys read, staff keys can book — and a deactivated user's keys stop working with them.
The public booking endpoints (/api/slots, /api/quote, /api/book, /b/{code}) are your storefront, unauthenticated by design, and unchanged from 1.x.
The REST API (/api/v1/*)
Machine-readable spec: GET /api/openapi.json — imports into n8n, Power Automate, Zapier and most HTTP tooling.
GET /api/v1/services active services (duration, price in cents, capacity)
GET /api/v1/slots?service_id&date free slots for a local date (optional staff_id)
GET /api/v1/appointments list; filters: status, from, to (UTC)
POST /api/v1/appointments create — through the conflict-free engine; 409 on a lost slot
GET /api/v1/appointments/{id} one appointment, with payments
GET /api/v1/customers list/search customers
GET /healthz liveness + version (no auth)
POST /api/v1/appointments body:
{
"service_id": 1, "staff_id": 2, "start_utc": "2026-09-01 14:00:00",
"party_size": 1,
"customer_name": "Cara Doe", "customer_email": "cara@example.com",
"notes": "prefers window seat"
}
Pass customer_id instead of name/email to book for an existing customer. The engine validates the slot server-side (lead time, advance window, working hours, buffers, capacity) and inserts inside a serialized transaction with a UNIQUE slot key — the same code path the public form uses, so the API cannot double-book what the form protects.
MCP — the agent door (POST /mcp)
Point Claude Desktop/Code, an n8n AI node, or any MCP client at POST /mcp with the same bearer key. GET /mcp answers with a transport hint.
Tools:
| Tool | Writes? | What it does |
|---|---|---|
list_services | no | services with price + capacity |
list_staff | no | bookable staff for a service |
availability | no | free slots for a service on a date — from the product's own generator |
list_appointments | no | appointments in a UTC range |
appointment_detail | no | one appointment by id or booking code, with payments |
book_appointment | yes | books through Schedule::book — the same engine as every other door |
reschedule_appointment | yes | moves a booking via Schedule::move; the reference code survives |
day_report | no | a local day at a glance: bookings, expected takings, per-staff load |
The rules do not relax for agents. A write tool checks the key's role through the same permission map the browser uses; a viewer key is refused by name. Booking runs the identical serialized transaction — two agents racing for the last seat lose exactly like two humans do.
What an agent deliberately cannot do: cancel, delete, or touch money. There is no such tool. An AI receptionist that can fill your book overnight is useful; one that can empty it is a liability — so the second kind cannot be built on this endpoint.
Connect it to an assistant
Mint the key in the app first: Settings → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.
Every client needs the same three facts, and nothing in the handshake is vendor-specific:
| The address | https://booking.example.com/mcp |
| The key | header Authorization: Bearer apk_xxxx |
| The transport | MCP over streamable HTTP, stateless |
Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:
claude mcp add --transport http slotly https://booking.example.com/mcp \
--header "Authorization: Bearer apk_xxxx"
ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):
{
"type": "mcp",
"server_label": "slotly",
"server_url": "https://booking.example.com/mcp",
"authorization": "apk_xxxx",
"require_approval": "never"
}
Own Your AI reads a list of servers in this shape:
{
"mcpServers": [
{ "id": "slotly", "name": "Slotly", "url": "https://booking.example.com/mcp",
"token": "apk_xxxx", "enabled": true }
]
}
**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"* — not an mcpServers array. Pasted as-is, the block above will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.
A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:
curl -X POST https://booking.example.com/mcp \
-H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Slotly implements the protocol, not an integration with a particular vendor.
Webhooks
Settings → API & Webhooks. Events: appointment.created, appointment.rescheduled, appointment.cancelled, payment.recorded. Each delivery is signed X-Slotly-Signature: sha256=<hmac_sha256(body, secret)> so your receiver can verify origin and integrity. Recent deliveries with status codes are listed on the same page.
Backups, scheduled
Mint a token-guarded URL under Backups & restore, then let cron fetch it:
*/60 * * * * curl -fsS "https://booking.example.com/backup/scheduled?t=<token>" > /dev/null
Each hit writes a secrets-redacted JSON backup into data/backups/ and prunes to the configured keep-count. Restoring always dry-runs first — the diff shows add/update/delete counts per table before you commit.