# Wagend — full documentation (English) --- # Introduction Source: /en/docs/introduction What Wagend is, who it is for and the building blocks you will use. Wagend is a **scheduling engine** with **WhatsApp + AI** as its main channel. Your customers book by chatting; the system offers only slots that really exist, holds them, confirms them and sends reminders. Businesses manage everything from a dashboard, and developers integrate through a **REST API**, **webhooks** and an **MCP server**. ::callout{type="warning"} Wagend is **in development**. This documentation describes the target contract (API v1 draft). Endpoints may still change before the pilot. :: ## Who it is for Any business that sells **someone's or something's time**: | Business | What gets booked | | --- | --- | | Barbershop | A barber for 30–45 minutes | | Aesthetics | A professional **and** a machine **and** a room, at the same time | | Clinic | A doctor and an office | | Restaurant | Covers in a dining room for a party size | | Deliveries | Capacity in a 2-hour window for a zone | | Classes, courts, rooms | A seat in a class or a court by the hour | ## Building blocks - **Organization** → your account. It holds one or more **workspaces** (a business or location). - **Resource** → what gets consumed: a barber, a table, a laser machine, a room, a delivery zone. - **Service** → what the customer books. It declares its **requirements**: which resources it needs, and how many, at the same time. - **Schedule** → when each resource is available. - **Booking** → goes through a **hold** (10 minutes) before being **confirmed**. - **Automation** → reminders, confirmations and staff alerts triggered by booking events. Read [Workspaces and resources](/docs/concepts/workspaces-and-resources) for the full model. ## Channels All channels use the same engine, so a slot booked on WhatsApp disappears instantly from the booking page and the API. - **WhatsApp** with an AI assistant (text and voice notes). - **Public booking page** and embeddable widget. - **Dashboard** for the team (today's agenda, inbox, calendar, settings). - **REST API**, **webhooks** and **MCP** for developers and AI agents. ## Next steps - [Quickstart](/docs/quickstart): make your first booking through the API in 5 minutes. - [How it works](/docs/how-it-works): the architecture and the life of a message. - [MCP](/docs/mcp): connect an AI agent to Wagend. --- # Quickstart Source: /en/docs/quickstart Create your first booking through the API in five minutes using a sandbox key. This guide uses a **test key** (`wg_test_...`). Test keys work against a sandbox copy of your workspace: no WhatsApp messages are sent and no real payments are made. ::callout{type="info"} Base URL: `https://api.wagend.app/v1`. Every request needs `Authorization: Bearer `. :: ## 1. Get a test key In the dashboard, go to **Developers → API keys → New key**, choose **Test**, and select the scopes `slots:read`, `bookings:write` and `config:read`. The key is shown **only once**. ```bash export WAGEND_KEY="wg_test_xxxxxxxx_xxxxxxxxxxxxxxxxxxxx" ``` ## 2. Check who you are ```bash curl https://api.wagend.app/v1/me -H "Authorization: Bearer $WAGEND_KEY" ``` ```json { "workspace": { "id": "ws_9f2c", "name": "Downtown Barbershop", "timezone": "America/Sao_Paulo", "locale": "pt", "currency": "BRL" }, "scopes": ["slots:read", "bookings:write", "config:read"], "mode": "test" } ``` The workspace always comes from the key. You never send a workspace id. ## 3. List services ```bash curl https://api.wagend.app/v1/services -H "Authorization: Bearer $WAGEND_KEY" ``` ```json { "data": [ { "id": "svc_corte", "name": "Haircut", "duration_min": 30, "price_cents": 5000, "currency": "BRL", "requirements": [{ "resource_group_id": "grp_barbers", "units": 1 }] } ] } ``` ## 4. Find available slots ```bash curl "https://api.wagend.app/v1/slots?service_id=svc_corte&from=2026-10-15T08:00:00-03:00&to=2026-10-15T13:00:00-03:00" \ -H "Authorization: Bearer $WAGEND_KEY" ``` ```json { "data": [ { "start": "2026-10-15T09:00:00-03:00", "end": "2026-10-15T09:30:00-03:00", "resource_ids": ["res_juan"] }, { "start": "2026-10-15T09:45:00-03:00", "end": "2026-10-15T10:15:00-03:00", "resource_ids": ["res_juan"] } ], "unavailable_reason": null } ``` ## 5. Hold the slot A hold reserves the slot for **10 minutes** while you collect the customer's details. Always send an `Idempotency-Key` so retries are safe. ```bash curl -X POST https://api.wagend.app/v1/holds \ -H "Authorization: Bearer $WAGEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "service_id": "svc_corte", "start": "2026-10-15T09:45:00-03:00", "resource_ids": ["res_juan"] }' ``` ```json { "id": "bkg_7Qx1", "status": "held", "expires_at": "2026-10-14T15:30:00-03:00", "start": "2026-10-15T09:45:00-03:00" } ``` ## 6. Confirm ```bash curl -X POST https://api.wagend.app/v1/holds/bkg_7Qx1/confirm \ -H "Authorization: Bearer $WAGEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "customer": { "name": "Carlos", "phone": "+5521999990000", "locale": "pt" } }' ``` ```json { "id": "bkg_7Qx1", "status": "confirmed", "start": "2026-10-15T09:45:00-03:00", "source": "api" } ``` If the service requires a Pix deposit, the status is `pending_payment` and the response includes the Pix code. It becomes `confirmed` when the payment arrives. ## Same flow in JavaScript ```ts const api = (path: string, init: RequestInit = {}) => fetch(`https://api.wagend.app/v1${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.WAGEND_KEY}`, 'Content-Type': 'application/json', ...init.headers }, }).then((r) => r.json()) const { data: slots } = await api(`/slots?service_id=svc_corte&from=${from}&to=${to}`) const hold = await api('/holds', { method: 'POST', headers: { 'Idempotency-Key': crypto.randomUUID() }, body: JSON.stringify({ service_id: 'svc_corte', start: slots[0].start }), }) const booking = await api(`/holds/${hold.id}/confirm`, { method: 'POST', headers: { 'Idempotency-Key': crypto.randomUUID() }, body: JSON.stringify({ customer: { name: 'Carlos', phone: '+5521999990000' } }), }) ``` ## Next - React to bookings with [webhooks](/docs/api/webhooks). - See every endpoint in the [reference](/docs/api/endpoints). --- # How it works Source: /en/docs/how-it-works Architecture, principles and the life of a WhatsApp message inside Wagend. ## Principles 1. **Deterministic core, AI as interpreter.** The AI understands the customer and calls tools. The engine computes availability, holds, confirms and charges. The AI never invents a time or a price. 2. **The database enforces the rules.** Double booking is impossible because PostgreSQL rejects overlapping allocations, not because the app "checks first". 3. **API-first.** The dashboard, the booking page, the WhatsApp bot and MCP all use the same services. If something can't be done through the API, it's missing from the API. 4. **The credential defines the tenant.** A key or session always belongs to one workspace. ## Architecture ```text Channels: WhatsApp · Booking page · Dashboard · REST / MCP │ Gateway: credential → workspace · scopes · rate limits · signed webhooks │ Core: conversations + AI │ scheduling engine │ automations │ payments │ knowledge │ Data: PostgreSQL (row-level security, exclusion constraints, jobs) · Redis External: AI models (OpenRouter) · speech-to-text · WhatsApp provider · Pix ``` ## The life of a message 1. **Arrives.** The WhatsApp provider calls our webhook. We verify the HMAC signature, drop duplicates by message id and store it. We answer in under 200 ms. 2. **Understood.** Voice notes are transcribed. We identify the customer and the conversation. If a person took over, the bot stays silent. 3. **The AI asks.** The model gets the business context, the local date and time, a structured conversation state and the last messages. It can only call tools such as `find_slots`, `hold_slot` or `confirm_hold`. 4. **The engine decides.** Each tool call is validated against a strict schema and business rules, then executed. Errors come back as structured data (for example `slot_taken` with alternatives) so the AI asks again. 5. **Exact reply.** Dates, times, services, prices and addresses are rendered from templates filled with the real result. 6. **Events.** `booking.confirmed` and friends trigger automations (reminders) and your webhooks. ## Where to go next - [Bookings and holds](/docs/concepts/bookings-and-holds) explains the guarantees. - [WhatsApp and AI](/docs/whatsapp-and-ai) explains what the AI can and cannot do. --- # Workspaces and resources Source: /en/docs/concepts/workspaces-and-resources Organizations, workspaces, roles, resources, capacity modes and resource groups. ## Organization and workspaces An **organization** is the account that pays. It contains one or more **workspaces**. A workspace is a business or a location with its own: - time zone, language and currency, - WhatsApp number(s), - bot, services, resources and team. A chain of three barbershops is one organization with three workspaces. ## Roles | Role | Can | | --- | --- | | `owner` | Everything, across all workspaces of the organization | | `manager` | Everything inside one workspace | | `staff` | See and manage only their own resources (for example, a barber sees only their agenda) | | `viewer` | Read-only | Staff can also act from WhatsApp after linking their number with a one-time code. Admin actions from WhatsApp always ask for a YES/NO confirmation. ## Resources A **resource** is anything that gets consumed by a booking. | `kind` | Examples | | --- | --- | | `staff` | Barber, doctor, beautician, instructor | | `space` | Room, office, booth, dining room | | `equipment` | Laser machine, chair, court | | `table` | Restaurant table (table mode) | | `vehicle` | Van, delivery crew | | `zone` | Delivery area by postal code | Each resource has a **capacity mode**: - `exclusive` — serves one booking at a time (a barber, a room, a machine). - `pooled` — has a capacity in **units** (40 covers per slot, 12 seats in a class, 10 deliveries per window). ## Resource groups A **resource group** is an interchangeable pool ("any barber", "laser machines"). Services require groups, not individual resources, so the engine can pick one. The assignment strategy decides which: | `assignment` | Behavior | | --- | --- | | `customer_choice` | The customer picks (for example "with Juan"); falls back to another strategy if they don't care | | `round_robin` | Rotates between members | | `least_busy` | Picks the member with the fewest bookings that day | | `fixed` | Always the same member (single machine, single room) | ```json { "name": "Barbers", "assignment": "customer_choice", "member_ids": ["res_juan", "res_pedro", "res_leo"] } ``` --- # Services and availability Source: /en/docs/concepts/services-and-availability Services, multi-resource requirements, schedules and how slots are computed. ## Services A **service** is what the customer books. | Field | Meaning | | --- | --- | | `duration_min` | Length of the service | | `step_min` | Grid for start times (every 15, 30 minutes…) | | `buffer_before_min` / `buffer_after_min` | Cleanup or preparation time blocked around the booking | | `price_cents`, `currency` | Price shown by the bot (always from the database) | | `deposit_cents` | Pix deposit required to confirm | | `lead_time_min` | Minimum notice (for example 60 minutes) | | `max_advance_days` | How far ahead customers can book | | `window_mode` | The slot is a fixed window (deliveries: 14:00–16:00) | | `fulfillment_mode` | `scheduled` (default, with a time slot) or `queue` (dispatch by queue, no fixed time) | | `intake_form` | JSON Schema of extra data to collect (insurance, address…) | | `requirements` | Which resources the service needs **at the same time** | ## Window or dispatch: two fulfillment modes Every service defines how the booking is fulfilled (`fulfillment_mode`): - **`scheduled`** (the default): the booking occupies a known time range — a fixed slot or, with `window_mode`, a window (deliveries from 14:00 to 16:00, with capacity per zone). - **`queue`** (dispatch, for water, gas or on-demand delivery): the booking **has no fixed time** — it joins a courier's backlog queue. The engine assigns it to the courier with the fewest active tasks, and each courier has a **cap on active tasks at a time**, enforced in the database. The queue uses the same states as a booking: pending → accepted → started → completed (or failed). So the same delivery business can offer scheduled windows, immediate dispatch, or both. ## Requirements A requirement says: *from this group, I need this many units*. A service can have several; a slot exists only if **all** of them are free. ```json { "name": "Laser hair removal", "duration_min": 45, "buffer_after_min": 10, "requirements": [ { "resource_group_id": "grp_professionals", "units": 1 }, { "resource_group_id": "grp_lasers", "units": 1 }, { "resource_group_id": "grp_rooms", "units": 1 } ] } ``` With two professionals but only one laser, Wagend will never offer two overlapping laser sessions. Advanced options: - `units: "party_size"` — consume as many units as people (restaurants, classes). - `offset_min` / `duration_min` on a requirement — use a resource for part of the service only (for example the wash basin during the last 15 minutes of a hair color). ## Schedules Each resource has a **schedule**: weekly rules in the resource's time zone plus **overrides** for specific dates (closed, or special hours). ```json { "tz": "America/Sao_Paulo", "weekly": { "tue": [["09:00", "19:00"]], "sat": [["08:00", "12:00"], ["13:00", "16:00"]] }, "overrides": [{ "date": "2026-12-24", "ranges": [["09:00", "13:00"]] }, { "date": "2026-12-25", "closed": true }] } ``` ## How slots are computed 1. Expand the schedules for the requested range, in each resource's time zone. 2. Remove active bookings and holds (plus buffers) and manual blocks. 3. Cut into candidate slots using the service duration and step. 4. Apply lead time, max advance, cutoff rules and party size. 5. Cross all requirements, trying group members according to the assignment strategy. 6. Return a short, ordered list. When nothing is available, the response includes `unavailable_reason` (`closed`, `no_staff`, `no_equipment`, `no_space`, `lead_time`, `max_advance`, `cutoff`, `full`) so the bot can explain why. --- # Bookings and holds Source: /en/docs/concepts/bookings-and-holds Booking states, the 10-minute hold, Pix deposits and the no-double-booking guarantee. ## Why a hold A WhatsApp conversation can take minutes: the customer picks a time, then you ask their name, then maybe a deposit. Meanwhile other customers, the booking page and the API are competing for the same slot. A **hold** reserves the slot for **10 minutes** (15 when a Pix deposit is required) and expires on its own. ## States | Status | Meaning | Occupies the slot | | --- | --- | --- | | `held` | Temporary reservation | Yes | | `pending_payment` | Waiting for the Pix deposit | Yes | | `confirmed` | Booked | Yes | | `checked_in` | The customer arrived | Yes | | `completed` | Service done | No | | `cancelled` | Cancelled (or replaced by a reschedule) | No | | `no_show` | Marked absent by a person | No | | `expired` | Hold not confirmed in time | No | ```text held ──confirm──► confirmed ──check-in──► checked_in ──► completed │ └─(deposit)─► pending_payment ──paid──► confirmed └─expire──► expired confirmed ──cancel──► cancelled ``` ## Guarantees - Every resource a booking occupies is stored as an **allocation** with a time range. - For `exclusive` resources, PostgreSQL rejects overlapping allocations with an **exclusion constraint**. - For `pooled` resources, capacity counters can never exceed the limit (checked atomically). - A multi-resource booking is written in a single transaction: if any resource clashes, nothing is booked. - `POST /holds` and `POST /holds/{id}/confirm` require an `Idempotency-Key`: retrying a request never creates a second booking. If a slot was taken while the customer was deciding, you get `409` with `code: "slot_taken"` and a list of `alternatives`. ## Reschedule and cancel - **Reschedule** creates a new booking and cancels the old one (linked by `rescheduled_to`). Reminders move automatically. - **Cancel** frees the slot and cancels pending reminders. - **No-show** is never automatic: the system suggests it, a person confirms it. ## Deposits If the service has `deposit_cents`, confirming a hold returns `pending_payment` with a Pix code. When the payment provider notifies us, the booking becomes `confirmed` and `payment.paid` is emitted. A payment that arrives after the hold expired is flagged for refund. --- # Automations Source: /en/docs/concepts/automations Reminders, confirmations and alerts as "when this happens, do that" rules. Automations are rules made of a **trigger**, optional **conditions** and an **action**. Each business template ships with sensible defaults you can edit. ## Triggers | Trigger | Example | | --- | --- | | `booking.confirmed` | Send the booking summary | | `booking.starts_in(Δ)` | 24 h before: ask for YES/NO confirmation | | `booking.ended(+Δ)` | 1 h after: suggest checking attendance | | `booking.cancelled`, `booking.rescheduled` | Notify staff | | `hold.expired` | Follow up with the customer | | `payment.paid` | Confirm and thank | | `daily_at(hh:mm)` | Send the day's agenda to staff | ## Conditions Service, resource, party size above N, channel, customer tag, number of past no-shows. ## Actions Send a message or template to the customer, notify staff, request a YES/NO confirmation, change the booking status, create an internal task, call a webhook. ## Defaults by business | Template | Automations | | --- | --- | | Barbershop, aesthetics, clinic | Reminder 24 h before with YES/NO · staff heads-up 2 h before · attendance check 1 h after · daily agenda 7:30 | | Restaurant | Reminder the day before · confirmation required for parties above 6 · daily agenda | | Deliveries | "On the way" 1 h before · delivery received confirmation | ## Execution guarantees - Jobs are created **in the same transaction** as the booking event, so they are never lost. - Before running, a job re-checks the booking. If it was rescheduled or cancelled, the old job is discarded. - Each job runs once (idempotency key) and retries with backoff; after 5 failures it shows up in the dashboard. - **Quiet hours:** nothing is sent to customers between 21:00 and 08:00 in their time zone. - YES/NO answers are parsed without AI (`sim`, `sí`, `ok`, 👍, `não`…), which is instant and free. --- # WhatsApp and AI Source: /en/docs/whatsapp-and-ai Connecting a number, what the assistant can and cannot do, staff actions and human handoff. ## Connecting a number In the dashboard, go to **Channels → WhatsApp → Connect** and scan the QR code with the business phone, just like WhatsApp Web. A workspace can connect several numbers (one per location, for example). ::callout{type="tip"} Use a number dedicated to the business. For the green badge or bulk messaging, an official WhatsApp Business Platform connection will be offered as an option. :: ## Customers vs staff The role is decided **in code**, never by what someone says in the chat: - A number linked to a team member (verified with a one-time code) gets **staff tools**. - Everyone else gets **customer tools**. | Customer tools | Staff tools (extra) | | --- | --- | | `list_services`, `find_slots`, `hold_slot`, `confirm_hold`, `my_bookings`, `cancel_my_booking`, `reschedule_my_booking`, `get_business_info`, `search_knowledge`, `handoff_to_human` | `list_today`, `block_time`, `mark_no_show`, `update_catalog_item` — always with a YES/NO confirmation | ## What the AI can and cannot do | Can | Cannot | | --- | --- | | Understand text, voice notes, typos and changes of mind | Invent times, prices or addresses | | Find slots and offer 2–3 options | Confirm a booking without the engine | | Hold a slot and ask for missing details | Use admin tools with a customer | | Answer questions from the business knowledge base | Follow instructions hidden in messages or files | | Hand over to a person | See another business's data | Workspace ids, customer ids and prices are injected by the system; the model never supplies them. ## Human handoff The team sees every conversation live in the **Inbox**. Taking over pauses the bot for that conversation; it resumes automatically after 15 minutes without activity from the operator. The assistant also hands over on request ("I want to talk to a person"). ## Voice notes and languages Voice notes are transcribed before the AI reads them, and the transcript is shown in the inbox. The assistant replies in the customer's language (Portuguese, Spanish or English). ## Models The AI layer runs through OpenRouter with a dedicated key and spending limit per workspace. The default is a fast, low-cost model; a stronger model is used only for turns that fail validation. Providers are pinned to ones that do not retain data. --- # Authentication Source: /en/docs/api/authentication API keys, scopes, test and live modes. ## API keys Send your key as a bearer token: ```http GET /v1/me HTTP/1.1 Host: api.wagend.app Authorization: Bearer wg_live_3fa9c2_Lr8t... ``` | Prefix | Mode | | --- | --- | | `wg_live_` | Production data, real messages and payments | | `wg_test_` | Sandbox: isolated data, no messages sent, simulated payments | Keys belong to **one workspace**. The workspace is always taken from the key: there is no workspace id in paths or bodies. Keys are stored hashed; the full key is shown only once at creation. ## Scopes | Scope | Allows | | --- | --- | | `slots:read` | `GET /slots` | | `bookings:read` | List and read bookings | | `bookings:write` | Holds, confirm, cancel, reschedule, check-in, no-show | | `customers:read` | Read customers | | `messages:read` | Read conversations and messages | | `messages:write` | Send messages, hand over | | `config:read` / `config:write` | Services, resources, groups, schedules, automations, knowledge | | `webhooks:manage` | Webhook endpoints | A request without the needed scope returns `403` with `code: "insufficient_scope"`. ## Rotation Create a new key, deploy it, then revoke the old one in **Developers → API keys**. Revoked keys return `401` immediately. --- # Conventions Source: /en/docs/api/conventions Formats, idempotency, pagination, errors and rate limits. ## Basics - Base URL `https://api.wagend.app/v1`. Breaking changes ship as a new version. - JSON in and out (`Content-Type: application/json`). - Timestamps in ISO 8601 **with offset** (`2026-10-15T09:45:00-03:00`). Stored in UTC. - Money in integer **cents** plus `currency` (`BRL`, `ARS`, `PYG`, `USD`). - IDs are opaque strings with a prefix (`bkg_`, `svc_`, `res_`, `grp_`, `cus_`). ## Idempotency `POST /holds`, `POST /holds/{id}/confirm`, `POST /bookings` and `POST /bookings/{id}/reschedule` **require** an `Idempotency-Key` header (for example a UUID). Repeating a request with the same key within 24 hours returns the original response. Reusing a key with a different body returns `422`. ## Pagination List endpoints use cursors: ```bash curl "https://api.wagend.app/v1/bookings?limit=50" -H "Authorization: Bearer $WAGEND_KEY" # → { "data": [...], "next_cursor": "eyJpZCI6..." } curl "https://api.wagend.app/v1/bookings?limit=50&cursor=eyJpZCI6..." -H "Authorization: Bearer $WAGEND_KEY" ``` ## Errors Errors follow RFC 9457 (`application/problem+json`): ```json { "type": "https://wagend.app/docs/api/conventions#errors", "title": "Slot no longer available", "status": 409, "code": "slot_taken", "alternatives": [{ "start": "2026-10-15T10:15:00-03:00", "end": "2026-10-15T10:45:00-03:00" }] } ``` | `code` | Status | When | | --- | --- | --- | | `invalid_request` | 400 / 422 | Validation failed | | `unauthorized` | 401 | Missing, invalid or revoked key | | `insufficient_scope` | 403 | Key lacks the scope | | `not_found` | 404 | Unknown id (or belongs to another workspace) | | `slot_taken` | 409 | Someone else got the slot | | `invalid_transition` | 409 | For example confirming a cancelled booking | | `hold_expired` | 410 | The hold expired before confirm | | `idempotency_mismatch` | 422 | Same key, different body | | `rate_limited` | 429 | Too many requests | ## Rate limits Limits apply per key and per workspace. Every response includes `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. On `429`, wait `Retry-After` seconds. --- # Endpoints Source: /en/docs/api/endpoints Every v1 endpoint at a glance, with the most common request and response shapes. The machine-readable contract lives in the repository at `packages/openapi/openapi.yaml` (OpenAPI 3.1). ## Scheduling | Method | Path | Scope | Description | | --- | --- | --- | --- | | GET | `/slots` | `slots:read` | Available slots for a service | | POST | `/holds` | `bookings:write` | Create a 10-minute hold | | POST | `/holds/{id}/confirm` | `bookings:write` | Confirm (or `pending_payment` if a deposit is required) | | DELETE | `/holds/{id}` | `bookings:write` | Release a hold | | GET | `/bookings` | `bookings:read` | List (filters: `from`, `to`, `status`, `resource_id`, `customer_id`) | | POST | `/bookings` | `bookings:write` | Hold + confirm in one call | | GET / PATCH | `/bookings/{id}` | `bookings:read` / `write` | Read, update notes or intake data | | POST | `/bookings/{id}/cancel` | `bookings:write` | Cancel | | POST | `/bookings/{id}/reschedule` | `bookings:write` | Move to a new start | | POST | `/bookings/{id}/check-in` | `bookings:write` | Mark arrival | | POST | `/bookings/{id}/no-show` | `bookings:write` | Mark absence | ## Catalog | Method | Path | Scope | | --- | --- | --- | | GET / POST | `/services` | `config:read` / `config:write` | | GET / POST | `/resources` | `config:read` / `config:write` | | GET | `/resource-groups` | `config:read` | | POST | `/schedules/{id}/overrides` | `config:write` | ## Customers and conversations | Method | Path | Scope | | --- | --- | --- | | GET | `/customers` | `customers:read` | | DELETE | `/customers/{id}` | `customers:read` + owner role (anonymizes, LGPD) | | GET | `/conversations` | `messages:read` | | GET / POST | `/conversations/{id}/messages` | `messages:read` / `messages:write` | | POST | `/conversations/{id}/handoff` | `messages:write` | ## Platform | Method | Path | Scope | | --- | --- | --- | | GET | `/me` | any | | GET / POST | `/webhook-endpoints` | `webhooks:manage` | | GET | `/events` | `bookings:read` | ## Example: slots ```http GET /v1/slots?service_id=svc_laser&from=2026-10-20T09:00:00-03:00&to=2026-10-20T20:00:00-03:00&around=2026-10-20T18:00:00-03:00 ``` ```json { "data": [ { "start": "2026-10-20T17:45:00-03:00", "end": "2026-10-20T18:30:00-03:00", "resource_ids": ["res_ana", "res_laser1", "res_room2"] }, { "start": "2026-10-20T18:45:00-03:00", "end": "2026-10-20T19:30:00-03:00", "resource_ids": ["res_carla", "res_laser1", "res_room1"] } ], "unavailable_reason": null } ``` ## Example: booking object ```json { "id": "bkg_7Qx1", "status": "confirmed", "service_id": "svc_laser", "start": "2026-10-20T17:45:00-03:00", "end": "2026-10-20T18:30:00-03:00", "party_size": 1, "customer": { "id": "cus_31", "name": "Marina", "phone": "+5521988887777", "locale": "pt" }, "allocations": [ { "resource_id": "res_ana", "start": "2026-10-20T17:45:00-03:00", "end": "2026-10-20T18:40:00-03:00", "units": 1 }, { "resource_id": "res_laser1", "start": "2026-10-20T17:45:00-03:00", "end": "2026-10-20T18:40:00-03:00", "units": 1 }, { "resource_id": "res_room2", "start": "2026-10-20T17:45:00-03:00", "end": "2026-10-20T18:40:00-03:00", "units": 1 } ], "payment": { "status": "paid", "amount_cents": 5000 }, "source": "whatsapp", "created_at": "2026-10-19T11:02:13-03:00" } ``` Allocations include the buffer time (here 10 minutes after the service). --- # Webhooks Source: /en/docs/api/webhooks Signed events for bookings, payments and conversations, with automatic retries. ## Events | Event | When | | --- | --- | | `booking.created` | A hold or booking was created | | `booking.confirmed` | A booking was confirmed (including after payment) | | `booking.cancelled` | Cancelled or expired | | `booking.rescheduled` | Moved to a new time (payload has the old and new ids) | | `booking.no_show` | Marked as absent | | `payment.paid` | A Pix deposit was received | | `message.received` | A customer message arrived | | `conversation.handoff` | A conversation was handed to a person | Create endpoints in **Developers → Webhooks** or via `POST /v1/webhook-endpoints`. The signing secret is shown once. ## Payload ```json { "id": "evt_01J9ZK3T6", "type": "booking.confirmed", "created_at": "2026-10-14T15:21:07-03:00", "workspace_id": "ws_9f2c", "data": { "booking": { "id": "bkg_7Qx1", "status": "confirmed", "start": "2026-10-15T09:45:00-03:00" } } } ``` Delivery is **at least once**: use `id` to ignore duplicates. ## Verifying the signature Every request has a header: ```text Wagend-Signature: t=1791040867,v1=5c2b9f...e81 ``` `v1` is `HMAC-SHA256(secret, t + "." + raw_body)` in hex. Reject requests older than 5 minutes. ```ts import crypto from 'node:crypto' export function verifyWagend(rawBody: string, header: string, secret: string) { const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string])) const age = Math.abs(Date.now() / 1000 - Number(parts.t)) if (!parts.t || !parts.v1 || age > 300) return false const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex') return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)) } ``` ```python import hashlib, hmac, time def verify_wagend(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) if abs(time.time() - int(parts.get("t", "0"))) > 300: return False signed = f"{parts['t']}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts.get("v1", "")) ``` ## Retries Respond with any `2xx` within 10 seconds. Otherwise we retry after 1 min, 5 min, 30 min, 2 h and 12 h. Every attempt is visible in the delivery log, where you can also resend manually. --- # MCP and AI agents Source: /en/docs/mcp Let Claude, ChatGPT or your own agent find slots, book and run a business through the Model Context Protocol. Wagend exposes an **MCP server** so AI agents can use the same engine as the WhatsApp bot: same holds, same validation, same audit log. ## Connect - URL: `https://mcp.wagend.app` (Streamable HTTP) - Auth: an API key in the `Authorization` header. OAuth will follow. Example client configuration: ```json { "mcpServers": { "wagend": { "type": "http", "url": "https://mcp.wagend.app", "headers": { "Authorization": "Bearer wg_live_..." } } } } ``` Use a `wg_test_` key while you experiment. ## Toolsets The tools an agent sees depend on the key's scopes. **Booking** (`slots:read`, `bookings:write`) — for assistants booking on behalf of a customer: | Tool | Does | | --- | --- | | `list_services` | Services with duration and price | | `find_slots` | Available slots for a service and date range | | `hold_slot` | Hold a slot for 10 minutes | | `confirm_booking` | Confirm a hold with customer details | | `cancel_booking`, `reschedule_booking`, `get_booking` | Manage an existing booking | **Admin** (`config:write`) — for the owner running the business from their AI assistant: | Tool | Does | | --- | --- | | `list_resources`, `list_today` | Team, rooms, machines and today's agenda | | `block_time` | Block a resource (vacation, maintenance) | | `create_service`, `update_schedule` | Change the catalog and hours | | `get_stats` | Bookings, occupancy and no-shows | ## Example prompts - "Book me a haircut with Juan tomorrow morning, name Carlos." - "Block the laser machine next Monday for maintenance and tell me which bookings are affected." - "How many no-shows did we have this month?" ## Docs for LLMs - [`/llms.txt`](/llms.txt) lists every documentation page. - [`/llms-full.txt`](/llms-full.txt) contains the full English documentation in Markdown. --- # Recipes by business Source: /en/docs/recipes How to model a barbershop, an aesthetics clinic, a medical office, a restaurant and deliveries. Each recipe matches a ready-made **template** you can pick during onboarding. ## Barbershop Each barber is an `exclusive` `staff` resource with their own schedule. One group, `customer_choice` with a `round_robin` fallback. ```json { "name": "Haircut", "duration_min": 30, "step_min": 15, "buffer_after_min": 5, "price_cents": 5000, "requirements": [{ "resource_group_id": "grp_barbers", "units": 1 }] } ``` ## Aesthetics Professionals, machines and rooms are separate resources. A service requires all three at the same time, so the scarcest one (usually the machine) limits the agenda. ```json { "name": "Laser hair removal", "duration_min": 45, "buffer_after_min": 10, "price_cents": 18000, "deposit_cents": 5000, "requirements": [ { "resource_group_id": "grp_professionals", "units": 1 }, { "resource_group_id": "grp_lasers", "units": 1 }, { "resource_group_id": "grp_rooms", "units": 1 } ] } ``` ## Clinic Doctor plus office, per location (one workspace per location). Health data is sensitive: the assistant never asks about symptoms, and the intake form collects only what scheduling needs. ```json { "name": "Appointment", "duration_min": 30, "buffer_after_min": 10, "intake_form": { "type": "object", "properties": { "insurance": { "type": "string" } } }, "requirements": [ { "resource_group_id": "grp_doctors", "units": 1 }, { "resource_group_id": "grp_offices", "units": 1 } ] } ``` ## Restaurant The dining room is one `pooled` resource with 40 units (covers) per slot. Duration grows with the party size, and large parties require a deposit. ```json { "name": "Table", "step_min": 30, "duration_by_units": [ { "max_units": 2, "duration_min": 90 }, { "max_units": 4, "duration_min": 120 }, { "max_units": 20, "duration_min": 150 } ], "requirements": [{ "resource_group_id": "grp_dining_room", "units": "party_size" }] } ``` Table mode (specific tables with minimum and maximum size, and combinations) is on the roadmap. ## Deliveries Each zone is a `pooled` resource with a capacity per 2-hour window. The zone is selected by postal code, and a cutoff rule closes same-day windows at noon. ```json { "name": "Delivery", "window_mode": true, "duration_min": 120, "cutoff_rule": { "same_day_until": "12:00" }, "intake_form": { "type": "object", "required": ["address", "postal_code"] }, "requirements": [{ "resource_group_id": "grp_zones", "units": 1, "select_by": "postal_code" }] } ```