# Fiuit — full documentation (English) --- # Introduction Source: /en/docs/introduction What Fiuit is, who it is for, and the building blocks you will use to take bookings, serve customers and run work with your team. Fiuit is a **scheduling and work engine** with an AI assistant that serves your customers on **WhatsApp, Telegram and your website chat**. Customers book by chatting; the system only offers times that really exist, holds them, confirms them and notifies your team. You run everything from a dashboard, and developers integrate through the **REST API**, **webhooks**, **SDKs** and **MCP**. ::callout{type="info"} This documentation describes what is already available. Anything that does not exist yet is marked **Coming soon**. :: ## Who it is for Any business that sells **someone's or something's time**. Fiuit uses generic vocabulary (task, resource, service) and adapts to your trade with a starting template: | Business | What is booked or done | | --- | --- | | Barbershop | One barber for 30 to 45 minutes | | Aesthetics | A professional **and** a machine **and** a booth, at the same time | | Clinic | A doctor and a consulting room | | Restaurant | Seats in the dining room according to party size | | Deliveries | Tasks that a courier picks from a queue, with a destination on the map | ## What you will use - **Today**: what needs doing now, your own work or the whole team's depending on your role. - **Inbox**: every conversation from every channel in one place; take over whenever needed. - **Work**: all tasks as a list, calendar or map, with actions per stage (take, release, complete, no-show). - **Customers**, **Services**, **Team and resources**: your catalog and your people. - **Bot**: personality, knowledge and simulator. **Automation**: rules and flows. - **Wagy**: the dashboard assistant that helps you set everything up by chatting. - **Settings**: Business, Status, Team, Channels, Developers, Webhooks and AI usage. ## Channels All channels use the same engine: a time booked on WhatsApp disappears instantly from the dashboard, the web chat and the API. - [WhatsApp](/docs/channels/whatsapp) with an AI assistant (text and audio). - [Web chat](/docs/channels/webchat) to paste into your site. - [Telegram](/docs/channels/telegram) with your business's own bot. - [Team Telegram](/docs/team/telegram-team): alerts and the "My day" Mini App. ## Where to go next 1. [Getting started](/docs/getting-started): from a business template to your first task, with Wagy. 2. [How it works](/docs/how-it-works): the life of a message and the engine's guarantees. 3. [Work and actions](/docs/work/tasks-and-actions) and [Flows](/docs/work/flows). 4. If you build: [API quickstart](/docs/quickstart), [SDKs](/docs/api/sdks) and [MCP](/docs/mcp). --- # Getting started Source: /en/docs/getting-started Step-by-step guide to set up your business in Fiuit with Wagy's help, connect a channel, test the bot and complete your first task. This guide takes you from zero to a business that serves customers. It takes about 20 minutes and you can ask **Wagy** for help at every step. ## 1. Create the business from a template Each business (or location) is a **project**. When you create it you pick the **business type** and Fiuit loads a template with the services, resources, schedules and stages typical for that trade: barbershop, aesthetics, clinic, restaurant or deliveries. 1. Open the dashboard and the project selector. 2. Tap **Create project**, enter the name, choose the **business type**, the time zone and the language. 3. Enter the new project. Your plan sets how many projects you can have; if you reached the limit, the selector tells you. ::callout{type="tip"} Everything in the template can be changed later. Names adapt to the trade (for example "Accept delivery" for deliveries), but underneath it is the same engine. :: ## 2. Set up with Wagy **Wagy** is the dashboard's setup assistant: a friendly bot you tell what you do, and it builds your business with you. Open it with the **Talk to Wagy** button. Only the owner and managers use it. 1. Tell it about your business ("I have a barbershop with three barbers"). 2. Wagy proposes a **change plan**: a card with what it will create or modify (services, schedules, resources, bot rules, automations). 3. Review the plan and tap **Confirm**, **Adjust** (you keep chatting and it builds another) or **Discard**. Nothing is applied without your confirmation. 4. If you do not like the result, tap **Undo**: you can undo the last applied plan for 24 hours. Wagy also shows a **First steps** checklist that ticks itself from your real data: business profile, opening hours, services, team, connected channel, bot tested, first booking and first completed task. It also sends discreet nudges (at most two a day) that you can dismiss or mute. Wagy never asks for passwords or tokens: to connect a channel it takes you to the right screen. The first messages with Wagy are paid by the platform; after that they use your plan's AI quota. ## 3. Review the basics by hand If you prefer the dashboard, these are the screens: - **Settings → Business**: name, time zone, currency, language, address and cancellation policy. - **Services**: duration, price, deposit and which resources each service needs. - **Team and resources**: people, rooms or machines with their schedules. Invite your team from **Settings → Team** (see [roles and permissions](/docs/team/roles)). ## 4. Connect a channel Go to **Settings → Channels**: - **WhatsApp**: scan a QR code. See [WhatsApp](/docs/channels/whatsapp). - **Web chat**: paste one line of code into your site. See [Web chat](/docs/channels/webchat). - **Telegram**: paste your bot's token. See [Telegram](/docs/channels/telegram). All conversations land in the same place, the **Inbox**. ## 5. Test the bot Before opening it to real customers, use the **Simulator** under **Automation → Bot**. You chat with the bot as if you were a customer, without sending real messages; the bookings it makes are test ones. Try phrases like "I want to book", "how much is it?" or "what time do you close?". Adjust the personality and load knowledge (see [Bot and knowledge](/docs/bot/knowledge)). ## 6. Your first task 1. Tap **New task** (in the top bar, on any screen). 2. Pick the service, search for or create the customer and choose the time. 3. Save. The task shows up in **Today** and in **Work**. ## 7. See your day in Today **Today** shows what needs doing now. Owners and managers see everything; team members see their own. From each task you move to the next stage with the available actions. Continue with [Work and actions](/docs/work/tasks-and-actions). ## Next steps - [Flows and conditions](/docs/work/flows): automatic reminders and alerts. - [Team Telegram](/docs/team/telegram-team): alerts and "My day" on your phone. - [Pauses and Status](/docs/channels/pauses-and-status): what to do if something goes wrong. --- # How it works Source: /en/docs/how-it-works Architecture, principles and the life of a message, from arriving on a channel to becoming a task, an action or an alert. ## Principles 1. **Deterministic core, AI as interpreter.** The AI understands the customer and calls tools. The engine computes availability, holds and confirms. The AI never invents a time or a price. 2. **The database guarantees the rules.** Double booking is impossible because PostgreSQL rejects overlapping allocations, not because the app "checks first". 3. **One operational truth, many views.** A task is a single object; you see it in Today, as a list, in the calendar or on the map. 4. **API first.** The dashboard, the bot, MCP and Wagy use the same services. If something cannot be done through the API, the API is missing it. 5. **The credential defines the business.** A key or session always belongs to one project. ## Architecture ```text Channels: WhatsApp · Web chat · Telegram · Dashboard · Wagy · API · MCP │ Gateway: credential → project · permissions · limits · pauses · signed webhooks │ Core: conversations + AI │ scheduling and task engine │ flows │ knowledge │ Data: PostgreSQL (row-level security, exclusion constraints) · Redis External: AI models · audio transcription · channel providers · maps ``` ## The life of a message 1. **It arrives.** The channel (WhatsApp, web chat or Telegram) delivers the message. The signature or key is verified, duplicates are dropped and it is stored in the Inbox. If the channel is [paused](/docs/channels/pauses-and-status), the pause applies before anything else. 2. **It is understood.** Audio is transcribed. The customer is identified (one customer can have several identities: phone, Telegram) along with the conversation. If a person took over, the bot does not answer. Channel rules (for example asking for phone and email on the web chat) apply before the AI is called. 3. **The AI asks.** The model receives the business context, the local date and time and the latest messages. It can only call tools: find slots, hold, confirm, query knowledge, save a shared location or hand off to a person. 4. **The engine decides.** Every call is validated against the business rules and only then executed. If something changed, the engine returns alternatives and the AI asks again. 5. **Exact reply.** Dates, times, services, prices and addresses come from templates filled with the real result. 6. **Work and events.** A booking is a task with stages. Every stage change or action produces events that trigger [flows](/docs/work/flows), team alerts (via [Telegram](/docs/team/telegram-team) or WhatsApp) and [webhooks](/docs/api/webhooks). ## Tasks, stages and actions Each business defines the **stages** its work goes through and the **actions** that move a task from one to another (take, release, complete, "no-show"). An action can have **effects**: assign it to whoever runs it, release it, close it with an outcome. See [Work and actions](/docs/work/tasks-and-actions). ## Flows A [flow](/docs/work/flows) says "when this happens, if this condition holds, do that": send a message, notify the team, wait, run an action. [Conditions](/docs/work/conditions) are simple expressions about the task, the customer or the time. ## Security Each business is isolated in the database itself, secrets are encrypted and every change is recorded in an audit log. You are responsible for your customers' data; Fiuit processes it on your behalf. ## Where to go next - [Bookings and holds](/docs/concepts/bookings-and-holds): the schedule's guarantees. - [Bot and AI](/docs/bot/overview): what the assistant 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 In the dashboard, a workspace is called a **project**: you can have several and switch between them with the project selector. 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 Full detail of each role and its permissions in [Roles and permissions](/docs/team/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` | Deposit required to confirm (online charging: coming soon) | | `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, Fiuit 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, 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 and the API are competing for the same slot. A **hold** reserves the slot for **10 minutes** (15 when a deposit is required) and expires on its own. ## States | Status | Meaning | Occupies the slot | | --- | --- | --- | | `held` | Temporary reservation | Yes | | `pending_payment` | Waiting for the 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 leaves the booking as `pending_payment` and the slot stays blocked. Charging the deposit online is coming soon: until it ships, the booking does not move to `confirmed` on its own and `payment.paid` is not emitted. --- # Automations Source: /en/docs/concepts/automations Simple reminder and confirmation rules, quiet hours and execution guarantees. For waits and conditions, see flows. There are two automation tools: - **Rules**: "when this happens, send that message". They are the simplest way to send reminders and confirmations. They are managed in **Automation → Rules**. - **Flows**: they wait, decide with conditions and act on tasks. See [Flows](/docs/work/flows) and [Conditions](/docs/work/conditions). ## Rules A rule has a **trigger**, the **message** it sends (a template with preview) and, if you want, a filter by stage or action. Each business creates, activates and adjusts its own: industry templates do not turn rules on by themselves. ### Triggers | Trigger | Example | | --- | --- | | Booking confirmation | Send the summary | | Some time before the start | 24 h before: send a reminder | | Some time after the end | Follow-up message | | Cancellation or reschedule | Notify the customer | | Stage or action change | When the task moves to another stage (for example "Out for delivery") | | Fixed time of day | Team alerts, such as the day's agenda | Rules aimed at customers go out through WhatsApp. Team alerts go out through Telegram, WhatsApp or email, depending on how each person receives alerts: see [Team Telegram](/docs/team/telegram-team). In each rule you see the **latest sends** with their status and the reason if one failed. ## Execution guarantees - Each send is scheduled **together with the event** that causes it, so it is not lost. - Before going out, the system checks the task again. If it was rescheduled or cancelled, the old send is discarded. - Each send goes out once and, if it fails, retries with growing waits; after 5 failures it shows in the panel. - **Quiet hours:** nothing is sent to customers between 21:00 and 08:00 in the business's time zone. The send is deferred, not lost. - YES/NO replies ("yes", "ok", "👍", "não"...) are interpreted without AI: instant and free. ## Coming soon Triggers for expired holds and for payments do not exist yet (they arrive with deposit collection). --- # WhatsApp Source: /en/docs/channels/whatsapp Connect your business number by QR, what the assistant does with customers and with your team, and how to hand a conversation to a person. ## Connect a number 1. In the dashboard, go to **Settings → Channels**. 2. Tap **Link WhatsApp**. A QR code appears. 3. On the business phone open WhatsApp, tap **Settings** (or the three dots), **Linked devices** and **Link a device**, and point the camera at the code. 4. When the status turns **Connected** you can receive messages. If the code expires, tap **Reconnect** to generate a new one. Only the owner and managers can link. The screen shows the status (connected, waiting for scan, disconnected) and how many connections your plan includes. **Disconnect** stops the bot on that number but keeps conversations and history. ::callout{type="tip"} Use a number dedicated to the business. Today the QR connection (like WhatsApp Web) is the way to connect WhatsApp. Do not share the code: it gives access to your WhatsApp. :: ::callout{type="info"} There is a **Test channel**: it is the bot simulator and does not count toward your plan. :: ## Customers and team 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 **team tools**. - Everyone else gets **customer tools**. | Customer tools | Team tools (extra) | | --- | --- | | See services, find slots, hold and confirm, see and cancel or reschedule their own bookings, query the business's information and knowledge, save a shared location, hand off to a person | See the day, block time, mark a no-show, update the catalog and advance a task's stage, always with a YES/NO confirmation | Your team can also receive alerts on [Telegram](/docs/team/telegram-team). ## What the AI can and cannot do | Can | Cannot | | --- | --- | | Understand text, audio, typos and changes of mind | Invent times, prices or addresses | | Find slots and offer 2 or 3 options | Confirm a booking without the engine | | Hold a slot and ask for missing details | Use admin tools with a customer | | Answer from the business's [knowledge](/docs/bot/knowledge) | Follow instructions hidden in messages or files | | Hand the conversation to a person | See another business's data | More in [Bot and AI](/docs/bot/overview). ## Shared location If a customer shares their location on WhatsApp, it is saved in the conversation and the Inbox shows it on a map. The bot can use it as a delivery destination or as the customer's address, always asking for a YES or NO confirmation. See [Map and addresses](/docs/work/map-and-addresses). ## Handing off to a person The team sees every conversation live in the **Inbox**. **Take over** pauses the bot in that conversation; it comes back by itself after 15 minutes without operator activity, or when you tap **Hand back to bot**. The assistant also hands off when the customer asks ("I want to talk to a person"). ## Audio and languages Audio is transcribed before the AI reads it, and the transcript shows in the Inbox. The assistant answers in the customer's language (Portuguese, Spanish or English). ## Pausing the channel If there is spam or noise, you can pause the bot or the whole channel without disconnecting. See [Pauses and Status](/docs/channels/pauses-and-status). --- # Web chat Source: /en/docs/channels/webchat Put the Fiuit assistant on your site with one line of code, with required contact details and per-channel rules. The web chat is a widget your business pastes into its own site. It talks to the **same bot** as WhatsApp (same schedules, prices and bookings), with no phone number or third-party approvals. Conversations land in the same Inbox. ::callout{type="info"} If you self-host Fiuit, the web chat ships turned off: enable it with `PUBLIC_WEBCHAT_ENABLED=true` on the API. :: ## Install step by step 1. As the **owner**, go to **Settings → Channels → Web chat**. 2. Under **Allowed sites** add each site where you will use it, one per line, with `https://` and no wildcards or paths (for example `https://www.mystore.com`). To test on your computer, `http://localhost` is accepted. 3. Choose the main color, button position, default language and an optional title (up to 40 characters). 4. Tap **Turn on web chat**. You get a **publishable key** `wg_web_…` and the code to paste. 5. Paste the snippet before `` on every page where you want the chat: ```html ``` Optional attributes: `data-locale` (`pt`, `es` or `en`; defaults to the browser language), `data-offset-bottom` (pixels, to raise the button if your site has a fixed bottom bar) and `data-api` (API origin, for test environments only). If you tap **Turn off**, the chat stops working and open conversations are closed; when you turn it on again you get a new key and must update the code. ::callout{type="tip"} There is a demo page at `/demo/webchat` where you paste the key and see the widget working. :: ## What the visitor sees - A floating button that opens the chat (full screen on phones, respecting safe areas), in Portuguese, Spanish or English. - A chat that is **short on purpose**: brief answers that suggest booking or continuing on WhatsApp. On the web the visitor is anonymous and every answer costs money. - If the bot asks for their location (for example for a delivery), it offers **Use my location**, with explicit consent. - The conversation **carries across pages and reloads**: when the visitor comes back, the chat shows what was said and any replies that arrived meanwhile (with a new-messages badge if it was closed). A **New conversation** button, in the chat's ⋯ menu, clears it and starts fresh. ## Continuity across pages and reloads When a conversation opens, the server issues an **opaque session code**. The widget keeps it in the browser storage (`localStorage`, or `sessionStorage` if unavailable) of **the site where you installed the chat**, with one key per publishable key. It uses no cookies or third-party services, and **nothing personal is stored** there: no name, phone, email or messages. Declared contact details live only on the server, so the chat does not ask for them again within the same conversation. - **On another page or after a reload**, the widget validates the code, loads the history and reconnects to receive new replies. - **Expiry:** the conversation expires after inactivity and at a maximum lifetime. If the code expired, was closed or was purged, the widget discards it and starts a new conversation, with no visible error. - **New conversation:** the visitor picks it from the ⋯ menu. The code is invalidated on the server, the browser copy is deleted and the chat starts empty. Your team keeps the previous history in the Inbox until it is purged. - **Private mode or blocked storage:** the chat still works, but the conversation does not survive a reload. - **Paused channel:** if you paused the channel, the chat is not shown even when the visitor has a saved code; when you resume, the conversation continues. ::callout{type="warning"} The code is **not a strong secret**: it is a key to **a single conversation**, never to others or to the panel. It only works from the origins you authorized in the widget, is subject to the same usage limits, and is invalidated on expiry, on New conversation and when the conversation is purged. :: ## Required contact details By default the bot **does not chat until it has a phone and an email**: for any message it replies with a fixed text (no AI) and the chat shows a small form (phone with country selector, in international format, and email). As soon as it is filled in, the visitor's first message is processed on its own, without repeating it. The details are **declared, not verified**: they do not identify the visitor and are never merged with existing customers. They are stored encrypted and deleted along with the expired conversation. ## Bot rules per channel Under **Channels → Bot rules per channel** (owner and manager) you pick the **channel** (Web chat or WhatsApp) and set: | Rule | What it does | Web chat (default) | WhatsApp (default) | | --- | --- | --- | --- | | Required details | Name, phone and/or email before chatting | phone and email | none (the phone already comes from the channel) | | When it asks | From the first message, after a few replies, or never | from the first message | never | | Text used to ask | A customizable fixed text | the default | not applicable | | Reply limit | Cap on bot replies per conversation | 6 | no cap | | What it offers at the limit | Continue on WhatsApp and/or get the information by email | both | none | | Bot style in the channel | A tone preference (up to 500 characters) | short answers | none | These are **project** rules: the server applies them before invoking the AI, even if someone uses the public API without the widget. At the limit the bot stops replying and stops spending AI, sends a fixed message with the options and the conversation stays in the Inbox as "needs follow-up", with the details the visitor left. If no WhatsApp is connected, that option is not offered. ## Security - The publishable key only opens anonymous conversations: it gives **no** access to the dashboard, the private API or customer data. You can revoke it at any time. - The business always comes from the key; the widget never sends a business identifier. - Only allowed sites can use the chat. No cookies. - **The origin check is not authentication**: it only protects against other sites in a browser. Real protection comes from usage limits. - There are caps on messages per conversation, per hour and per day. When reached, the chat asks the visitor to wait and the AI is not invoked. - Visitor text is treated as untrusted data, and the widget always renders it as plain text. - The chat only replies: it sends no proactive messages. - You can [pause](/docs/channels/pauses-and-status) the bot or the whole channel if needed; with the channel paused the widget hides. ## API Management (owner): `GET|PUT|DELETE /v1/webchat-site`. Per-channel rules (owner and manager): `GET /v1/bot/channel-policies` and `PUT /v1/bot/channel-policies/{channel}`. The widget's public endpoints are in the OpenAPI contract under `/v1/public/webchat`. --- # Telegram Source: /en/docs/channels/telegram Connect your business's Telegram bot so customers can book and ask questions as on WhatsApp, with the same Inbox and the same bot. With Telegram, your customers message **a bot that belongs to your business** and get served by the same assistant as on WhatsApp: same services, same schedules, same Inbox. It is separate from [Team Telegram](/docs/team/telegram-team), which is a single Fiuit bot for internal alerts and the "My day" Mini App. ## Connect your bot 1. In Telegram open **@BotFather** and send `/newbot`. Choose a name and a username ending in `bot`. 2. BotFather gives you a **token** (something like `123456:ABC…`). It is a secret: do not share it. 3. In the dashboard, go to **Settings → Channels → Telegram for your customers**. 4. Paste the token into **Bot token** and tap **Connect bot**. Only the owner can do this. 5. When the status reads **Receiving messages**, it works. Try it by messaging the bot from your own Telegram. The token is stored encrypted and never shown again. If you revoke it in BotFather, paste the new one with **Update token**. One bot cannot be connected to two businesses. **Disconnect** deletes the stored token and keeps the history. ## What it does - Serves with the same bot: finds slots, holds, confirms, answers from your [knowledge](/docs/bot/knowledge) and hands off to a person. - Options and confirmations appear as **buttons** inside the chat. - Accepts **text and location** (including a location shared as a place). Today it does not process voice, photos or group and channel messages. - There is no reply window like WhatsApp: you can answer whenever you want. ## Multichannel identity Each person who writes on Telegram becomes an identity of the customer, verified by Telegram and without a phone number. One customer can have several identities (WhatsApp phone, Telegram) and see them all on their profile. Outgoing messages go through the channel of the matching identity. ## Shared location If the customer shares their location, it stays in the conversation and the Inbox shows a map. The bot can save it as the task destination or as the customer's address, with a YES/NO confirmation. See [Map and addresses](/docs/work/map-and-addresses). ## Status and pauses In **Settings → Status** you see whether the bot is receiving messages, whether there were delivery errors and whether any customer blocked the bot. You can [pause](/docs/channels/pauses-and-status) the bot or the channel. ## Coming soon Instagram: not available yet. --- # Pauses and Status Source: /en/docs/channels/pauses-and-status Pause a channel or your project's AI in an emergency, and check Status and AI usage to see whether your business is serving customers. There are three tools for when something goes wrong (spam, unexpected costs, Inbox noise) and two screens to see how your service is doing. ## Pause a channel Every connected channel (WhatsApp, Telegram and web chat) can be paused without disconnecting it, in **Settings → Channels**. Owner and manager only. There are two levels: | Level | What happens | When to use it | | --- | --- | --- | | **Pause bot** | Messages are still stored and reach the Inbox, but the bot does not reply or spend AI. The conversation switches to human mode, flagged for attention. Optionally the customer gets **one fixed message** (you choose the text; by default "A person will reply shortly."). | You want to handle things by hand for a while. | | **Pause channel** | Everything arriving on the channel is **discarded**: no customers, conversations or messages are created, and there is no AI. Only a count of discarded messages is kept. The team cannot send on that channel either. The web chat stops showing. | There is spam or abuse. | Steps: 1. Go to **Settings → Channels** and find the channel. 2. Tap **Pause bot** or **Pause channel**. 3. Write a reason (optional), decide whether to send the fixed message and confirm. 4. To go back, tap **Resume**. While something is paused, the dashboard shows a notice with who paused it and since when. Everything is recorded in the audit log. ## Pause the project's AI It is an emergency switch in **Settings → AI usage**: it cuts **all** of the project's AI consumption (bot, Wagy, knowledge and audio transcription) before another cent is spent. - The bot replies with a fixed message and moves the conversation to the Inbox with attention flagged. - Knowledge imports wait, without error, and resume when you turn AI back on. - Everything else keeps working: calendar, tasks, customers and manual actions. Tap **Pause AI** and confirm; to go back, **Resume AI**. Owner and manager only. ## Automatic pause for spam or abuse On by default, in **Settings → AI usage**. Two protections, neither of which ever pauses the whole channel (so no legitimate customer is lost): - **Silence per contact.** If one contact sends more than **15 messages in 5 minutes** (or **60 in an hour**), the bot stops answering them in that conversation for **30 minutes**. Messages are still saved and reach the Inbox; only the AI and automatic replies are cut. The conversation is flagged for attention with the reason "possible spam", you get a spam notification and, from the conversation, **Reactivate bot** brings it back early. When the cooldown ends the bot comes back by itself. - **AI spend brake.** If the project goes over **300 AI calls in 10 minutes**, the project's AI pauses itself (reason: automatic) and we notify you. This pause **does not lift by itself**: a burst like that can be a loop or an attack, and resuming on its own would spend again. You see it in Status and AI usage, and you resume it with **Resume AI** once you have checked what happened. Your team's messages and the simulator's do not count. Owner and manager can turn it off or change the thresholds and the cooldown. ## My service status **Settings → Status** (owner and manager) answers in plain language whether your business is serving customers. It shows an overall traffic light and one card per part: - **WhatsApp**: whether it is connected and receiving messages. - **Automatic assistant**: whether the bot is on. - **Smart replies**: whether AI quota is available this month. - **Web chat** and **Telegram**: allowed sites and active sessions, last message, usage caps reached, Telegram webhook, delivery errors and customers who blocked the bot. - The **available channels** you have not connected yet, with a link to Channels. Each card with a problem has a link to fix it. If something is paused, that is shown too. ## AI usage **Settings → AI usage** shows how much AI your business used this month and your plan's limit, with warnings at 80 % and at the limit. - At the limit, AI features pause and AI automations wait in the inbox; everything else keeps working. - If usage cannot be confirmed, "Uncertain usage" appears and AI may be limited. - The amount is the known subtotal, not an invoice. - With **Request more** (owner) you notify the Fiuit team to raise the limit. ## Who can do what | Action | Owner | Manager | Team and read-only | | --- | --- | --- | --- | | Pause or resume channels and AI | Yes | Yes | No | | See Status and AI usage | Yes | Yes | No | | Request more AI | Yes | No | No | | Connect Telegram or publish the web chat | Yes | No | No | More about permissions in [Roles and permissions](/docs/team/roles). --- # The bot and the AI Source: /en/docs/bot/overview What your business's bot does, what it cannot do, how to configure it, test it in the simulator and when a conversation passes to a person. The bot is the assistant that serves your customers on WhatsApp, Telegram and your website chat. It understands what they write, offers real time slots and leaves the booking ready. **Everything that matters is calculated by the system**: the bot does not invent times, prices or addresses, and it never confirms anything on its own. ## What it can and cannot do | It can | It cannot | | --- | --- | | Understand text, voice notes, typos and changes of mind | Invent times, prices or addresses | | Look up slots and offer 2 or 3 options | Confirm a booking without the scheduling engine | | Hold a slot and ask for missing details | Use admin tools with a customer | | Answer questions from the [knowledge base](/docs/bot/knowledge) | Follow instructions hidden in a message or a file | | Save a shared location (with your YES/NO confirmation) | See another business's data | | Hand the conversation to a person | | Dates, times, services and prices in every message come from templates filled with system data. Voice notes are transcribed before the AI reads them, and the transcript shows in the Inbox. ## Configure the bot Go to **Automation → Bot**. It has four tabs: 1. **Personality**: the bot's name, tone (friendly, professional or casual), the first message (you can use `{name}` and `{business}`), behavior instructions and default language. 2. **Knowledge Base**: what the bot knows about your business. See [Bot knowledge](/docs/bot/knowledge). 3. **Simulator**: test the bot without sending real messages. 4. **Sources**: web pages, site sections and listings that the bot reads and keeps up to date. ::callout{type="tip"} Instructions are a style preference ("keep it short", "be informal"). They do not change the rules: the bot can never bypass the scheduling engine. :: ## Test it in the simulator In the **Simulator** tab you write as if you were a customer. You see the bot's replies, its configuration, the active knowledge items and the **test bookings** it creates. These are test bookings, kept apart from the real ones. **Restart Conversation** starts over. ## When a person steps in Every conversation shows in the **Inbox**, with the state "AI replying", "Wants a person" or "Person handling". - **Take over**: if you write a reply, the bot pauses in that conversation. - **Hand back to bot**: do it manually when you are done. - If you are inactive for **15 minutes**, the bot resumes on its own. - The bot also moves the conversation to the Inbox when the customer asks to talk to a person or when something fails. ## Limits per channel Each channel can have its own rules. On the web chat, for example, the bot asks for phone and email before chatting and stops replying after a set number of messages. See [WebChat](/docs/channels/webchat). ## How much AI your business uses In **Settings → AI usage** you see how much your business used this month and your plan's limit. At 80 % a warning appears; if the limit is reached, AI features pause, and the bot replies with a fixed message and moves the conversation to the Inbox. You can also cut consumption manually with the emergency pause: see [Pauses and Status](/docs/channels/pauses-and-status). ## Next steps - [Bot knowledge](/docs/bot/knowledge): teach it to answer with your business information. - [WhatsApp](/docs/channels/whatsapp) and [Telegram](/docs/channels/telegram): connect your channels. --- # Bot knowledge Source: /en/docs/bot/knowledge Teach the bot with texts, files, web pages, a section of your site or listings (spreadsheets, CSV, JSON and RSS) that stay up to date. The **knowledge base** is what the bot consults to answer questions: opening hours, policies, products, prices, news. It has two origins: what you enter by hand and what you import from **sources** that refresh themselves. Everything is in **Automation → Bot**, in the **Knowledge Base** and **Sources** tabs. ::callout{type="warning"} Do not put customers' personal data in any source. What is in a source can show up in the bot's replies. The bot **quotes** that data as information; it never treats it as orders. :: ## Add knowledge by hand In **Knowledge Base**, tap **New Item** and choose the type: - **FAQ**: a question and its answer. - **Text**: a title and free content (for example, the cancellation policy). - **Product/Service**: name, description and an optional price. - **File**: drag a file (5 MB maximum). It is processed and can be turned on or off. Each item has an **Active** switch: the bot only uses active ones. You can search and filter by type. ## Sources: what updates itself In **Sources**, tap **Add source** and choose the type. The system imports it within minutes and refreshes it on its own; you can change the **frequency** (every hour, 6 hours, day or week), **Refresh now**, **Pause** and **Resume**. Each card shows the status: *Queued*, *Up to date*, *Error* or *Paused*. A source that fails several times in a row pauses itself. If you delete it, its imported items go too (items written by hand are untouched). Common rules: public `http(s)` addresses only, up to 2 MiB per source and 10 sources per project. Pages that require login are not read, and the site's `robots.txt` is respected. ### A web page 1. **Add source → Web page**, scope **Page**. 2. Enter the public URL (for example, your pricing page). 3. The page text is read, without running JavaScript. ### A section of your site For the bot to learn several pages of the same site without adding them one by one: 1. **Add source → Web page**, scope **Site section**. 2. Enter the prefix, for example `https://yoursite.com/help`. 3. Choose the **maximum pages** (50 by default, up to 200). The system reads the domain's `sitemap.xml` and takes only pages on the same site that start with that prefix. On every refresh it adds new pages, updates changed ones and archives those that disappeared. The card shows "N pages imported" and **Show pages** lists each one with its status (*Imported* or *Archived*). ::callout{type="info"} There is no crawler: if the site does not publish a `sitemap.xml`, the source shows the matching notice and you need to import pages one by one. :: ### A listing: spreadsheet, CSV, JSON or RSS Useful for products, prices or news you already keep in a spreadsheet, a store or a feed. 1. **Add source** and choose **Spreadsheet**, **CSV**, **JSON API** or **RSS**. 2. Paste the link. For a Google Sheet: *File → Share → Publish to web*, or make it viewable by anyone with the link; the first row must contain headers. 3. In **Field mapping**, write which column feeds each field: Name (required), Description, Price, Category, Link and **Stable key**. For JSON you write the path, for example `products[].name`. RSS needs no mapping. 4. Tap **Show preview**: it shows the first valid rows and how many were skipped. 5. If it looks right, **Save source**. Up to 1000 rows per source. If you use a **Stable key** (a code that does not change, like a SKU), renaming a product updates it; without a key, renaming creates a new item and deactivates the old one. For an API that needs a credential you can enter an `Authorization` header or a URL parameter: it is stored encrypted and never shown again. ## If something goes wrong The source card explains the cause: | Message | What to do | | --- | --- | | We could not find that page (404) | Check the address | | The page asks for login | Only public pages are read | | The site did not respond | It retries on its own later | | The site does not publish `sitemap.xml` | Import pages one by one | | The sitemap has no pages under that address | Check the prefix | | The sitemap is not valid | Review the site's sitemap | | The site does not allow reading (`robots.txt`) | It is the site's decision | | A mapping column does not exist | Check the column names | | No text found to import | The page or listing is empty; what was already imported is not deactivated | ::callout{type="tip"} After adding knowledge, try it in the [Simulator](/docs/bot/overview): ask what a customer would ask. :: To integrate sources through the API, see the `/knowledge` endpoints in [Endpoints](/docs/api/endpoints). --- # Work and actions Source: /en/docs/work/tasks-and-actions How the day to day is organized in Fiuit, with Today, Inbox and Work, and how task stages and actions work, such as take, release and close. In Fiuit, everything that needs doing is a **task**: an appointment, a table, a visit, a delivery. A conversation can create one; you also create them by hand with the **New task** button or through the API. The panel gives you three main destinations: **Today**, **Inbox** and **Work**. The rest lives under **More**. ## Today, Inbox and Work - **Today** is your day's board. It shows what **needs your attention**, what is **next**, what is **in progress**, what is **later today** and, collapsed, what is **completed**. Team members see only their own (**Mine**); owner and manager switch between **Mine** and **All**. - **Inbox** gathers the conversations that need a person. See [The bot and the AI](/docs/bot/overview). - **Work** is where you see all tasks. You look at it along two axes that combine. ### Work: set × form First you choose **which work to see** (the set): | Set | What it shows | | --- | --- | | **Unassigned** | What needs attention: no owner, no time or waiting for confirmation | | **Today** | Today's work for the whole business | | **Upcoming** | What is coming, grouped by day | | **All** | All work, with the order and grouping you pick | | **Mine** | What is assigned to you | Then you choose **how to see it** (the form): | Form | When it appears | | --- | --- | | **List** | Always | | **Calendar** | If there are resources with a schedule (by day, by professional or by week) | | **Map** | If there are tasks with a location. See [Map and addresses](/docs/work/map-and-addresses) | "If it has nothing, it does not show": a form only appears when your business needs it. All of them open the same task detail. Filters (search, priority, tag, dates), order and grouping apply to every form. ## The task and its stages Each task is in a **stage**. Stages are your business's vocabulary: a barbershop uses *Confirmed → In progress → Completed*; a delivery uses *Unassigned → Accepted → On the way → Delivered*. Behind them are fixed states (hold, confirmed, in progress, completed, cancelled, absent, failed), so numbers and statistics mean the same in every industry. The task detail shows the customer, the date and resource, notes, form data, the origin (for example the WhatsApp conversation it came from) and the **comments and activity**. ### Forms per service A service can ask for its own data when the task is created: the delivery address, the reason for the visit, the equipment model. That data is filled in on the task and stays with it. It is defined on the service (**Services**). ## Actions Actions are a task's buttons. Depending on the stage, role and business, the panel shows **one big main action** (for example *Accept delivery* or *On my way*) and the rest under **Other actions**. Each action can ask for confirmation or a short form (a comment, a reason). An action can do more than change stage. The available effects are: | Effect | What it does | | --- | --- | | **Take** | The task is assigned to whoever takes it. If someone else got there first, it warns and does not assign it twice | | **Assign** | The owner or manager chooses who gets it | | **Release** | The task goes back to *Unassigned* for someone else to take (only before it starts) | | **Outcome** | Closes the task as **completed**, **absent** ("not home"), **refused** ("does not want it") or **cancelled**, with an optional reason | | **Reschedule** | On tasks with no set time, moves the due time a little later | Everything an action does happens together or not at all: if something fails, nothing is left half done. And an action **never grants extra permissions**: a team member cannot assign tasks even if the button existed. ### Unassigned tasks and queues Some jobs have no set time, like deliveries. They enter as **Unassigned**, whoever can do them **takes** them and then follows the sequence. A team member sees the tasks assigned to them and those their group can take, without the customer's phone or email until they take it. A courier first sees only the approximate area. Example in the deliveries industry: 1. An order arrives: it is **Unassigned**. 2. A courier taps **Accept delivery**: the task is theirs. (Or the manager taps **Assign delivery**.) 3. If they cannot, they tap **Release delivery** and it goes back to the queue. 4. They tap **On my way**, then **Arrived**. 5. They close with **Complete**, or with **Not home** or **Refused** (plus a reason). ### Each industry with its own vocabulary Stages and actions come from your industry's template (barbershop, aesthetics, clinic, restaurant, deliveries) and can be adjusted. Barbershop, aesthetics, clinic and restaurant come with *Start*, *Complete*, *Cancel* and *No-show*. To rename them or add actions, today you do it through the API or the MCP server: see [API](/docs/api/endpoints) and [MCP](/docs/mcp). ## Work evidence An action can ask for the **location** at that moment (for example when marking "Arrived") and a task accepts **attachments** (photo, document, audio, note). They are private: only people with access to that task see them, download links expire after 60 seconds and files are deleted after a retention period (30 days by default). The owner or manager can delete a location. ## What each role sees | Role | Today | Work | Customers | | --- | --- | --- | --- | | Owner and manager | The whole business | Everything | Yes | | Staff | Their own and what they can take | Their own and what they can take | No | | Viewer | View only | View only | No | More in [Roles and permissions](/docs/team/roles). ## Automate the work Every stage change or action can trigger a message or a flow: see [Flows](/docs/work/flows). --- # Map and addresses Source: /en/docs/work/map-and-addresses The Work map view, address autocomplete, the location a customer shares and how privacy is protected. If your business serves customers at their address (deliveries, visits, field service), Fiuit stores where each task is and shows it on a map. ## The Map view In **Work**, the **Map** form appears when there is at least one task with a location. If you do not need it, you do not see it. - Each task is a marker, with color, symbol and name according to its status: pending, confirmed, in progress, completed or with a problem. Color is not the only thing that tells them apart. - Tap a marker to open the task. - The side list shows the same tasks (useful with a keyboard or screen reader). Tasks without a location are listed separately. - The map fits itself to the results and shows up to 500 tasks; if there are more, narrow the filters. Filters and the set (Unassigned, Today, Upcoming...) work as in the list. - The **legend** counts tasks by status. The map uses Google Maps. If Google is unavailable, a fallback map is used and the view keeps working. ### Light and dark mode A floating selector has three options: **Automatic**, **Light** and **Dark**. Automatic follows your panel's theme. It is remembered per browser. ## Address autocomplete When you enter a customer's address, or a task's destination (**New task** or the edit form), a search box appears: 1. Start typing street and number (from 3 characters). 2. Pick a suggestion: the full address (street, number, neighborhood, city, postal code, country) and the point on the map are saved. 3. A **small map with a pin** appears: drag it or tap the map to adjust the exact spot. If you prefer, **enter the address manually** and tap **Place on the map**: the system looks it up and leaves the pin for you to move. If autocomplete is unavailable, it tells you and what you typed moves to manual entry: you lose nothing. ::callout{type="info"} A service "has a destination" when its form asks for the delivery address. For that service, the destination is entered with this same search box. :: ## Location shared by the customer A customer can send their location on **WhatsApp** or **Telegram** (or from the web chat, with explicit consent). - In the **Inbox** the message shows with a small map, the name or address if the channel provided them, and an **Open in map** link. - If the service asks for a destination, the bot can save that location as the task's destination or as the customer's address. **It asks first with YES/NO** in the conversation. - On the web chat, the widget offers "Use my location" only when the bot asked for it, and the visitor must explicitly accept. ## Privacy - Address and location are personal data: they do not go to logs or to the panel URL. - A **courier** sees only the **approximate area** of a task that has not been taken. The exact address appears when they take it. See [Work and actions](/docs/work/tasks-and-actions). - Google receives what its map needs to draw. In the Map view it does **not** receive addresses or coordinates of your tasks. Autocomplete and "Place on the map" do send it the address text you search. If your customers have special requirements, mention it in your privacy notice. ## For whoever manages the account The Google map needs a browser key and three Google Maps Platform APIs enabled (Maps JavaScript, Places and Geocoding). It is a platform setting, not a business one: if the map or autocomplete do not appear, contact support. --- # Flows Source: /en/docs/work/flows What to do automatically when something happens in your business: waits, conditions and alerts. Load examples, activate, pause and review history. A **flow** is what the system does by itself when something happens: *when* an event occurs, *wait* a while, *check* a condition and *do* something (alert the team, send a message, run an action). It is the next step after the [simple rules](/docs/concepts/automations) for reminders. Flows are in **Automation → Flows**. ## The pieces **Trigger: when it starts.** Some of the available events: - a task is created, assigned, modified, completed or cancelled; its status changes; a task becomes overdue; - **an action is executed** (for example, someone marks "Not home"); - a conversation starts, is resolved or receives a message; - a customer is created or becomes inactive; - an attachment is added or a location is recorded. **Steps: what it does.** | Step | What it does | | --- | --- | | **Wait** | A duration (for example 2 hours), until a date, until a time of day, some time before or after the task's start or end, or until an event happens with a maximum wait. Up to 90 days | | **Condition** | "If this condition is met": continues on the *If met* or *If not met* branch. See [Conditions](/docs/work/conditions) | | **Alert the team** | An internal alert about a task, by Telegram, WhatsApp or email depending on how each person receives alerts | | **Send a message to the customer** | A message in their conversation | | **Run an action** | Uses a task action (for example *Release delivery*), with the same rules as the panel | | **Leave a comment** | A note on the task | | **Create, update or assign a task** | Operations on tasks | | **Calculate data** | Prepares values for later steps | A flow has up to 32 steps, with no loops. Flows **bypass no rule**: every step is validated again by the system, with the role of the owner or manager who created the flow. A wait cancels itself if the task is cancelled. ## Start with examples Your industry template comes with example flows. In **Flows**, tap **Load examples** (owner only). They stay **inactive** until you activate them, and loading again does not duplicate them. | Industry | Example | | --- | --- | | Deliveries | **Delivery not taken for 2 h**: when a delivery is created, wait 2 hours and, if it is still unassigned, alert the team | | Deliveries | **Accepted delivery that does not leave in 30 min**: if someone accepts and does not leave, the delivery goes back to *Unassigned* | | Barbershop | **Customer's third no-show**: when a no-show is marked, if it is the third, alert to ask for a deposit next time | ## View, activate and pause Each flow shows its state (**Active**, **Paused** or **Draft**) and its last run. When you open it you see **What it does** in plain language: the trigger and the steps, with the branches. 1. Tap **Activate** and confirm. From then on it acts on its own every time the trigger happens. 2. **Pause** stops starting new runs; those already in progress finish their path. 3. In **Run history** you see each run with its status (*Done*, *Running*, *Waiting*, *Failed*, *Cancelled*...), how long it waits and which branch it took. A **Draft** flow lets you edit the condition: **Edit condition**, **Test this condition** and **Save as new version**. To change an active or more complex flow, ask Wagy, the assistant ([Getting started](/docs/getting-started)), or use the API. ::callout{type="tip"} Before activating a flow with a condition, test it with **Test this condition** on a real task. Nothing is changed: it only tells you whether it comes out true or false. :: ## Flows and rules **Rules** (the tab next to Flows) remain the simplest way to send a reminder or a confirmation. A flow that comes from a classic rule is managed in Rules. Flows are for what needs to wait, decide or act on tasks. ## For developers Flows are managed through the API (`/v1/automation-flows`, owner or manager with a panel session only). See [Endpoints](/docs/api/endpoints). The events that trigger them are the same ones your [webhooks](/docs/api/webhooks) receive. --- # Flow conditions Source: /en/docs/work/conditions How to write a condition in a flow, which data it can look at, ready-made examples by industry and how to test it before activating. A **condition** decides which branch a [flow](/docs/work/flows) follows. It is written as a short sentence that gives **true or false**, for example "the service costs more than 100 and the customer is VIP". A condition only **looks at** data: it changes nothing. ## How it is written | What | Example | | --- | --- | | Compare | `==` (equal), `!=`, `<`, `<=`, `>`, `>=` | | Combine | `&&` (and), `\|\|` (or), `!` (not) | | Text | `"delivery"` in quotes | | Is in a list | `"vip" in customer.tags` | | Contains | `service.name.contains("Haircut")` | | If / then | `workItem.priority == "urgent" ? now.hour < 22 : now.hour < 18` | | A field | `customer.no_show_count`, with a dot | That is the whole language: no loops or custom functions. Conditions are validated **when you save** the flow and again when you activate it. ## Which data it can look at | Object | What it has | | --- | --- | | `trigger` | The event that started the flow. With an executed action: `action_key` (the action), `from_stage`, `to_stage` | | `workItem` | The task: `status`, `stage`, `priority`, `tags`, `party_size`, `start_at`, `due_at`, `age_minutes` (minutes since it was created), `assigned` (whether it has an owner) | | `customer` | `name`, `locale` (language), `tags`, `no_show_count` (real no-shows) | | `service` | `name`, `price_cents` (the price in cents: 100.00 is `10000`), `deposit_cents`, `duration_min` | | `resource` | `name`, `kind` | | `now` | **Your business's** time: `hour`, `minute`, `weekday` (0 = Monday ... 6 = Sunday) | | `variables` | Values calculated by an earlier step | For privacy, a condition **cannot see** phones, emails, documents, addresses or customer notes. If the event does not carry a task, `workItem`, `customer` and `service` arrive empty and the condition fails with a clear message instead of silently answering "false". ## Examples **VIP with an expensive service: alert the manager** ```text service.price_cents > 10000 && "vip" in customer.tags ``` **"Not home" before 6 pm** (trigger: an action is executed) ```text trigger.action_key == "nao_estava" && now.hour < 18 ``` **Third no-show: ask for a deposit next time** ```text trigger.action_key == "ausencia" && customer.no_show_count >= 3 ``` **Task unassigned for more than 2 hours** (after a 2 h Wait step) ```text !workItem.assigned && workItem.age_minutes > 120 ``` **Outside business hours** (Saturday, Sunday or outside 9 to 18) ```text now.weekday >= 5 || now.hour < 9 || now.hour >= 18 ``` **Urgent task** ```text workItem.priority == "urgent" || "urgente" in workItem.tags ``` **Customer who speaks Portuguese** ```text customer.locale == "pt" ``` **Deposit pending for over an hour** ```text workItem.stage == "aguardando_sinal" && workItem.age_minutes >= 60 ``` **Long haircut service** ```text service.name.contains("Corte") && service.duration_min >= 45 ``` ::callout{type="info"} Action and stage keys (`ausencia`, `nao_estava`, `aguardando_sinal`) are the ones from your industry template. If you renamed them, use yours. Text values such as `"Corte"` must match the name your service has. :: ## Test a condition In a flow's detail, tap **Test this condition** (or **Test a condition**): 1. Write the condition. 2. Pick a **real task** from your project with the search box. 3. If it looks at `trigger.action_key`, write the action key in **Simulate the action**. 4. Tap **Test**. It shows **True** or **False** and, below, **What the condition saw**: exactly the data it decided with. Nothing is executed. ## Common errors When saving, the message says which step failed and why: | Problem | What it means | | --- | --- | | The field does not exist | You used data that is not available (for example `customer.email`) | | Types that do not match | You compared text with a number (`customer.name > 5`) | | Not true or false | The condition must be a yes or no question | | Function not allowed | Only `contains` exists | | Unknown object | Only the objects in the table above can be used | | Too long or costly | Split it into two conditions with another step in between | A condition accepts up to 1024 characters and is evaluated in a fraction of a second. --- # Zones Source: /en/docs/work/zones Group your team by zone, with its area, service days and hours and a per-slot limit, and let each task find its own zone. A **zone** is a group of people with an area and service days and hours. It works for deliveries, technical visits, routes or any job done at the customer's address: for example, the North Zone serves Tuesdays and Thursdays and the South Zone, Wednesdays and Fridays. ## Create a zone In **Team and resources**, open the **Zones** tab and tap **New zone**. Each zone has: - **Name.** - **Area:** neighborhoods, cities or postal codes, as tags. A postal code matches by prefix: `20` covers every code that starts with 20. - **People:** the ones who serve the zone. A person can belong to several zones. - **Days and hours:** for example Tuesday and Thursday from 9 to 6. If you leave it empty, the zone does not restrict days. - **Per-slot limit** (optional): the maximum number of tasks that fit in each time slot of the zone. You can also ask Wagy: "North Zone, Tuesday and Thursday, Camila and Diego". Wagy builds the plan, you review the change and confirm it. ## How the zone is assigned Each task with an address finds its zone by itself, comparing postal code, neighborhood and city while ignoring accents and capital letters. When several match, the postal code wins, then the neighborhood, then the city. If none match, the task is left **without a zone** and shows up in **Today → Needs your attention**; fixing the address assigns it automatically. In **Work** you can filter by zone or see only what has no zone, and on the **map** each task shows its zone. ## Times and the bot When a delivery or visit has a time, only the zone's days and hours are offered, crossed with the availability of its people and the per-slot limit. The bot asks the customer for their neighborhood, city or postal code and says, for example: "For your zone (North Zone) we serve Tuesday or Thursday". ## Unclaimed tasks An unclaimed task in a zone is announced, and offered for **Take**, only to the people in that zone. Owners and managers see them all and can assign them to anyone. ::callout{type="info"} The "zone" used to be a resource with capacity. Those resources keep working; the migration command converts them into zones without losing any task. :: --- # Team Telegram Source: /en/docs/team/telegram-team Get work alerts on Telegram, take tasks with one tap and open your day in the "My day" Mini App. Team Telegram is the fastest way for everyone in the business to know what is theirs: an assigned task, an unclaimed job, a reminder or the daily summary. It works from your phone, with nothing to install except Telegram. ::callout{type="info"} There is **one single Fiuit bot for all teams**. It is not your business's bot: that one talks to your customers ([customer Telegram](/docs/channels/telegram)). The two are independent and never mixed. :: ## Link your Telegram The link is **per person**, not per business: you do it once and it applies to every business you have access to. 1. In the dashboard, open **My account** (your name, at the bottom of the menu) and tap **Link Telegram**. 2. Tap **Open Telegram** and, in the bot, **Start**. The bot confirms the link. 3. To unlink, send `/leave` to the bot or unlink from **My account**. The link is single-use and expires after 15 minutes. If it expired, generate another. If you do not see **Link Telegram**, the team bot is not enabled in your environment yet: let us know. ## Which alerts you get | Alert | When it arrives | | --- | --- | | Assigned task | Someone assigned a task to you | | Unclaimed job | There is a queue task your group can take | | Reminder | An appointment is coming up (team advance notice) | | Daily report | A summary of your day | | Changes | One of your tasks was cancelled or changed | Alerts come with buttons so you can act without opening the dashboard: - **Open** opens the Mini App straight on that task. - **Take** and **Complete** run the action with your permissions, just like the dashboard. If someone else took the job first, the bot tells you it is already taken. - Alerts never include data from another business. Reminders, the daily report and the team notice come from your [flows and rules](/docs/work/flows). If you turn one on, the team receives it on this channel. ## Choose how to receive them In **My account → Alert preferences** you set: - **Preferred channel:** automatic, Telegram, WhatsApp or email. On automatic, Telegram if you have it linked; if not, WhatsApp (with your verified link) and, if not that either, email. - **What to mute:** assigned, unclaimed, reminders, daily report or changes. If Telegram cannot deliver an alert (for example, you blocked the bot), the alert falls back to the next channel. ## Commands | Command | What it does | | --- | --- | | `/start` | Links your account (with the link from **My account**) | | `/today` | Shows your day, per business | | `/leave` | Unlinks your Telegram | ## "My day" Mini App The bot's menu button (to the left of the text field) opens **My day**: the dashboard in a compact format inside Telegram, without asking for a password. It shows the same as you would see in the dashboard for your role: **Today** and **Work** (with "Mine" by default for staff) and a business selector if you have several. - The session lasts 60 minutes and only reaches Today, Work and tasks. It gives no access to API keys, team, channels or webhooks. - It ends on its own if you unlink Telegram or are removed from the business. ## Limits and security - The bot only talks in private chats with one person. - Telegram allows 1 message per second per chat; if many alerts arrive together, the next ones wait in a queue and still arrive. - The link and the Mini App are always validated on the server. A used, expired or other-account link gets the same neutral message. To see what each person can view and do, read [Roles and permissions](/docs/team/roles). For developers: the endpoints `GET/DELETE /me/telegram`, `POST /me/telegram/link` and `GET/PUT /me/notification-preferences` belong to the user's account (dashboard session) and are not used with API keys. --- # Roles and permissions Source: /en/docs/team/roles What each role (owner, manager, staff and read-only) can see and do, how to invite the team and how to work with several projects. Each person joins a business (a **project**) with a role. The role decides which screens they see and what they can change. The server enforces it: hiding a button in the dashboard is not the only barrier. ## The four roles | Role | For whom | What they do | | --- | --- | --- | | **Owner** (`owner`) | Whoever administers the account | Everything, including API keys, the team and channels | | **Manager** (`manager`) | Supervisors | Runs the business: catalog, team, customers, bot, flows, channels and webhooks. Does not administer API keys | | **Staff** (`staff`) | Whoever serves or delivers | Sees and works on **their own**: their tasks and what their group can take | | **Read-only** (`viewer`) | Partners, accountants | Views Today, Work and conversations; changes nothing | ## What each one sees in the dashboard | Section | Owner | Manager | Staff | Read-only | | --- | :---: | :---: | :---: | :---: | | Today and Work | Yes | Yes | Their own | Yes (read) | | Conversations | Yes | Yes | Only theirs | Yes (read) | | Catalog, Team, Customers | Yes | Yes | No | No | | Bot and Flows | Yes | Yes | No | No | | Settings, Channels, AI and usage | Yes | Yes | No | No | | Webhooks | Yes | Yes | No | No | | API keys (Developers) | Yes | No | No | No | Staff look up customers with a **minimal view**: name, partially hidden phone, tags and no-shows, with a minimum number of characters and a result cap. A courier sees a delivery's exact destination only after taking it; before that they see an approximate area. ## Invite the team In **Team** you can invite in two ways: - **By email:** for owner, manager, staff or read-only. The person accepts the invitation and creates a password. - **By contact (WhatsApp):** only for operational staff, without a password. They are sent an access link to their agenda. You associate a staff person with one or more **resources** (their agenda, their vehicle): that defines which tasks they see and which they can take. You can change a role or remove someone at any time; their sessions and their Telegram stop working immediately. ## Several projects An account can have **several projects** (businesses or branches), each with its own team, agenda, channels and data, fully isolated. The number of projects depends on the plan. - With more than one project, the dashboard's **project selector** lets you switch between them. - Only the account owner can create a new project, from a template by industry (barbershop, aesthetics, clinic, restaurant or deliveries). - Your role can be different in each project. ## Platform support When you need help, the Fiuit team can enter your project temporarily: - Always with a written **reason** and for at most **60 minutes**. - In **read** mode (sees what a manager would see, changes nothing) or **assist** mode (can adjust catalog, schedules and business settings). - Never able to see or change API keys, passwords, channel secrets, billing or the team, nor send messages to your customers. - Every entry and exit is logged and the owner sees them in the business activity, with who, in which mode and why. ## API keys and permissions API keys have no role: they have **scopes** (narrow permissions, such as `slots:read` or `bookings:write`) and only the owner creates them. See [Authentication](/docs/api/authentication). --- # Notifications Source: /en/docs/team/notifications The panel bell, push on your phone, which notices you get and where, quiet hours and the project rules. Notifications tell you what needs attention without you having to watch the panel all the time. They match what you see under **Needs attention** in the Inbox and in Today. ## The bell In the panel, the bell shows how many notifications you have not read and updates by itself, with no reload. If nothing is pending you see just the bell, with no number. Tap the bell to see the list: each notification takes you to what needs attention (the conversation, the task, the channel or the AI). **Mark all as read** clears the count. Read notifications are deleted after 14 days and all of them after 60. ## Which notices there are | Notice | When it arrives | | --- | --- | | A conversation needs attention | The bot handed off or failed and nobody has replied yet | | Task assigned to me | Someone assigned a task to you | | Unassigned task | There is a task your group or zone can take | | Channel down or in error | A messaging channel disconnected | | AI paused or quota used up | The project's AI was paused or the monthly quota ran out | | Possible spam | A paused channel discarded many messages in one day | | Wagy suggestion | Wagy has something to propose | ## Choose what you get and where In **My account → Notifications** you choose, for each notice, whether you want it in the **panel**, as **push**, by **Telegram** or by **email**. There you also set: - **Quiet hours:** inside the range nothing goes out of the panel (no push, Telegram or email). Notifications stay in the bell. - **Daily email summary:** one email a day with what is still unread. It is off by default. If your environment does not have email sending set up yet, the summary does not go out until it does. ## Push on your phone and browser In **My account → Notifications → Push on this device** tap **Turn on push** and accept the browser permission. Each device is turned on separately. If your project does not have push available yet, the screen says so. ::callout{type="info"} **On iPhone and iPad** push only works if you add the panel to your home screen: open it in Safari, tap **Share → Add to Home Screen**, open Fiuit from the new icon and turn push on from there. :: ## Project rules (owner) In **Settings → Notifications** the owner decides who receives channel-down and spam alerts (owner only, owner and managers, or the whole team) and which notices are active for the whole project. Personal choices, such as channels and quiet hours, are made by each person in My account. --- # Quickstart Source: /en/docs/quickstart Create your first booking through the API in five minutes using a key for a test business. This guide creates a real booking, so use a **production key** (`wg_live_...`) from a business you set up for testing. `wg_test_` keys are read-only: they can list and read, not create or change anything. A sandbox for writes is Coming soon. To try the bot without WhatsApp, use the simulator in the dashboard. ::callout{type="info"} Base URL: `https://api.fiuit.com/v1`. Every request needs `Authorization: Bearer `. :: ## 1. Get a key In the dashboard, go to **Settings → Developers**, create a **Production** key, and select the scopes `slots:read`, `bookings:write` and `config:read`. The key is shown **only once**. ```bash export WAGEND_KEY="wg_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxx" ``` ## 2. Check who you are ```bash curl https://api.fiuit.com/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": "live" } ``` The workspace always comes from the key. You never send a workspace id. ## 3. List services ```bash curl https://api.fiuit.com/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.fiuit.com/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.fiuit.com/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.fiuit.com/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 deposit, the status is `pending_payment`. Charging the deposit online is coming soon. ## Same flow in JavaScript ```ts const api = (path: string, init: RequestInit = {}) => fetch(`https://api.fiuit.com/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 - Would rather not build the client by hand? Use the [TypeScript and Python SDKs](/docs/api/sdks). - React to bookings with [webhooks](/docs/api/webhooks). - See every endpoint in the [reference](/docs/api/endpoints) and more examples in the [recipes](/docs/recipes). - Connect an AI agent with the [MCP server](/docs/mcp). --- # Authentication Source: /en/docs/api/authentication API keys, scopes, and which routes you can use with a key and which are dashboard-only. ## API keys Keys are created by the **owner** in **Settings → Developers**. They are shown once. Send your key as a bearer token: ```http GET /v1/me HTTP/1.1 Host: api.fiuit.com Authorization: Bearer wg_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxx ``` | Prefix | Mode | | --- | --- | | `wg_live_` | Production data and real messages | | `wg_test_` | Read-only: it can list and read, not create or change anything. A sandbox for writes is Coming soon | Keys belong to **one project** (workspace). The project always comes from the key: there is no workspace id in paths or bodies. Keys are stored hashed. ## Scopes | Scope | Allows | | --- | --- | | `slots:read` | `GET /slots` | | `bookings:read` | Read bookings and tasks (`/bookings`, `/work-items`), stage history, attachments, team statistics | | `bookings:write` | Holds, confirm, cancel, reschedule, check-in, no-show, complete, run actions and edit a task's priority, tags and due date | | `customers:read` | Read and search customers, their history, summary and comments | | `customers:write` | Create and edit customers | | `messages:read` | Read conversations and messages | | `messages:write` | Send messages, switch the mode (bot or person), resolve, mark as read | | `config:read` / `config:write` | Services, resources, groups, schedules, rule-based automations, knowledge and bot | | `settings:write` | Replace and revert stages, actions and forms (`/stage-config`) | | `webhooks:manage` | Outgoing webhook endpoints | A request without the needed scope returns `403` with `code: "insufficient_scope"`. ## What is dashboard-only These areas do **not** accept API keys: they use the session of a team member (with their role) and are protected with CSRF. If you call them with a key, the API answers that they are not available for that type of credential. - Channels, channel and AI pauses, service status. - Automation flows (`/automation-flows`), the Wagy assistant and its plan. - Knowledge sources (`/knowledge/sources`) and the per-channel bot policy. - Team, invitations, API keys, projects and platform support. - Team Telegram and alert preferences. The [endpoint list](/docs/api/endpoints) marks which is which. ## Rotation Create a new key, deploy it, then revoke the old one in **Settings → Developers**. Revoked keys return `401` immediately. --- # Conventions Source: /en/docs/api/conventions Formats, idempotency, pagination, errors and rate limits of the v1 API. ## The basics - Base URL `https://api.fiuit.com/v1`. Breaking changes ship as a new version; additive changes (new fields) can arrive at any time, so ignore fields you do not know. - JSON in and out (`Content-Type: application/json`). - Dates in ISO 8601 **with offset** (`2026-10-15T09:45:00-03:00`). They are stored in UTC. - Amounts in integer **cents** plus `currency` (`BRL`, `ARS`, `PYG`, `USD`). - IDs are opaque strings. Fields without a value arrive as explicit `null`. - The machine-readable contract is `packages/openapi/openapi.yaml` (OpenAPI 3.1) and the [recent changes](/docs/api/endpoints#recent-changes) summarize what is new. ## Idempotency `POST /holds`, `POST /holds/{id}/confirm`, `POST /bookings`, `POST /bookings/{id}/reschedule` and `POST /bookings/{id}/actions/{key}` **carry** the `Idempotency-Key` header (for example a UUID). Repeating a request with the same key within 24 hours returns the original response. Reusing the key with a different body returns `422` with `code: "idempotency_conflict"`. `POST /customers` accepts `Idempotency-Key` optionally. `POST /webhook-endpoints` does **not** accept it, because its response contains the secret. ## Pagination List endpoints use cursors: ```bash curl "https://api.fiuit.com/v1/bookings?limit=50" -H "Authorization: Bearer $WAGEND_KEY" # → { "data": [...], "next_cursor": "eyJpZCI6..." } curl "https://api.fiuit.com/v1/bookings?limit=50&cursor=eyJpZCI6..." -H "Authorization: Bearer $WAGEND_KEY" ``` When `next_cursor` is `null`, there are no more pages. ## Errors Errors follow RFC 9457 (`application/problem+json`): ```json { "type": "https://fiuit.com/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 | | --- | --- | --- | | `validation_error` | 422 | Body or parameter validation failed | | `invalid_api_key` | 401 | Key missing, invalid or revoked | | `idempotency_key_required` | 400 | The `Idempotency-Key` header is missing on an operation that needs it | | `insufficient_scope` | 403 | The key lacks the scope | | `not_found` | 404 | Unknown id (or from another project) | | `slot_taken` | 409 | Someone else took the time | | `already_claimed` | 409 | Another team member took the queue task first | | `customer_exists` | 409 | A customer with that phone already exists | | `invalid_transition` | 409 | For example confirming a cancelled booking | | `version_conflict` | 409 | Someone edited the same resource first (stale version) | | `hold_expired` | 410 | The hold expired before confirming | | `idempotency_conflict` | 422 | Same key, different body | | `rate_limited` | 429 | Too many requests | Every error response carries `code`: use it to decide what to do, not the text of `title`. ## Rate limits Limits apply per key and per project (a bucket with a burst of 120 requests and a refill of 2 per second). Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. On a `429`, wait `Retry-After` seconds. --- # Endpoints Source: /en/docs/api/endpoints The v1 API endpoints grouped by topic, with the scope each one needs, and which areas are dashboard-only. The machine-readable contract lives in the repository, in `packages/openapi/openapi.yaml` (OpenAPI 3.1). This page summarizes what you can use **with an API key**. Areas marked "dashboard-only" use the session of a team member (see [Authentication](/docs/api/authentication)). ## Schedule: times and bookings | Method | Path | Scope | Description | | --- | --- | --- | --- | | GET | `/slots` | `slots:read` | Available times 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 | | POST | `/bookings` | `bookings:write` | Hold and confirmation in one call | | GET | `/bookings` | `bookings:read` | List (`from`, `to`, `status`, `resource_id`, `customer_id`) | | GET / PATCH | `/bookings/{id}` | `bookings:read` / `write` | Read, update notes or intake data | | POST | `/bookings/{id}/cancel`, `/reschedule`, `/check-in`, `/no-show`, `/complete`, `/fail` | `bookings:write` | Change the state | ## Work: tasks and actions A booking is a **task** with stages. Tasks without a time (for example deliveries) sit in a group's queue until someone takes them. See [Work and actions](/docs/work/tasks-and-actions). | Method | Path | Scope | Description | | --- | --- | --- | --- | | GET | `/work-items` | `bookings:read` | Unified query: filters by `view` (`inbox`, `today`, `upcoming`), `status`, `stage`, `unassigned`, `priority`, `tag`, `origin`, `q`, date range and geographic box | | PATCH | `/work-items/{id}` | `bookings:write` | Change priority, tags or due date (with optional `expected_version`) | | POST | `/bookings/{id}/actions/{key}` | `bookings:write` | Run a stage action (take, release, complete, "not home"…) with `Idempotency-Key` | | GET | `/bookings/{id}/stage-history` | `bookings:read` | Stage history | | GET | `/bookings/{id}/comments`, `/timeline` | `bookings:read` | Comments and timeline | | GET | `/bookings/{id}/attachments`, `/location-events` | `bookings:read` | Work evidence | | GET | `/conversations/{id}/work-items` | `bookings:read` | Tasks created from a conversation | | GET | `/team/overview`, `/resources/{id}/stats` | `bookings:read` | Team occupancy and statistics | ## Catalog and stages | Method | Path | Scope | | --- | --- | --- | | GET / POST | `/services`, `/resources`, `/resource-groups`, `/schedules` | `config:read` / `config:write` | | GET / PATCH / DELETE | `/services/{id}`, `/resources/{id}`, `/resource-groups/{id}`, `/schedules/{id}` | `config:read` / `config:write` | | POST / DELETE | `/schedules/{id}/overrides`, `/schedules/{id}/overrides/{date}` | `config:write` | | GET | `/schedule-overrides` | `config:read` | | GET | `/stage-config`, `/stage-config/versions`, `/stage-config/versions/{version}` | `config:read` | | PUT | `/stage-config` | `settings:write` | | POST | `/stage-config/versions/{version}/revert` | `settings:write` | ## Customers | Method | Path | Scope | Description | | --- | --- | --- | --- | | GET | `/customers` | `customers:read` | List and search (`q`: name, email, company, tag or phone; `phone`, `tag`) | | POST | `/customers` | `customers:write` | Manual creation. The phone is unique: if it exists, `409 customer_exists` | | GET / PATCH | `/customers/{id}` | `customers:read` / `write` | Read or edit (includes address with `formatted`, `lat` and `lng`) | | GET | `/customers/{id}/bookings`, `/summary`, `/timeline`, `/comments` | `customers:read` | History, live summary, timeline and comments | A customer can have several **identities** (WhatsApp, Telegram, WebChat) in `identities[]`. ## Conversations | Method | Path | Scope | | --- | --- | --- | | GET | `/conversations`, `/conversations/{id}`, `/conversations/{id}/messages` | `messages:read` | | POST | `/conversations/{id}/messages` | `messages:write` (answers `409 channel_paused` if the channel is paused) | | POST | `/conversations/{id}/mode`, `/read`, `/resolve` | `messages:write` | ## Bot and knowledge | Method | Path | Scope | | --- | --- | --- | | GET / PUT | `/bot` | `config:read` / `config:write` | | GET / POST | `/knowledge`, `/knowledge/files` | `config:read` / `config:write` | | POST | `/knowledge/search` | `config:read` | | PATCH / DELETE | `/knowledge/{id}` | `config:write` | | GET / POST / PATCH | `/automations`, `/automations/{id}` | `config:read` / `config:write` | ## Outgoing webhooks | Method | Path | Scope | | --- | --- | --- | | GET / POST | `/webhook-endpoints` | `webhooks:manage` | | GET / PATCH / DELETE | `/webhook-endpoints/{id}` | `webhooks:manage` | | POST | `/webhook-endpoints/{id}/rotate-secret`, `/ping`, `/deliveries/{delivery_id}/resend` | `webhooks:manage` | | GET | `/webhook-endpoints/{id}/deliveries` | `webhooks:manage` | Details in [Webhooks](/docs/api/webhooks). ## Platform | Method | Path | Scope | | --- | --- | --- | | GET | `/me` | any | ## Dashboard-only (they do not accept API keys) | Area | Routes | | --- | --- | | Channels and pauses | `/channels`, `/channels/whatsapp`, `/channels/telegram`, `/channels/service-status`, `/channels/pauses`, `/channels/{channel}/pause` and `/resume`; see [Pauses and Status](/docs/channels/pauses-and-status) | | Per-channel bot policy | `/bot/channel-policies` (see [WebChat](/docs/channels/webchat)) | | Knowledge sources | `/knowledge/sources` (see [Knowledge](/docs/bot/knowledge)) | | Flows | `/automation-flows` (see [Flows](/docs/work/flows)) | | Project AI | `/ai/usage`, `/ai/pause`, `/ai/resume`, `/ai/spam-guard`, `/ai/notices` | | Wagy | `/assistant/*`, the dashboard's setup assistant | | Team and account | `/team/*`, `/api-keys`, `/workspaces`, `/me/telegram`, `/me/notification-preferences` | | WebChat | `/webchat-site` (management) and `/public/webchat/*` (public, with the widget's publishable key) | ## 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", "stage": "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" }, { "resource_id": "res_laser1", "start": "2026-10-20T17:45:00-03:00", "end": "2026-10-20T18:40:00-03:00" }, { "resource_id": "res_room2", "start": "2026-10-20T17:45:00-03:00", "end": "2026-10-20T18:40:00-03:00" } ], "priority": "normal", "tags": [], "source": "whatsapp", "created_at": "2026-10-19T11:02:13-03:00" } ``` Allocations include the buffer (here, 10 minutes after the service). On queue tasks, `start` and `end` are `null` until someone takes them. ## Recent changes These are the API's new features, all additive. The full history lives in the repository (`docs/api/CHANGELOG.md`). - **Customers:** manual creation, extended data, address with `formatted`, `lat` and `lng`, and multichannel `identities[]`. - **Tasks and actions:** unified `GET /work-items` query, action executor with effects (take, release, outcomes) and `409 already_claimed`. - **Messages with location:** WhatsApp, Telegram and WebChat store the shared location in the conversation. - **Pauses:** when a channel is paused, sending a message answers `409 channel_paused`. - **Knowledge:** site-type sources with `max_pages` and errors by cause. --- # Webhooks Source: /en/docs/api/webhooks Signed notifications for what happens to your bookings, with automatic retries, a delivery log and examples to verify the signature. ## Events These booking events can be subscribed to today: | Event | When | | --- | --- | | `booking.created` | A hold or booking was created | | `booking.confirmed` | A booking was confirmed | | `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 | | `booking.completed` | Appointment completed | The contract also reserves `payment.paid`, `message.received` and `conversation.handoff`, but they **cannot be subscribed to yet and are not emitted** (collecting the deposit is Coming soon). An endpoint that asks for them gets `422`. Create endpoints in **Settings → Webhooks** in the dashboard or with `POST /v1/webhook-endpoints` (`url` and `events`). The signing secret (`whsec_…`) is shown once. ```bash curl -X POST https://api.fiuit.com/v1/webhook-endpoints \ -H "Authorization: Bearer $WAGEND_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/wagend", "events": ["booking.confirmed", "booking.cancelled"] }' ``` ## Payload ```json { "id": "evt_3f6c1d9e0b7a4c2f8e5d1a9b7c3e6f20", "type": "booking.confirmed", "created_at": "2026-10-14T15:21:07-03:00", "workspace_id": "b3a6c8e2-5f1d-4c7a-9e0b-2d8f4a1c6e53", "data": { "booking": { "id": "6d1f0c4a-7b2e-4a58-9c3d-0e5f8a2b1c47", "status": "confirmed", "start_at": "2026-10-15T12:45:00+00: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 and compute the signature over the **raw body**, without re-serializing the JSON. ```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. ## Security and limits - The URL must be `https` and resolve to a public address: we reject `localhost`, private networks, link-local and cloud metadata addresses (when you register it **and** on every delivery). Redirects are not followed (a `3xx` counts as a failure). - Extra headers: `Wagend-Event` (type) and `Wagend-Delivery` (delivery id). Every attempt is signed with a fresh timestamp. - Up to 10 endpoints per workspace. After 10 consecutive failures the endpoint is disabled (re-enable it with `PATCH /v1/webhook-endpoints/{id}` and `{"active": true}`). - `POST /v1/webhook-endpoints/{id}/ping` sends a `webhook.ping` test event; `POST /v1/webhook-endpoints/{id}/rotate-secret` issues a new secret (shown once; the old one stops working immediately). - With an API key use the `webhooks:manage` scope. The log is at `GET /v1/webhook-endpoints/{id}/deliveries` and resending at `POST …/deliveries/{delivery_id}/resend`. --- # TypeScript and Python SDKs Source: /en/docs/api/sdks Typed clients generated from the OpenAPI contract: how to use them today from the repository, with booking, error, pagination and idempotency examples. Fiuit has two SDKs that are **generated from the contract** `packages/openapi/openapi.yaml`, so their types always follow the API. ::callout{type="warning"} The SDKs are **not published yet** on npm or PyPI (Coming soon). Today you use them from the repository, as explained below. If you would rather not depend on them, the [REST API](/docs/quickstart) works with any HTTP client. :: ## TypeScript The client (`packages/sdk-ts`) is a light wrapper over `fetch` with types for every route. ### Install from the repository ```bash cd packages/sdk-ts npm ci npm run build # genera dist/ ``` In your project, install it from that folder: ```bash npm install /path/to/wagendapp/packages/sdk-ts ``` ### Book a slot ```ts import { createWagendClient, WagendError } from "@wagend/sdk"; const api = createWagendClient({ baseUrl: "https://api.fiuit.com/v1", apiKey: process.env.WAGEND_API_KEY!, // wg_live_xxx }); try { const { data: services } = await api.GET("/services"); const serviceId = services!.data![0].id!; const { data: slots } = await api.GET("/slots", { params: { query: { service_id: serviceId, from: "2026-10-15T08:00:00-03:00", to: "2026-10-15T13:00:00-03:00" } }, }); const slot = slots!.data[0]; // 10-minute hold const { data: hold } = await api.POST("/holds", { params: { header: { "Idempotency-Key": crypto.randomUUID() } }, body: { service_id: serviceId, start: slot.start, party_size: 1, resource_ids: slot.resource_ids }, }); // Confirmation with the customer data const { data: booking } = await api.POST("/holds/{id}/confirm", { params: { path: { id: hold!.id! }, header: { "Idempotency-Key": crypto.randomUUID() } }, body: { customer: { name: "Carlos", phone: "+5491155550000", locale: "es" } }, }); console.log(booking!.status); // "confirmed" } catch (err) { if (err instanceof WagendError) { console.error(err.status, err.code, err.retryAfter); } else { throw err; } } ``` - **Authentication:** the client adds `Authorization: Bearer` for you. - **Idempotency:** on every write it adds an `Idempotency-Key` if you do not pass one. To retry an operation, pass the **same** key yourself in both attempts (valid for 24 hours). The TypeScript type declares it in `params.header` for the routes that require it. - **Errors:** any non-2xx response throws `WagendError` with `.status`, `.code`, `.problem` (RFC 9457) and `.retryAfter` (seconds, on 429). ### Pagination ```ts let cursor: string | undefined; do { const { data: page } = await api.GET("/bookings", { params: { query: { limit: 50, cursor } } }); for (const booking of page?.data ?? []) console.log(booking.id); cursor = page?.next_cursor ?? undefined; } while (cursor); ``` ### Customers and actions ```ts // Search and create customers (scopes customers:read and customers:write) const { data: found } = await api.GET("/customers", { params: { query: { q: "carlos" } } }); await api.POST("/customers", { body: { name: "Ana", phone: "+5491155551111", tags: ["vip"] } }); // Run a stage action, for example assigning a delivery to a courier (scope bookings:write). // "atribuir" is the key of that action in the deliveries template. await api.POST("/bookings/{id}/actions/{key}", { params: { path: { id: taskId, key: "atribuir" }, header: { "Idempotency-Key": crypto.randomUUID() } }, body: { data: {}, resource_id: courierResourceId }, }); ``` Action keys (`key`) are defined by your business's stage configuration: look them up with `GET /stage-config`. ## Python The client (`packages/sdk-py`) uses `httpx` and generated Pydantic v2 models. ### Install from the repository ```bash pip install /path/to/wagendapp/packages/sdk-py # or, with uv: uv pip install /path/to/wagendapp/packages/sdk-py ``` ### Book a slot ```python from datetime import UTC, datetime from wagend import Fiuit, WagendError with Fiuit("https://api.fiuit.com/v1", "wg_live_xxx") as api: try: service = api.list_services()[0] slots = api.list_slots( str(service.id), datetime(2026, 10, 15, 8, tzinfo=UTC), datetime(2026, 10, 15, 13, tzinfo=UTC) ) hold = api.request( "POST", "/holds", json={"service_id": str(service.id), "start": slots[0].start.isoformat(), "party_size": 1}, idempotency_key="booking-carlos-2026-10-15", ) booking = api.request( "POST", f"/holds/{hold['id']}/confirm", json={"customer": {"name": "Carlos", "phone": "+5491155550000"}}, ) print(booking["status"]) except WagendError as err: print(err.status, err.code, err.retry_after) ``` - `list_services()` and `list_slots()` return typed models. For the other routes use `api.request(method, path, params=…, json=…)`; you can validate the response with `wagend.models`. - **Idempotency:** writes carry an `Idempotency-Key` (one is generated if you do not pass `idempotency_key`). Retry with the same key. - **Errors:** `WagendError` with `.status`, `.code`, `.problem` and `.retry_after` (on 429). - **Pagination:** `api.paginate("/bookings", params={"limit": 50})` walks every page. ```python for booking in api.paginate("/bookings", params={"limit": 50}): print(booking["id"]) ``` ## Rate limits The limit is per key and per project. On a `429`, wait `retry_after` / `retryAfter` seconds before retrying. See [Conventions](/docs/api/conventions). ## Regenerating the SDKs If the contract changes, `make sdk` regenerates both from `openapi.yaml` and `make sdk-check` fails if they are out of date. --- # MCP and AI agents Source: /en/docs/mcp Connect Claude, ChatGPT or your own agent to the Fiuit MCP server to look up times, book and run the business with your API key. Fiuit includes an **MCP server** (Model Context Protocol) so AI agents use the same engine as the WhatsApp bot: same holds, same validation and same audit trail. The server is a thin client of the [API](/docs/api/endpoints): every tool calls the API with **your own key**, so MCP never grants more permissions than the key. ::callout{type="warning"} The server lives in the repository (`packages/mcp`) and today you run it yourself. The hosted server at `mcp.fiuit.com` is not deployed yet: it is Coming soon. So is OAuth authentication. :: ## Run the server You need Python 3.12+ and [uv](https://docs.astral.sh/uv/). ```bash cd packages/mcp uv sync WAGEND_API_URL=https://api.fiuit.com MCP_PORT=8700 uv run python -m wagend_mcp # escucha en http://127.0.0.1:8700/ (Streamable HTTP) ``` Variables: | Variable | Purpose | | --- | --- | | `WAGEND_API_URL` | API URL (default `https://api.fiuit.com`) | | `MCP_TOOLSETS` | Toolsets to expose: `booking`, `admin` or both (default `booking,admin`) | | `MCP_HOST`, `MCP_PORT` | Where it listens (default `127.0.0.1:8700`) | | `MCP_ALLOWED_HOSTS` | Allowed hosts (DNS rebinding protection) | ## Connect a client Authentication is an API key in the `Authorization` header. Without a key in a valid format, the server answers `401` before entering the protocol. With Claude Code: ```bash claude mcp add --transport http wagend http://127.0.0.1:8700/ \ --header "Authorization: Bearer wg_live_xxx" ``` Or with the JSON configuration of any client that supports HTTP: ```json { "mcpServers": { "wagend": { "type": "http", "url": "http://127.0.0.1:8700/", "headers": { "Authorization": "Bearer wg_live_xxx" } } } } ``` To try it without an agent: `npx @modelcontextprotocol/inspector` (Streamable HTTP transport, the same URL and header). ## Tools All tools in the active toolset are listed, but each one only works if the key has the scope it needs; otherwise it returns the API's `403` error. Writes are audited with actor `mcp`. **Bookings** (`booking`): for assistants that book on behalf of a customer. | Tool | Scope | Does | | --- | --- | --- | | `list_services` | `config:read` | Services with duration and price | | `find_slots` | `slots:read` | Available times for a service between two dates | | `hold_slot` | `bookings:write` | 10-minute hold | | `confirm_booking` | `bookings:write` | Confirms a hold with the customer's data | | `cancel_booking`, `reschedule_booking` | `bookings:write` | Manages an existing booking | | `get_booking` | `bookings:read` | Booking detail | **Administration** (`admin`): so the owner can run the business from their AI assistant. | Tool | Scope | Does | | --- | --- | --- | | `list_resources` | `config:read` | Team, rooms, machines and groups | | `list_today` | `bookings:read` | Today's bookings | | `block_time` | `config:write` and `bookings:read` | Closes whole days of a resource (max. 31). It does not cancel existing bookings: it returns them for a person to decide | | `create_service`, `update_service` | `config:write` | Creates or changes a service, with its booking form | | `update_schedule` | `config:write` | Changes the weekly schedule or the time zone | | `get_stats` | `bookings:read` | Bookings, occupancy and no-shows per resource | | `get_stage_config`, `list_stage_config_versions`, `get_stage_config_version` | `config:read` | Stages, actions and forms, with their version history | | `update_stage_config`, `revert_stage_config` | `settings:write` | Replaces or reverts the stages (creates a new version; history is kept) | ::callout{type="tip"} Configuration writes require `confirm=true`. Without it, the tool does not call the API: it returns `confirmation_required` with what it would send, so the agent can show it to a person and repeat with the confirmation. :: ## Example requests - "Book a haircut with Juan tomorrow morning, under the 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 documentation in Markdown. --- # Recipes Source: /en/docs/recipes API recipes (customers, tasks, actions, webhooks) and how to model a barbershop, aesthetics, clinic, restaurant and deliveries. ## API recipes All of them use `$WAGEND_KEY` as in the [quickstart](/docs/quickstart). ### Create a customer and book for them in one go Needs `customers:write` and `bookings:write`. The phone number is the customer's unique identifier: if it already exists, the API answers `409 customer_exists`. ```bash curl -X POST https://api.fiuit.com/v1/customers \ -H "Authorization: Bearer $WAGEND_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Marina", "phone": "+5521988887777", "locale": "pt", "tags": ["vip"], "address": { "street": "Rua das Flores", "number": "120", "city": "Rio de Janeiro", "formatted": "Rua das Flores 120, Rio de Janeiro" } }' curl -X POST https://api.fiuit.com/v1/bookings \ -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", "customer": { "name": "Marina", "phone": "+5521988887777" } }' ``` `POST /bookings` does the hold and the confirmation in a single call. ### Read what is due today Needs `bookings:read`. `GET /work-items` is the same query the **Today** screen uses. ```bash curl "https://api.fiuit.com/v1/work-items?view=today&limit=50" \ -H "Authorization: Bearer $WAGEND_KEY" # Only what nobody has taken yet curl "https://api.fiuit.com/v1/work-items?unassigned=true&limit=50" \ -H "Authorization: Bearer $WAGEND_KEY" ``` Each task carries its `stage` and its `available_actions`. ### Assign and close a delivery with actions Needs `bookings:write`. Actions are named by the business's stage configuration (with the deliveries template: `atribuir`, `soltar`, `iniciar`, `concluir`, `nao_estava`). Look them up in `GET /stage-config` or in the task's `available_actions`. ```bash # Assign the delivery to a courier (resource_id is the courier's resource) curl -X POST https://api.fiuit.com/v1/bookings/$TASK_ID/actions/atribuir \ -H "Authorization: Bearer $WAGEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": {}, "resource_id": "'$COURIER_ID'" }' # Close it as completed curl -X POST https://api.fiuit.com/v1/bookings/$TASK_ID/actions/concluir \ -H "Authorization: Bearer $WAGEND_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "data": {} }' ``` If two people take the same task at once, one wins and the other gets `409 already_claimed`. The "Accept delivery" action (`aceitar`) is for staff from the dashboard or Telegram: it takes the task with the resource of whoever runs it. ### Receive webhook notifications Register an endpoint (see [Webhooks](/docs/api/webhooks)) and verify the signature before processing. Minimal Express example: ```ts import express from 'express' import { verifyWagend } from './verify' // the function from the Webhooks page const app = express() app.post('/wagend', express.raw({ type: 'application/json' }), (req, res) => { const raw = req.body.toString('utf8') if (!verifyWagend(raw, req.header('Wagend-Signature') ?? '', process.env.WAGEND_WEBHOOK_SECRET!)) { return res.sendStatus(400) } const event = JSON.parse(raw) if (event.type === 'booking.confirmed') console.log('New booking', event.data.booking.id) res.sendStatus(200) // answer 2xx quickly; duplicates are dropped by event.id }) app.listen(3000) ``` ## Templates by business 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" }] } ```