R
Reservory Docs
Getting StartedAPI ReferenceWebhooksGraphQLSDKs

API Reference

OpenAPI 3.1
← Home

Reservory REST API

Public API surface. All responses are JSON. CORS-enabled where indicated (anon endpoints). Mutating POSTs accept an Idempotency-Key header for safe client retries.

Machine-readable spec: /api/openapi.json (OpenAPI 3.1, covers the public booking + payment surface). Import into Postman, Stoplight, or any SDK generator.

Auth at a glance

  • anon — no auth; CORS-open; rate-limited where relevant
  • signed token — HMAC token issued at booking-create or waiver-dispatch; passed as a header (X-Booking-Token) or URL segment
  • operator staff+/manager+/admin+ — Supabase JWT bearer; requireOperator enforces the role floor server-side

Booking

GET/api/widget/experience?tenant=&experience=anon

Widget bootstrap. Returns experience metadata + first 40 available slots (capacity-aware).

200 — { experience, slots: [{ id, starts_at, ends_at, seats_remaining }] }
404 — tenant_not_found | experience_not_found
POST/api/bookings/holdanon

Acquire a 10-minute soft hold on N seats. Rate-limited 10/min/IP.

Body
{ slot_id, experience_id, seats }
200 — { hold_id, slot_id, expires_at, seats_remaining }
409 — hold_unavailable (reason: insufficient_capacity | already_held | ...)

Supports Idempotency-Key header. CORS-enabled.

POST/api/bookingsanon

Convert a hold into a booking. Returns a signed booking_token for the customer payment-intent route.

Body
{ hold_id, slot_id, experience_id, venue_id, customer:{email,first_name,last_name?,phone?}, guest_count, notes? }
201 — { booking_id, booking_token, waiver_url }
410 — hold_expired
409 — capacity_exceeded | slot_already_booked

Supports Idempotency-Key. Idempotent: same key + body returns cached response.

POST/api/bookings/[id]/canceloperator manager+

Cancel a held / payment-pending / confirmed booking. Does NOT refund.

200 — { ok: true, id }
POST/api/bookings/[id]/check-inoperator staff+

Stamp checked_in_at + checked_in_by_user_id. Idempotent.

200 — { checked_in_at } or { already_checked_in: true, checked_in_at }

Payments

POST/api/embed/bookings/[id]/payment-intentsigned token

Customer-facing PI creation. Requires X-Booking-Token (HMAC issued at booking-create).

200 — { clientSecret, paymentIntentId, publishableKey }
401 — invalid_booking_token
503 — stripe_not_configured | connected_account_not_ready
POST/api/payments/bookings/[bookingId]/payment-intentoperator manager+

Operator-side PI creation (in-person collection).

200 — { paymentIntentId, clientSecret }
POST/api/payments/refundsoperator manager+

Refund (full or partial) a previously confirmed payment.

Body
{ paymentIntentId, amountCents?, reason?: duplicate|fraudulent|requested_by_customer }
200 — { refundId, status }
POST/api/payments/connect/onboarding-linkoperator admin+

Start or resume Stripe Connect Standard onboarding. Returns a hosted URL.

200 — { accountId, onboardingUrl }

Operator CRUD

POST/api/venuesoperator manager+

Create a venue.

Body
{ name, slug, timezone?, currency? }
PATCH/api/venues/[id]operator manager+

Update venue name/status/timezone.

POST/api/experiencesoperator manager+

Create an experience.

PATCH/api/experiences/[id]operator manager+

Update experience.

POST/api/slotsoperator manager+

Create a slot. Capacity defaults to experience.default_capacity.

Body
{ experience_id, starts_at, ends_at, capacity? }
DELETE/api/slots/[id]operator manager+

Delete a slot (only if unbooked).

Waivers

GET/api/waivers/[token]signed token

Return waiver + booking context. Token is HMAC, 14d TTL.

POST/api/waivers/[token]/signsigned token

Persist signature (data URL) + signer name. Idempotent on re-sign.

Body
{ signer_name, signature_data_url }

Team

POST/api/team/inviteoperator admin+

Invite a teammate by email + role.

Body
{ email, role: admin|manager|staff, first_name? }
PATCH/api/team/members/[id]operator admin+

Change role.

DELETE/api/team/members/[id]operator admin+

Revoke membership. Refuses last-owner removal and self-removal.

Webhooks (outbound)

POST/api/webhooks/endpointsoperator admin+

Create a subscription. Signing secret returned once.

Body
{ url, events:[booking.created, booking.confirmed, booking.cancelled, refund.created, waiver.signed] }
DELETE/api/webhooks/endpoints/[id]operator admin+

Delete a subscription. Pending deliveries dropped.

GDPR + import + alerts + data

GET/api/data-exportoperator admin+

Download a JSON bundle of all tenant data.

POST/api/deletion-requestsoperator admin+

Queue a customer for anonymisation. 30-day cooling-off.

Body
{ customer_email, reason? }
POST/api/deletion-requests/[id]/canceloperator admin+

Cancel a pending deletion.

POST/api/import/customers/previewoperator admin+

Parse a CSV, auto-detect columns, return preview.

Body
{ csv }
POST/api/import/customers/commitoperator admin+

Upsert previewed rows.

Body
{ rows: [...] }
POST/api/alerts/[id]/resolveoperator admin+

Mark a capacity alert resolved.

Body
{ notes? }
GET/api/tenants/waiver-textoperator manager+

Get current per-tenant waiver override.

PUT/api/tenants/waiver-textoperator admin+

Set or reset (empty string) waiver override.

Body
{ waiver_text }

Webhook signatures

Outbound webhook deliveries include X-Reservory-Timestamp and X-Reservory-Signature: v1,<hex>. The signature is HMAC-SHA256 of `${timestamp}.${rawBody}` with your endpoint's signing secret. Reject deliveries older than 5 minutes to mitigate replay attacks.