> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.recurrente.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.recurrente.com/_mcp/server.

# Bookings (Citas)

> Read availability, book appointments, classes and stays, take deposits and no-show fees, and manage the agenda from your own systems or an agent

Citas is Recurrente's booking product: appointments with your team, classes with limited seats, hotel stays by the night, gift cards and contracts. Everything a business does with it in the dashboard can also be done through the API, the `recurrente` CLI and the MCP server.

Unlike most booking APIs, **the money lives on the booking**: what it costs, what the payer already paid, what is left, and the checkout where they pay. You do not have to reconcile a booking with a payment yourself.

> **Note**
>
> Every business account has Citas. A personal account does not: every Citas endpoint answers it with `403` and `code: bookings_not_enabled`.

## Concepts

| Object         | Prefix | What it is                                                                                                                                                    |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Service        | `svc_` | What makes a product bookable: duration, who performs it, what it occupies, and what the payer leaves to hold the time. It rides on the product as `booking`. |
| Staff member   | `stf_` | A person bookings are made with, with their own weekly hours and time off.                                                                                    |
| Resource       | `res_` | What a booking occupies besides a person: a chair, a room, equipment, a hotel room.                                                                           |
| Booking        | `bk_`  | A payer's hold on a time: one appointment, one seat in a class, or one stay.                                                                                  |
| Booking series | `ser_` | A booking the business repeats ("every 2 weeks, 10 times").                                                                                                   |
| Class session  | `cls_` | One occurrence of a class, with its capacity.                                                                                                                 |

A service has a `kind`: `appointment` (one payer, one staff member), `class` (several payers in one session) or `lodging` (nights instead of minutes).

## Book a time in three calls

**1. Read the free times.** `GET /availability` answers with the same times the public booking page shows: it subtracts what is already booked, buffers, minimum notice and the account's daily limit.

```bash
curl "https://app.recurrente.com/api/availability?service_id=svc_ab12cd34&from=2026-10-07&to=2026-10-08" \
  -H "X-SECRET-KEY: sk_live_..."
```

```json
[
  { "starts_at": "2026-10-07T09:00:00-06:00", "ends_at": "2026-10-07T10:00:00-06:00",
    "staff_member_id": "stf_ab12cd34", "resource_id": null, "class_session_id": null, "seats_left": null }
]
```

Dates without a time (`2026-10-07`) are midnight in the business's time zone. Up to 31 days per request.

**2. Book one of them.** Send the `starts_at` you were offered and identify the customer by `customer_id`, or by `customer[phone]` and `customer[name]` to create them. Use a unique `Idempotency-Key` per booking.

```bash
curl -X POST https://app.recurrente.com/api/bookings \
  -H "X-SECRET-KEY: sk_live_..." -H "Idempotency-Key: booking-7781" \
  -H "Content-Type: application/json" \
  -d '{"service_id":"svc_ab12cd34","starts_at":"2026-10-07T09:00:00-06:00",
       "customer":{"name":"Lucía Pérez","phone":"+50255551234"}}'
```

**3. Listen for the outcome.** The service's payment policy decides what happens:

| Policy         | Status after booking                                                    | What the payer does                                                   |
| -------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `none`         | `confirmed` (or `pending` if the business accepts each booking by hand) | Nothing.                                                              |
| `card_on_file` | `pending` with `checkout_url`                                           | Leaves a card for the no-show fee; the booking confirms when they do. |
| `deposit`      | `pending` with `checkout_url`                                           | Pays the deposit; the booking confirms when they pay.                 |
| `full`         | `pending` with `checkout_url`                                           | Pays the full price.                                                  |

A `pending` booking holds the time until `hold_expires_at`. If nobody pays, it is cancelled by `system`. Subscribe to `booking.confirm` instead of polling.

If someone took the time first, the answer is `422` with `code: slot_taken`; read availability again.

## The money on a booking

| Field                     | Meaning                                                                                                  |
| ------------------------- | -------------------------------------------------------------------------------------------------------- |
| `total_in_cents`          | What the booking costs, frozen when it was made.                                                         |
| `paid_in_cents`           | What the payer already paid (deposit, prepayment, protection, balance).                                  |
| `balance_in_cents`        | What is left to collect on the day. `0` for a package session or a balance collected outside Recurrente. |
| `checkout_url`            | Where the payer pays what the policy asked for, or leaves their card.                                    |
| `balance_checkout_url`    | The balance's payment link, once you ask for it.                                                         |
| `no_show_fee_checkout_id` | The no-show fee's checkout, charged or declined.                                                         |

## Change a booking

Every change answers with the booking as it is afterwards, and every one of them is idempotent with an `Idempotency-Key`.

| Operation                                    | Endpoint                                                 | Notes                                                                                                                                                      |
| -------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Move it                                      | `POST /bookings/{booking_id}/reschedule`                 | Same staff member. The answer is the new booking; `rescheduled_from_id` points back and the old one is cancelled by `system`. What was paid moves with it. |
| Where can it move?                           | `GET /availability?booking_id=…`                         | The booking's own time counts as free.                                                                                                                     |
| Cancel it                                    | `POST /bookings/{booking_id}/cancellation`               | The business can always cancel. Refunds nothing.                                                                                                           |
| Cancel and refund, or refund a cancelled one | `POST /bookings/{booking_id}/refund`                     | Gives back what the booking collected through the payments it came in on. Moves money.                                                                     |
| Accept or decline                            | `POST /bookings/{booking_id}/acceptance`, `/declination` | For businesses that review each booking. Declining refunds a prepaid booking.                                                                              |
| Mark completed                               | `POST /bookings/{booking_id}/completion`                 | Once its time has come.                                                                                                                                    |
| Mark no-show                                 | `POST /bookings/{booking_id}/no_show`                    | Charges nothing by itself.                                                                                                                                 |
| Charge the no-show fee                       | `POST /bookings/{booking_id}/no_show_fee`                | On the card the payer left. Never automatic. A decline answers `code: declined`. Moves money.                                                              |
| Collect the balance                          | `POST /bookings/{booking_id}/balance_charge`             | Creates (or reuses) the balance's payment link. Charges nobody.                                                                                            |
| Balance paid outside                         | `POST /bookings/{booking_id}/outside_payment`            | The business collected it in cash or on its own terminal. Moves money.                                                                                     |
| Note on the customer                         | `POST /bookings/{booking_id}/customer_notes`             | Shown on the customer's next bookings too.                                                                                                                 |

Operations marked *moves money* need an API key with money movement enabled; otherwise they answer `403` with `code: money_movement_not_enabled`.

### Find a booking by the customer

`GET /bookings` filters by `status`, a `from`/`to` range on the start time, `staff_member_id`, `customer_id`, `class_session_id`, and `q`, words from the customer's name, email or phone:

```bash
curl "https://app.recurrente.com/api/bookings?q=Ana&from=2026-10-06T00:00:00-06:00&to=2026-10-07T00:00:00-06:00" \
  -H "X-SECRET-KEY: sk_live_..."
```

### Repeating bookings

`POST /booking_series` books the same customer, service and staff member every `interval` `frequency` (`day`, `week`, `month`, `year`, or `back_to_back` the same day), `occurrences` times or until `ends_on`. A time that is taken or closed is skipped and listed in `skipped_starts_at` instead of failing the series. Each visit is collected on the day.

## Set up services and your team

A service is a product with a `booking` object. Send it when you create or update the product (`POST /products`, `PATCH /products/{id}`) with the same settings the dashboard's form has: `kind`, `duration_minutes`, `buffer_after_minutes`, `payment_policy` (and the deposit or no-show fee in cents), `staff_member_ids`, `resource_ids`, add-ons and, for rooms, nightly rates. `GET /services` lists every bookable product with the `service_id` the other endpoints take.

| What                                 | Endpoints                                                                                                                   |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| Staff members                        | `GET/POST /staff_members`, `GET/PATCH/DELETE /staff_members/{id}` (`position` reorders them)                                |
| A staff member's week and time off   | `GET/PUT /staff_members/{staff_member_id}/schedule`, `GET/POST/DELETE …/availability_exceptions`                            |
| Resources (chairs, rooms, equipment) | `GET/POST /resources`, `GET/PATCH/DELETE /resources/{id}`, and a room's external calendars at `…/ical_feeds`                |
| Business hours and special dates     | `GET/PUT /business_hours`, `GET/POST/DELETE /business_hours/availability_exceptions` (`closed`, or `hours` for a short day) |

Everything you change here is read back by `GET /availability` at once. Connecting a staff member's Google Calendar is an OAuth consent and stays in the dashboard.

## Classes

A class is a service with `kind: class` and a `capacity`. `POST /class_sessions` writes its sessions from a weekly rule ("Tuesdays and Thursdays at 5:45 pm, from October 6 to December 15"): `service_id`, `staff_member_id`, `weekdays` (0 is Sunday), `time` and the date range. It is all or nothing: if the instructor or the room is already busy at one of those times, nothing is written (`busy`).

A seat is a booking: `POST /bookings` with `class_session_id`. When the class is full the answer is `422` with `code: full`. `GET /class_sessions` and `GET /class_sessions/{id}` show the seats taken and left.

`POST /class_sessions/{class_session_id}/cancellation` cancels a session: every seat is cancelled as the business and refunded (a package session goes back to its package), and each customer is told. It moves money.

## Waitlist

`POST /waitlist_entries` puts a customer on a service's waitlist for up to five days, each whole or in part (`morning`, `afternoon`, `evening`), like the public page's "Let me know". When a booking that fits is cancelled or moved, they get a WhatsApp with the link to book it. `GET /waitlist_entries` lists who is waiting; `DELETE /waitlist_entries/{id}` takes someone off.

## Lodging

A room type is a service with `kind: lodging`; each physical room is a resource. Stays are bookings too, with a `stay` object (room type, room, check-in and check-out dates, nights, guests, the open tab and the guest register).

1. `GET /lodging/availability?check_in_on=…&check_out_on=…&guests=2` answers each room type with its free rooms and the stay's price night by night, as the public page prices it (seasons, long-stay discount, extra guests, lodging tax).
2. `POST /stays` books it with `room_type_id`, the dates and the guest. The payment policy works as for appointments.
3. On the day: `POST /stays/{stay_id}/check_in` (opens a tab for extras), `…/guest_registration` (the guest register), `…/date_change`, `…/unit_assignment`, and `POST /stays/{stay_id}/check_out`. Checking out closes the tab by `channel`: `payment_link` (default), `pos`, or `saved_card`, which charges the card and moves money.

The booking operations above (balance link, paid outside, no-show, no-show fee, cancel, refund) work on stays too. `GET /bookings?kind=stay` lists them, `POST/DELETE /maintenance_blocks` takes a room out of sale for some nights, and `GET /lodging/occupancy` gives the month's occupancy and revenue.

## Gift cards

| Operation      | Endpoint                                     | Notes                                                                                                                           |
| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Sell or top up | `POST /gift_cards`                           | Opens the checkout that sells a card (or tops up `gift_card_id`). Charges nobody by itself: the card is issued when it is paid. |
| List, read     | `GET /gift_cards`, `GET /gift_cards/{id}`    | With the balance and every movement. `q` searches by code, or by the recipient's name or email.                                 |
| Redeem         | `POST /gift_cards/{gift_card_id}/redemption` | Spends balance for a sale collected outside a Recurrente checkout. Moves money.                                                 |
| Adjust         | `POST /gift_cards/{gift_card_id}/adjustment` | `credit` or `debit`, with a note. Moves money.                                                                                  |
| Void           | `POST /gift_cards/{gift_card_id}/void`       | The card stops working and loses its balance. Moves money.                                                                      |
| Settings       | `GET/PATCH /gift_card_settings`              | Whether the business sells gift cards, and how many months a card lasts.                                                        |

The code is a bearer secret: whoever holds it spends it. Keep it out of logs. Payers redeem codes on their own checkout; that stays in the checkout.

## Contracts

| Operation                  | Endpoint                                                                          | Notes                                                                                                                                                                       |
| -------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Templates                  | `GET/POST /contract_templates`, `GET/PATCH/DELETE /contract_templates/{id}`       | `body` is HTML with variables (`{nombre}`, `{servicio}`, `{fecha}`…); `fields` are what the customer fills in; `service_ids` sends it with every booking of those services. |
| AI draft                   | `POST /contract_template_drafts`                                                  | Describe what the customer must accept; the answer is an unsaved template to review. 20 per hour.                                                                           |
| Send                       | `POST /contracts`                                                                 | By WhatsApp and email, with a reminder at 48 hours. `charge_amount_in_cents` makes them sign before they pay.                                                               |
| Send a booking's contracts | `POST /bookings/{booking_id}/contract_delivery`                                   | The ones its services ask for and it has not sent yet.                                                                                                                      |
| Follow                     | `GET /contracts`, `GET /contracts/{id}`, `POST /contracts/{contract_id}/reminder` | `signing_url` is the page the customer signs on.                                                                                                                            |
| Signed PDF                 | `GET /contracts/{contract_id}/pdf`                                                | `409 pdf_not_ready` until it is signed.                                                                                                                                     |

Signing itself always happens on the customer's own page.

## Settings, packages and notes

| What                                                                        | Endpoints                                                                                                                                            |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Switch a line on or off (store, citas, lodging, gift\_cards, contracts)     | `GET /sales_channels`, `PUT /sales_channels/{key}` with `{"on": false}`                                                                              |
| Policies (notice, window, cancellations, deposits, no-show fee, acceptance) | `GET/PATCH /booking_settings/policy`                                                                                                                 |
| Which messages go out                                                       | `GET/PATCH /booking_settings/notifications`                                                                                                          |
| Lodging settings (rooms sold by the night, lodging tax)                     | `GET/PATCH /booking_settings/lodging`                                                                                                                |
| The public booking links to share                                           | `GET /booking_settings/share`                                                                                                                        |
| Packages of sessions sold ahead                                             | `GET/POST /booking_packages`, `DELETE /booking_packages/{id}`, and a customer's remaining sessions at `GET /customers/{customer_id}/package_credits` |
| Notes on a customer                                                         | `GET/POST /customers/{customer_id}/notes`, `DELETE …/notes/{id}`                                                                                     |

A line switched off takes nothing new, through the API either: with Citas off, availability by service and every new booking, series or waitlist entry answer `403` with `code: bookings_off`; with lodging off, a new stay answers `code: lodging_off`. What was already booked keeps working: moving, cancelling, collecting and the front desk.

A `PATCH` changes only the fields you send, with the dashboard's validations; a refusal answers `422` with `code: invalid_booking_settings` and the dashboard's own messages.

## Webhooks

Every event carries the object as it was when the event happened.

| Event                                                                                                              | When                                                                       |
| ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| `booking.create`                                                                                                   | A booking or a class seat was made, from any channel.                      |
| `booking.confirm`                                                                                                  | The booking is the payer's: booked without payment, paid, or accepted.     |
| `booking.reschedule`                                                                                               | Emitted on the new booking when one is moved.                              |
| `booking.cancel`                                                                                                   | Cancelled; `cancelled_by` says by whom.                                    |
| `booking.refund`                                                                                                   | What the booking collected was given back.                                 |
| `booking.no_show`                                                                                                  | Marked as a no-show.                                                       |
| `booking.complete`                                                                                                 | The visit happened.                                                        |
| `booking.check_in` / `booking.check_out`                                                                           | A stay's guest arrived / left.                                             |
| `gift_card.issue`, `.reload`, `.redeem`, `.release`, `.adjust`, `.refund_back`, `.refund_void`, `.expire`, `.void` | One per movement of a card's balance, with the card and the `transaction`. |
| `contract.send`, `.view`, `.sign`, `.decline`, `.expire`                                                           | A contract changed status.                                                 |

## Sandbox

Bookings are native in a Sandbox: the same agenda, payment links paid with the test card, and events delivered only to the Sandbox's webhook endpoints. The no-show fee, refunds, cancelling a class and checking out run against the Sandbox's test payments and never reach a real processor. No WhatsApp or email reaches a real person from a Sandbox: a contract sent from one is shared by its `signing_url`. A room's external calendar is saved but never fetched.

## Agents: CLI and MCP

Every operation above is in the agent catalog, so the `recurrente` CLI and the MCP server can run it without custom code. "Reschedule Ana's 3 pm to Thursday" is three calls:

```bash
recurrente bookings list --q Ana
recurrente bookings availability --booking-id bk_ab12cd34 --from 2026-10-08 --to 2026-10-09
recurrente bookings reschedule --booking-id bk_ab12cd34 --starts-at 2026-10-08T15:00:00-06:00
```

Operations that move money need a human's confirmation: `--yes` in the CLI, the `money` permission on an MCP connection.