Skip to main content
Admin-specific booking operations: status changes, check-in/out, and document generation. For customer booking management, see Customer Booking Lifecycle. For creating bookings, see Admin Booking Creation. For changing a confirmed booking’s resource, dates, fields, add-ons, or sub-bookings, see Admin Booking Edit. For general conventions, see JSON-API Conventions.

Prerequisites

  • A valid Bearer token with admin access — see Authentication
  • Organization context (?o={organization_id})

Status Changes

Request path documentation:
  • paths/admin/bookings.yaml for PATCH /api/v1/bookings/{booking_id}
  • paths/admin/bookings.yaml for GET /api/v1/bookings/{booking_id}/cancel

Accept a Requested Booking

When a booking has status requested, accept it by updating the booking status:
Example payload:

Reject a Requested Booking

Reject a requested booking with the same update endpoint:
Example payload:

Cancel a Booking

Cancel an accepted or requested booking with the dedicated cancel action:
This runs the booking cancellation flow. Refunds and invoice adjustments follow the configured cancellation policy.

Change resource or service

Calendar drag-and-drop and other in-place resource moves use the same JSON:API update:
The current service must already be offered on the target resource (or on its parent, when you move a child resource). If it is not, the request fails with HTTP 400 and service_and_resource_not_related. The API does not silently switch the booking to another service on the destination. To keep the service when you move the booking, attach that service to the target resource first, or send both relationships.resource and relationships.service in the same PATCH so the pair is valid together. Blockers and sequence bookings skip this check. To change resource, dates, add-ons, or sub-bookings on a confirmed booking as a dedicated admin action, use Admin Booking Edit.

Check-In / Check-Out

Request path documentation:
  • paths/admin/bookings.yaml for POST /api/v1/bookings/{booking_id}/check-in
  • paths/admin/bookings.yaml for POST /api/v1/bookings/{booking_id}/check-out
  • Scan for POST /api/v1/scans (staff / kiosk scanner)
Record customer arrivals and departures.

Web admin Presence section

The current web admin booking-details Presence card (app-admin/pages/bookings/[bookingId]/index.vue) uses these JSON:API fields and endpoints. Use the same fields to decide whether to show a check-in/out section.

Fields that gate the section

Include resource on GET /api/v1/bookings/{booking_id}. Show the section when all of these are true: is_editable is false when any of these apply: external_calendar is set, status is reserved, is_sequence is true, is_via_protected_channel is true, or the booking meta IS_READ_ONLY is set. The org feature Check-In & Check-Out is not a booking attribute. The UI still keys off settings.checkIn.isEnabled on the included resource. Buttons (Check-In / Check-Out) are not booking fields. Show both when the user can create scans (user.can('create', 'scans')). They are not disabled by start/end time. The overflow-menu actions additionally require data.attributes.status === "accepted".

Fields to render presence

check_in_date is the newest check-in scan with status valid or unpaid. check_out_date is the newest check-out after that time, otherwise null. Recalculated on scan save and delete (Booking::updatePresenceDates()).

Endpoints the Presence card uses

Named routes: api:v1:bookings.check-in, api:v1:bookings.check-out. History is the JSON:API related scans on bookings. Standalone GET /api/v1/scans is not registered (scans is except('index')).
The current admin Booking.checkIn() does not send X-Supports-Dynamic-Form. If delayed check-in documents or custom fields are still missing, POST /check-in returns HTTP 400 scans.check_in_requires_details_unsupported instead of requires_details. Shop checkInWithToken does send the header. See X-Supports-Dynamic-Form.
Related endpoints not used by the Presence buttons: Guest self check-in uses the same POST .../check-in with access_token (and optional lat / lng). That path enforces selfCheckIn.disabledUntil / allowedUntil.

Check-In

Optional body fields:

Check-Out

Both endpoints:
  • Log a Scan record with the timestamp
  • Update the booking’s check-in/check-out status
  • Can be triggered by admin users or via self-service terminals
Resources can be configured with auto-expiry — if a customer doesn’t check in within a configured window, the booking expires automatically.

X-Supports-Dynamic-Form

Clients that can render the delayed check-in form (legal documents / custom fields with collection timing check_in) must send:
The API reads the header with str_to_bool (FILTER_VALIDATE_BOOLEAN: true, 1, yes, on). CORS allow-lists it (config/cors.php). Constant: CheckInFormService::DYNAMIC_FORM_HEADER. Check-out (POST .../check-out) does not use this header. Delayed collection applies to check-in only.

Booking action (POST /api/v1/bookings/{id}/check-in)

When Booking::requiresCheckInDetails() is true and the request has no top-level form_data: Then GET {form_url} and retry POST /check-in with top-level form_data. On success, meta.status is the scan status (for example valid) and check_in_date is set.

Scanner create (POST /api/v1/scans)

When details are required and the request has no form payload: Submit answers on a later POST /scans as either:
  1. Top-level form_data (same shape as the booking action), or
  2. data.attributes.meta.form_data.
ScanAdapter::extractCheckInFormData accepts both.

Delayed forms at check-in

Legal documents and custom forms can be configured with collection timing check_in. If those details are still missing when you check a booking in, the API does not complete the check-in until the client collects and submits them. Advertise support for the dynamic check-in form with:
Without that header (and without form_data), check-in fails with HTTP 400 and code scans.check_in_requires_details_unsupported. 1. First attempt — details still required:
2. Load the form from meta.form_url (GET /api/ui/booking-check-in-form). It returns the same dynamic-form shape as checkout (components, validations), limited to still-missing items (documents.{uuid}, service_custom_fields.{uuid}). 3. Re-submit check-in with the collected values:
On success, meta.status is the resulting scan status (for example valid) and check_in_date is set. Invalid form_data returns HTTP 422. Timing / geo / business-rule failures return HTTP 400 with a scans.* error code.

Scanner endpoint (POST /scans)

Staff and kiosk clients that create scans by booking code use the same delayed-form flow. Submit details as either top-level form_data or data.attributes.meta.form_data. See X-Supports-Dynamic-Form.
See Scan for the resource schema and create parameters.

Generating Documents

Generate a PDF or ticket from a document template:
Returns a files resource with the generated document.
Booking exports are documented with the other admin export endpoints in Exports.

Common Errors