Public booking MCP endpoint (JSON-RPC)
/api/mcpThe Model Context Protocol endpoint an AI assistant connects to in order to read this platform's public booking surface on a guest's behalf. One endpoint for the whole platform, with the venue as a tool argument: both directories that can list an MCP server require it to call the operator's own first-party APIs from a domain matching the service, and no mainstream assistant discovers an MCP server from a web page at all, so a per-tenant endpoint would be unlistable by construction.
Not a REST operation, and the shape of this entry reflects that. The body is JSON-RPC 2.0 and the method vocabulary is the MCP specification's (initialize, tools/list, tools/call, ...), not anything defined here; an OpenAPI schema for it would describe the transport and say nothing useful about the contract. Read the MCP specification for the envelope, and tools/list against this endpoint for the tools, which is the authoritative and self-describing answer.
Five tools, none of which books anything. get_venue, find_appointment_times and find_table_times read; book_appointment and book_table turn a slot token into a short-lived link on the venue's own booking page, opened at that time, where the guest enters their own details and confirms. No tool accepts a guest's name, email or phone, none moves money, and none takes a table or provider out of inventory: opening a book_table link does cause the booking page to hold that table for about ten minutes, but the tool call itself holds nothing. Everything returned is already served to any anonymous visitor of /book/{slug}.
Findable on every plan, bookable from Growth. get_venue answers any venue with its bookingPage link. The two time searches and the two booking tools need the venue's plan to include ai_booking (Growth and up, src/lib/billing/config.ts); below it they answer with bookingPage and liveTimes: false instead of times or a link at a time.
No timezone and no machine-readable instant is ever returned. Times come back as display text plus an opaque HMAC-signed slot token, because a model handed an IANA zone next to a time converts it and books hours wrong. The token carries the real instant and is the only handle a future write tool will accept, which is also what stops a prompt injection moving a booking to another venue while the transcript still shows the right one.
No CORS, deliberately, and none is planned. An MCP client is a server-to-server HTTP client. The browser-based MCP Inspector cannot reach this and is not meant to; use the Inspector CLI.
Authentication: none, and that is the design rather than a gap. This surface holds no credential and reaches no tenant-private data. The separate PRIVATE per-tenant MCP server, which is not built, binds one sk_live_ key to one company and never accepts a venue as a tool argument; the two trust models are deliberately not blended.
Response Body
application/json
curl -X POST "https://example.com/api/mcp"{ "jsonrpc": "2.0", "id": "string", "result": {}, "error": { "code": 0, "message": "string" }}Retired email logo: redirects to the G7 mark GET
Emails sent before 2026-09-28 embed the Gaplessly logo from this URL, which used to draw the pre-G7 mark per request, tinted with the org's accent (the `accent`, `crescent` and `h` query parameters). Emails now carry the G7 logo as static files under `/email-icons/` (render.ts's logoImg), so this route only keeps those older emails from showing a broken image: every request is a permanent redirect to `/email-icons/g7-mark-legacy.png`, the G7 mark centred in the old mark's 253:376 shape so the old `<img>` boxes don't squash it. The query string no longer changes anything. No session, same as every email asset.
List clients (API key) GET
The client/guest directory, for a program. **Shared by both engines** and gated with no vertical for that reason: a client record means the same thing to a salon and a restaurant, which is why the dashboard screen behind it is one shared route too. There is consequently no `wrong_vertical` failure here. Read-only, like every `/api/v1` operation. As above, the handler's explicit `company_id` filter is the entire tenant boundary: removing it would leak every organization's client list. Ordered newest first. No date filters: a client directory is not time-shaped, and there is no search parameter either, deliberately: a `?q=` would be a third `.or()` call site to sanitize and pagination already lets a caller walk the whole list.