# Zenoti MCP Server A Model Context Protocol (MCP) server for the [Zenoti](https://www.zenoti.com/) spa/wellness/medspa management platform. Exposes appointments, guests, service booking, invoices, catalog, and forms as tools an AI agent can call. ### Hosted version available Do not want to manage credentials, hosting, and updates yourself? [DOCK](https://dockhq.vercel.app) is the managed version of this server: encrypted per-practice auth, audit logs, draft-first write actions, and a workflow library. Founding practices lock lifetime pricing: Front Desk $49/mo (Zenoti), Billing Desk $99/mo (Tebra, BAA included), Full Practice $129/mo (both). https://dockhq.vercel.app ## Installation ```bash npm install -g zenoti-mcp-server ``` Or run from source: ```bash git clone https://github.com/tacit-code/zenoti-mcp-server.git cd zenoti-mcp-server npm install npm run build ``` ## Configuration | Variable | Required | Description | |---|---|---| | `ZENOTI_API_KEY` | yes | API key from Zenoti Admin → Settings → Apps (create a backend app) | | `ZENOTI_API_URL` | no | Base URL, default `https://api.zenoti.com` (with or without `/v1`) | | `ZENOTI_CENTER_ID` | recommended | Default center GUID; tools accept `center_id` to override per call | Claude Desktop / Claude Code config: ```json { "mcpServers": { "zenoti": { "command": "npx", "args": ["-y", "zenoti-mcp-server"], "env": { "ZENOTI_API_KEY": "your_api_key", "ZENOTI_CENTER_ID": "your_center_guid" } } } } ``` The server starts even without credentials and returns a readable configuration error on each tool call, so a misconfigured client shows the problem instead of crash-looping. ## Tools ### Catalog | Tool | Purpose | |---|---| | `zenoti-centers-list` | List organization centers (find center GUIDs) | | `zenoti-services-list` | Services of a center (compact by default, `verbose` for raw) | | `zenoti-services-get` | Full service details | | `zenoti-therapists-list` | Therapists of a center, optionally filtered to a service | | `zenoti-employee-schedules` | Who is on shift for a date range (date-scoped staffing) | ### Guests | Tool | Purpose | |---|---| | `zenoti-guests-search` | Search by name/email/phone/code/tags (≥1 criterion required) | | `zenoti-guests-create` | Create a guest profile | | `zenoti-guests-get` | Guest details by GUID | | `zenoti-guests-update` | Update fields — does a safe read-merge-write (Zenoti replaces the whole object on update) | | `zenoti-guests-appointments` | Appointment history for a guest | | `zenoti-guests-notes-list` / `zenoti-guests-notes-add` | Guest notes and profile alerts | | `zenoti-guests-memberships` | Guest's memberships (status, credits, dues) | | `zenoti-guests-packages` | Guest's packages/series with remaining redemptions | ### Appointments | Tool | Purpose | |---|---| | `zenoti-appointments-list` | Center appointments for a date range (compact by default) | | `zenoti-appointments-get` | Appointment details (`appointment_id`) | | `zenoti-appointments-checkin` / `zenoti-appointments-undo-checkin` | Check-in state (`appointment_group_id`) | | `zenoti-appointments-noshow` | Mark no-show (`appointment_group_id`) | | `zenoti-appointments-progress` | Start/open/complete service (`appointment_id`) | | `zenoti-appointments-cancel` | Cancel a booking (by `invoice_id`) | | `zenoti-appointments-reschedule` | Start a reschedule (creates a booking draft tied to the invoice) | ### Booking flow 1. `zenoti-availability-slots` — creates a booking draft for guest+service+date and returns `booking_id` plus open slots 2. `zenoti-booking-reserve` — hold a chosen slot (holds are short-lived) 3. `zenoti-booking-confirm` — finalize; the response includes the invoice ### Invoices | Tool | Purpose | |---|---| | `zenoti-invoices-get` | Invoice with line items, payments, and optional dues/fees | | `zenoti-invoices-pay-custom` | Record a cash/custom payment (financial action) | | `zenoti-invoices-pay-card` | Charge the guest's card on file (financial action) | | `zenoti-invoices-close` | Close a fully-paid invoice | | `zenoti-invoices-email` | Email the receipt/invoice to the guest | | `zenoti-invoices-confirm-visit` | Confirm (or undo-confirm) the visit — a scheduling status, despite living on the invoice | ### Reports | Tool | Purpose | |---|---| | `zenoti-reports-sales` | What was sold over a date range | | `zenoti-reports-collections` | Money received over a date range | ### Forms & feedback | Tool | Purpose | |---|---| | `zenoti-forms-list` / `zenoti-forms-get` | Forms and submitted form data of an appointment | | `zenoti-guest-forms-list` | Forms on a guest profile | | `zenoti-feedback-submit` | Insert externally collected guest feedback | ## Zenoti API gotchas this server handles - **Three identifiers in one workflow.** Check-in/no-show/feedback take `appointment_group_id`; details/progress/forms take `appointment_id`; cancel and payments take `invoice_id`. Tool schemas name the exact one required. - **Guest updates replace the whole object.** The update tool fetches the current profile and merges your changes; a naive partial `PUT` would erase fields. - **Rate limit is 60 calls/minute** (org-wide). The HTTP layer honors `Retry-After` on 429. Reads also retry on 5xx/network failures/timeouts; writes retry **only** on 429 (a rate-limit rejection was never processed, so retrying can't duplicate a booking — any other write failure surfaces immediately). - **Pagination caps at `size=100`**; larger values are rejected by Zenoti with a 422. - **Webhooks have no subscription API** — they are configured in the Zenoti web UI (Admin → Webhooks) and require the Zenoti API package. This server intentionally has no webhook tool. - **Undocumented numeric enums.** Gender and appointment-progress codes aren't in Zenoti's public docs; the mappings used here are documented in the tool descriptions and can be overridden (`progress_code`). ## Development ```bash npm run dev # run from source (tsx) npm test # unit + end-to-end tests (mock Zenoti API; no real credentials needed) npm run typecheck # tsc --noEmit npm run build # emit dist/ ``` The e2e suite spawns the real server over stdio via the MCP SDK client and asserts every tool's method, path, query, and body against Zenoti's documented endpoints. ## License MIT