Skip to navigation

Bookings (Citas)

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.

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

Concepts

ObjectPrefixWhat it is
Servicesvc_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 memberstf_A person bookings are made with, with their own weekly hours and time off.
Resourceres_What a booking occupies besides a person: a chair, a room, equipment, a hotel room.
Bookingbk_A payer’s hold on a time: one appointment, one seat in a class, or one stay.
Booking seriesser_A booking the business repeats (“every 2 weeks, 10 times”).
Class sessioncls_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.

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_..."
[
{ "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.

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:

PolicyStatus after bookingWhat the payer does
noneconfirmed (or pending if the business accepts each booking by hand)Nothing.
card_on_filepending with checkout_urlLeaves a card for the no-show fee; the booking confirms when they do.
depositpending with checkout_urlPays the deposit; the booking confirms when they pay.
fullpending with checkout_urlPays 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

FieldMeaning
total_in_centsWhat the booking costs, frozen when it was made.
paid_in_centsWhat the payer already paid (deposit, prepayment, protection, balance).
balance_in_centsWhat is left to collect on the day. 0 for a package session or a balance collected outside Recurrente.
checkout_urlWhere the payer pays what the policy asked for, or leaves their card.
balance_checkout_urlThe balance’s payment link, once you ask for it.
no_show_fee_checkout_idThe 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.

OperationEndpointNotes
Move itPOST /bookings/{booking_id}/rescheduleSame 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 itPOST /bookings/{booking_id}/cancellationThe business can always cancel. Refunds nothing.
Cancel and refund, or refund a cancelled onePOST /bookings/{booking_id}/refundGives back what the booking collected through the payments it came in on. Moves money.
Accept or declinePOST /bookings/{booking_id}/acceptance, /declinationFor businesses that review each booking. Declining refunds a prepaid booking.
Mark completedPOST /bookings/{booking_id}/completionOnce its time has come.
Mark no-showPOST /bookings/{booking_id}/no_showCharges nothing by itself.
Charge the no-show feePOST /bookings/{booking_id}/no_show_feeOn the card the payer left. Never automatic. A decline answers code: declined. Moves money.
Collect the balancePOST /bookings/{booking_id}/balance_chargeCreates (or reuses) the balance’s payment link. Charges nobody.
Balance paid outsidePOST /bookings/{booking_id}/outside_paymentThe business collected it in cash or on its own terminal. Moves money.
Note on the customerPOST /bookings/{booking_id}/customer_notesShown 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:

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.

WhatEndpoints
Staff membersGET/POST /staff_members, GET/PATCH/DELETE /staff_members/{id} (position reorders them)
A staff member’s week and time offGET/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 datesGET/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

OperationEndpointNotes
Sell or top upPOST /gift_cardsOpens 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, readGET /gift_cards, GET /gift_cards/{id}With the balance and every movement. q searches by code, or by the recipient’s name or email.
RedeemPOST /gift_cards/{gift_card_id}/redemptionSpends balance for a sale collected outside a Recurrente checkout. Moves money.
AdjustPOST /gift_cards/{gift_card_id}/adjustmentcredit or debit, with a note. Moves money.
VoidPOST /gift_cards/{gift_card_id}/voidThe card stops working and loses its balance. Moves money.
SettingsGET/PATCH /gift_card_settingsWhether 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

OperationEndpointNotes
TemplatesGET/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 draftPOST /contract_template_draftsDescribe what the customer must accept; the answer is an unsaved template to review. 20 per hour.
SendPOST /contractsBy WhatsApp and email, with a reminder at 48 hours. charge_amount_in_cents makes them sign before they pay.
Send a booking’s contractsPOST /bookings/{booking_id}/contract_deliveryThe ones its services ask for and it has not sent yet.
FollowGET /contracts, GET /contracts/{id}, POST /contracts/{contract_id}/remindersigning_url is the page the customer signs on.
Signed PDFGET /contracts/{contract_id}/pdf409 pdf_not_ready until it is signed.

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

Settings, packages and notes

WhatEndpoints
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 outGET/PATCH /booking_settings/notifications
Lodging settings (rooms sold by the night, lodging tax)GET/PATCH /booking_settings/lodging
The public booking links to shareGET /booking_settings/share
Packages of sessions sold aheadGET/POST /booking_packages, DELETE /booking_packages/{id}, and a customer’s remaining sessions at GET /customers/{customer_id}/package_credits
Notes on a customerGET/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.

EventWhen
booking.createA booking or a class seat was made, from any channel.
booking.confirmThe booking is the payer’s: booked without payment, paid, or accepted.
booking.rescheduleEmitted on the new booking when one is moved.
booking.cancelCancelled; cancelled_by says by whom.
booking.refundWhat the booking collected was given back.
booking.no_showMarked as a no-show.
booking.completeThe visit happened.
booking.check_in / booking.check_outA stay’s guest arrived / left.
gift_card.issue, .reload, .redeem, .release, .adjust, .refund_back, .refund_void, .expire, .voidOne per movement of a card’s balance, with the card and the transaction.
contract.send, .view, .sign, .decline, .expireA 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:

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.