Booking API
Reservations and Public Booking
Create reservations, expose public booking, and track payment-required flows
This page documents the current enterprise reservation surface: internal reservations scoped to the active organisation, organisation-based public booking, legacy token booking, and Stripe webhook handling for payment confirmation.
JWT or user API keyPublic booking is unauthenticatedActive organisationStripe webhooks
Source
- Reservations controller:
backend-nestjs/src/reservations/reservations.controller.ts - Public booking controller:
backend-nestjs/src/resources/public-booking.controller.ts - Stripe webhook controller:
backend-nestjs/src/payments/stripe-webhook.controller.ts - DTOs:
backend-nestjs/src/dto/reservation.dto.ts,backend-nestjs/src/dto/public-booking.dto.ts,backend-nestjs/src/reservations/dto/list-reservations.query.dto.ts
Authentication and Permissions
- Internal reservation CRUD requires
JwtAuthGuardplusReservationAccessGuard. - Public booking routes are unauthenticated.
- Reservation, resource, and public-booking availability flows all reuse the same organisation-scoped availability logic.
- Payment-required public reservations are confirmed from Stripe webhook events, not from the initial browser request.
Endpoint Reference
Internal Reservations
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
POST | /api/reservations | Create a reservation in the active organisation. | Body: reservation fields | JWT or user API key | reservations/reservations.controller.ts |
GET | /api/reservations | List reservations in the active organisation. | Query: filter fields | JWT or user API key | reservations/reservations.controller.ts |
GET | /api/reservations/:id | Get one reservation. | Path: id | JWT or user API key | reservations/reservations.controller.ts |
PATCH | /api/reservations/:id | Update one reservation. | Path: id, body: partial reservation fields | JWT or user API key | reservations/reservations.controller.ts |
DELETE | /api/reservations/:id | Delete one reservation. | Path: id | JWT or user API key | reservations/reservations.controller.ts |
Public Booking
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
GET | /api/public/booking/organisations/:slug | Resolve the branded public booking catalog for one organisation. | Path: slug | Public | resources/public-booking.controller.ts |
GET | /api/public/booking/organisations/:slug/availability | Read public availability for one service on one day. | Path: slug, query: date,resourceTypeId,resourceId? | Public | resources/public-booking.controller.ts |
POST | /api/public/booking/organisations/:slug/reserve | Create a public reservation from the organisation booking page. | Path: slug, body: booking fields | Public | resources/public-booking.controller.ts |
GET | /api/public/booking/organisations/:slug/checkout-status | Poll the Stripe-backed payment state for a public reservation. | Path: slug, query: sessionId | Public | resources/public-booking.controller.ts |
GET | /api/public/booking/:token | Resolve public booking metadata for a legacy resource token. | Path: token | Public | resources/public-booking.controller.ts |
GET | /api/public/booking/:token/availability | Read available slots for a day for one legacy published resource. | Path: token, query: date | Public | resources/public-booking.controller.ts |
POST | /api/public/booking/:token/reserve | Create a public reservation from a legacy resource token. | Path: token, body: booking fields | Public | resources/public-booking.controller.ts |
GET | /api/public/booking/:token/checkout-status | Poll the Stripe-backed payment state for a legacy public reservation. | Path: token, query: sessionId | Public | resources/public-booking.controller.ts |
Stripe Webhook
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
POST | /api/payments/stripe/webhook | Handle payment success, failure, and checkout-expiration events. | Header: Stripe-Signature, raw body | Stripe signature | payments/stripe-webhook.controller.ts |
Request Shapes
Internal reservations
CreateReservationDto and UpdateReservationDto
startTime: required on create, ISO date-timeendTime: required on create, ISO date-time, must be afterstartTimequantity: optional int, minimum1customerInfo: optional objectnotes: optional sanitized string, max 2048 charsresourceTypeId: optional positive int, required for the enterprise type flowresourceId: optional positive int, used as a compatibility bridge or single-resource writeresourceIds: optional unique positive integer array for multi-resource same-type reservationsstatus: update-only enumpending|confirmed|completed|cancelled|waitlist
Query:
resourceId: optional int>= 1resourceTypeId: optional int>= 1status: optional reservation statusstartFrom: optional ISO date-timeendTo: optional ISO date-time
Public booking
CreateOrganisationPublicBookingDto
resourceTypeId: required positive intresourceId: optional positive int for specific-resource bookingstartTime: required ISO date-timeendTime: required ISO date-timequantity: required int, minimum1customerName: required stringcustomerEmail: required emailcustomerPhone: required stringnotes: optional string
PublicOrganisationAvailabilityQueryDto
date: required ISO date stringresourceTypeId: required positive intresourceId: optional positive int
CreatePublicBookingDto for the legacy token route
startTime: required ISO date-timeendTime: required ISO date-timequantity: required int, minimum1customerName: required stringcustomerEmail: required emailcustomerPhone: required stringnotes: optional string
Example Calls
Create a reservation
curl -X POST "$PRIMECAL_API/api/reservations" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"startTime": "2026-04-01T08:00:00.000Z",
"endTime": "2026-04-01T09:00:00.000Z",
"resourceTypeId": 8,
"resourceIds": [21, 22],
"quantity": 2
}'
Read organisation public availability
curl "$PRIMECAL_API/api/public/booking/organisations/acme-spa/availability?date=2026-05-20&resourceTypeId=8"
Create a public booking
curl -X POST "$PRIMECAL_API/api/public/booking/organisations/acme-spa/reserve" \
-H "Content-Type: application/json" \
-d '{
"resourceTypeId": 8,
"startTime": "2026-04-01T08:00:00.000Z",
"endTime": "2026-04-01T09:00:00.000Z",
"quantity": 1,
"customerName": "May B. Late",
"customerEmail": "may@example.com",
"customerPhone": "+36301112222"
}'
Example response for a payment-required booking:
{
"reservationId": 901,
"status": "pending_payment",
"paymentStatus": "pending",
"requiresPayment": true,
"quotedAmount": 2500,
"quotedCurrency": "eur",
"checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_123",
"stripeCheckoutSessionId": "cs_test_123",
"paymentData": {
"provider": "stripe",
"mode": "checkout",
"checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_123",
"stripeCheckoutSessionId": "cs_test_123",
"amount": 2500,
"currency": "eur",
"status": "pending",
"statusUrl": "/api/public/booking/organisations/acme-spa/checkout-status?sessionId=cs_test_123"
}
}
Read public checkout status
curl "$PRIMECAL_API/api/public/booking/organisations/acme-spa/checkout-status?sessionId=cs_test_123"
Example response:
{
"reservationId": 901,
"status": "confirmed",
"paymentStatus": "succeeded",
"requiresPayment": true,
"canRetryPayment": false
}
Response and Behavior Notes
- Internal reservations are scoped to the active organisation.
- Reservation quote fields are stored canonically on the reservation:
quotedUnitAmount,quotedTotalAmount,quotedCurrency, andpaymentRequiredSnapshot. - Multi-resource reservations can assign multiple concrete resources of the same resource type.
- When payment is required, public booking creates a
pending_paymentreservation, returnspaymentData, and waits for Stripe webhook confirmation before moving it toconfirmed. paymentDatacurrently uses Stripe Checkout with:provider: "stripe"mode: "checkout"checkoutUrlstripeCheckoutSessionId- quote snapshot fields such as amount and currency
- Public catalog payloads expose whether the organisation can currently accept Stripe-backed payments so the client can disable payment-required booking types before reservation creation.
- The public booking client can keep a reservation in a retryable
pending_paymentstate and pollcheckout-statusafter the Stripe return. - PrimeCal does not store raw card data.
Best Practices
- Set the active organisation before listing reservations in multi-org clients.
- Validate date ordering client-side before submitting reservation writes.
- Treat public booking tokens as secrets. Regenerate them when links leak or staff changes occur.
- Add rate limiting or anti-bot protection in front of public booking forms.
- Use Stripe webhooks as the source of truth for payment-required booking confirmation.