Home›Slotly›API
Slotly · API

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. A staff member's key sees and moves only that person's own appointments (/api/v1/appointments, and the MCP appointment tools and day report), exactly as their login does; an admin key sees every appointment.

The public booking endpoints (/api/slots, /api/quote, /api/book, /b/{code}) are your storefront, unauthenticated by design. 3.2 adds to them, without changing what was there: /api/book takes policy_ok (required when you set a cancellation policy), sms_opt_in and w (a waitlist offer token); /api/slots answers open (does anyone work that day); POST /api/waitlist joins the waitlist (service_id, staff_id, date, name, email, phone, sms_opt_in), rate-limited per address. Bookings made through /api/v1 or MCP are confirmed as before; deposit holds apply to the public page only.

4.1.2 guards POST /api/book and still asks for no key and no session token, so a server-side caller works as before: at most 30 posts per address in 10 minutes (then 429); a browser post whose Origin names another website is refused with 403 (a request with no Origin header, such as one from a server, passes the Origin check; the 30 per address limit still applies to it, so integrations should use /api/v1/appointments); and the field website must be empty or absent (the booking page's hidden honeypot; filled = 422, nothing booked). Behind Cloudflare or another proxy, list the proxy in trusted_proxies in config.php, or every caller counts as the proxy's one address (the manual's "Behind Cloudflare or a load balancer" has the steps).

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:

ToolWrites?What it does
list_servicesnoservices with price + capacity
list_staffnobookable staff for a service
availabilitynofree slots for a service on a date — from the product's own generator
list_appointmentsnoappointments in a UTC range
appointment_detailnoone appointment by id or booking code, with payments
book_appointmentyesbooks through Schedule::book — the same engine as every other door
reschedule_appointmentyesmoves a booking via Schedule::move; the reference code survives
day_reportnoa 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 addresshttps://booking.example.com/mcp
The keyheader Authorization: Bearer apk_xxxx
The transportMCP 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"
}

**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"*. Pasted as-is, a block written for another client 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, appointment.confirmed (3.2: a deposit hold was paid, so the booking now counts; a hold that lapses sends appointment.cancelled with "by": "hold_lapsed"). 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.

Connect it to Zapier, Make or n8n

Slotly talks to other tools without a plugin: it sends a webhook (an HTTPS message) the moment something happens, and it accepts calls to the API above. Zapier, Make and n8n can use both.

Before you start

  • In Slotly, open Settings → API & Webhooks. Mint an API key (choose Read only if the other tool only reads), and add a webhook when you have its URL (below).
  • Use your install's full address, for example https://slotly.yourcompany.com.

When something happens in Slotly: send it to another tool

  1. In the other tool, create the trigger that gives you a URL to receive webhooks: - Zapier: the "Webhooks by Zapier" app, trigger Catch Hook (Webhooks by Zapier needs a paid Zapier plan). - Make: module Webhooks → Custom webhook. - n8n: the Webhook node.
  2. Copy that URL into Slotly under Settings → API & Webhooks, and choose the event or events below.
  3. Make the event happen once in Slotly, so the other tool receives a sample and you can map its fields.
EventFires when
appointment.createdA booking is made, online or by staff
appointment.rescheduledA booking moves to another time
appointment.cancelledA booking is canceled; a deposit hold that lapses also sends this, with "by": "hold_lapsed"
payment.recordedA payment is recorded against a booking
appointment.confirmedA held booking's deposit was paid, so the booking now counts

Every delivery carries X-Slotly-Signature, an HMAC-SHA256 signature over the raw body. Zapier and Make accept the delivery without checking it, so keep the webhook URL private. In n8n you can check it in a Code node before using the data.

When something happens elsewhere: act in Slotly

  • Zapier: "Webhooks by Zapier", action POST (or Custom Request).
  • Make: module HTTP → Make a request.
  • n8n: the HTTP Request node.

Send the request to your install's address plus the path from the Endpoints table above, with the header Authorization: Bearer <your API key> and a JSON body. A read-only key cannot write.

The endpoints that change something:

  • POST /api/waitlist
  • POST /api/v1/appointments

Example recipes

  • appointment.created → a row in Google Sheets: webhook trigger on appointment.created, then the Google Sheets "Create row" action with the fields you want.
  • appointment.rescheduled → a message in Slack or Teams: webhook trigger on appointment.rescheduled, then the Slack or Microsoft Teams "Send message" action.
  • A new form entry or row elsewhere → Slotly: the other tool's trigger (for example a new Google Sheets row or a form submission), then an HTTP action calling POST /api/waitlist.

These recipes use only the events and endpoints documented above. We have not run them inside Zapier, Make or n8n ourselves; Slotly's own automated tests check its API.

← Back to Slotly · Manual · Quickstart · Test run