Resource Add-ons API
The Resource Add-ons API allows you to manage time-based bookable resources such as party rooms, VIP lounges, and equipment rentals that can be added to bookings.
Overview
Resource add-ons differ from regular add-ons in that they:
- Have specific time windows (before, during, or after the main activity)
- May have limited capacity and availability
- Support various duration and pricing modes
- Are tracked as resource allocations in the system
Get Resource Addon Configurations
Retrieve available resource addon configurations for a product.
GET /api/v1/availability/resource-addons
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
product_id | string | Yes | Product identifier |
Example Request
GET /api/v1/availability/resource-addons?product_id=p_laser_tag
x-tenant-id: t_demo
Response
Status: 200 OK
{
"configs": [
{
"id": "rac_party_room",
"productId": "p_laser_tag",
"addonId": "addon_party_room",
"addonName": "Party Room",
"addonDescription": "Private party room for your group celebration",
"resourceId": "res_party_room_1",
"resourceName": "Party Room A",
"durationMode": "SELECTABLE",
"durationOptions": [30, 60, 90],
"minDurationMins": 30,
"maxDurationMins": 90,
"defaultDurationMins": 60,
"pricingMode": "PER_HOUR",
"flatPrice": null,
"hourlyRate": 50.0,
"perPersonPrice": null,
"basePrice": null,
"allowBefore": true,
"allowAfter": true,
"allowDuring": false,
"bufferBeforeMins": 15,
"bufferAfterMins": 15,
"maxCapacity": 20,
"isActive": true
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique configuration identifier |
productId | string | Associated product |
addonId | string | Associated addon |
addonName | string | Display name of the addon |
addonDescription | string | Description of the addon |
resourceId | string | Associated resource |
resourceName | string | Display name of the resource |
durationMode | string | One of: FIXED, SELECTABLE, CUSTOM |
durationOptions | number[] | Available durations for SELECTABLE mode (minutes) |
minDurationMins | number | Minimum duration for CUSTOM mode |
maxDurationMins | number | Maximum duration for CUSTOM mode |
defaultDurationMins | number | Default duration |
pricingMode | string | One of: FLAT, PER_HOUR, PER_PERSON, COMPOSITE |
flatPrice | number | Price for FLAT mode |
hourlyRate | number | Hourly rate for PER_HOUR mode |
perPersonPrice | number | Per-person price for PER_PERSON and COMPOSITE modes |
basePrice | number | Base fee for COMPOSITE mode |
allowBefore | boolean | Can be booked before the activity |
allowAfter | boolean | Can be booked after the activity |
allowDuring | boolean | Can be booked during the activity |
bufferBeforeMins | number | Buffer time before activity (minutes) |
bufferAfterMins | number | Buffer time after activity (minutes) |
maxCapacity | number | Maximum capacity for the resource |
isActive | boolean | Whether the configuration is active |
Get Available Time Windows
Retrieve available time windows for a resource addon configuration.
GET /api/v1/availability/resource-addons/:configId/windows
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
configId | string | Yes | Resource addon configuration ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
activity_starts_at | string | Yes | Activity start time (ISO 8601) |
activity_ends_at | string | Yes | Activity end time (ISO 8601) |
party_size | number | No | Number of participants (default: 1) |
exclude_booking_id | string | No | Booking ID to exclude from conflicts |
Example Request
GET /api/v1/availability/resource-addons/rac_party_room/windows?activity_starts_at=2025-10-15T14:00:00Z&activity_ends_at=2025-10-15T15:00:00Z&party_size=8
x-tenant-id: t_demo
Response
Status: 200 OK
{
"configId": "rac_party_room",
"windows": [
{
"position": "BEFORE",
"startsAt": "2025-10-15T12:45:00.000Z",
"endsAt": "2025-10-15T13:45:00.000Z",
"durationMins": 60,
"price": 50.0,
"isAvailable": true
},
{
"position": "AFTER",
"startsAt": "2025-10-15T15:15:00.000Z",
"endsAt": "2025-10-15T16:15:00.000Z",
"durationMins": 60,
"price": 50.0,
"isAvailable": true
},
{
"position": "AFTER",
"startsAt": "2025-10-15T15:15:00.000Z",
"endsAt": "2025-10-15T16:45:00.000Z",
"durationMins": 90,
"price": 75.0,
"isAvailable": false
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
configId | string | Configuration identifier |
windows | array | Array of available time windows |
windows[].position | string | One of: BEFORE, DURING, AFTER |
windows[].startsAt | string | Window start time (ISO 8601) |
windows[].endsAt | string | Window end time (ISO 8601) |
windows[].durationMins | number | Duration in minutes |
windows[].price | number | Calculated price for this window |
windows[].isAvailable | boolean | Whether the window is available |
Add Resource Addons to Booking Hold
Add resource addon selections when creating or updating a booking hold.
POST /api/v1/bookings/:bookingId/resource-addons
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bookingId | string | Yes | Booking identifier |
Request Body
{
"selections": [
{
"configId": "rac_party_room",
"startsAt": "2025-10-15T12:45:00.000Z",
"endsAt": "2025-10-15T13:45:00.000Z",
"durationMins": 60
}
]
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
selections | array | Yes | Array of resource selections |
selections[].configId | string | Yes | Resource addon configuration ID |
selections[].startsAt | string | Yes | Start time (ISO 8601) |
selections[].endsAt | string | Yes | End time (ISO 8601) |
selections[].durationMins | number | Yes | Duration in minutes |
Response
Status: 200 OK
{
"success": true,
"allocations": [
{
"id": "alloc_abc123",
"configId": "rac_party_room",
"resourceId": "res_party_room_1",
"startsAt": "2025-10-15T12:45:00.000Z",
"endsAt": "2025-10-15T13:45:00.000Z",
"durationMins": 60,
"price": 50.0,
"status": "HELD"
}
],
"pricingUpdate": {
"resourceAddonTotal": 50.0,
"newSubtotal": 150.0,
"newTaxTotal": 37.5,
"newGrandTotal": 187.5
}
}
Get Booking Resource Addons
Retrieve resource addons for an existing booking.
GET /api/v1/bookings/:bookingId/resource-addons
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bookingId | string | Yes | Booking identifier |
Example Request
GET /api/v1/bookings/bk_123/resource-addons
x-tenant-id: t_demo
Response
Status: 200 OK
{
"addons": [
{
"bookingAddonId": "ba_abc123",
"allocationId": "alloc_abc123",
"addonId": "addon_party_room",
"addonName": "Party Room",
"resourceId": "res_party_room_1",
"resourceName": "Party Room A",
"startsAt": "2025-10-15T12:45:00.000Z",
"endsAt": "2025-10-15T13:45:00.000Z",
"durationMins": 60,
"totalPrice": 50.0,
"status": "CONFIRMED"
}
]
}
Modify Booking Resource Addons
Add or remove resource addons from an existing confirmed booking.
PATCH /api/v1/bookings/:bookingId/resource-addons
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bookingId | string | Yes | Booking identifier |
Request Body
{
"add": [
{
"configId": "rac_vip_lounge",
"startsAt": "2025-10-15T15:15:00.000Z",
"endsAt": "2025-10-15T16:15:00.000Z",
"durationMins": 60
}
],
"remove": ["alloc_xyz789"]
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
add | array | No | Resource addon selections to add |
remove | string[] | No | Allocation IDs to remove |
Response
Status: 200 OK
{
"success": true,
"addons": [...],
"pricingUpdate": {
"resourceAddonTotal": 75.00,
"newSubtotal": 175.00,
"newTaxTotal": 43.75,
"newGrandTotal": 218.75
},
"paymentAdjustment": {
"type": "ADDITIONAL_CHARGE",
"amount": 31.25,
"paymentRequired": true
}
}
Resolve Resource Addon Conflicts
When confirming a booking, resource conflicts may occur if the requested time windows are no longer available. This endpoint resolves conflicts.
POST /api/v1/bookings/:bookingId/resource-addons/resolve-conflict
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bookingId | string | Yes | Booking identifier |
Request Body
{
"action": "SELECT_ALTERNATIVE",
"allocationId": "alloc_abc123",
"newStartsAt": "2025-10-15T16:15:00.000Z",
"newEndsAt": "2025-10-15T17:15:00.000Z",
"expected_pricing_hash": "hash_xyz",
"payment_mode": "stripe"
}
Action Types
| Action | Description |
|---|---|
SELECT_ALTERNATIVE | Choose an alternative time window |
PROCEED_WITHOUT | Continue without the conflicted resource addons |
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Resolution action |
allocationId | string | No | Allocation to modify (for SELECT_ALTERNATIVE) |
newStartsAt | string | No | New start time (for SELECT_ALTERNATIVE) |
newEndsAt | string | No | New end time (for SELECT_ALTERNATIVE) |
releaseAllocationIds | string[] | No | Allocations to release (for PROCEED_WITHOUT) |
expected_pricing_hash | string | Yes | Pricing hash for verification |
payment_mode | string | Yes | Payment mode: stripe or poa |
Response
Status: 200 OK
{
"status": "CONFIRMED",
"paymentIntent": {
"clientSecret": "pi_xxx_secret_yyy"
}
}
Conflict Response Format
When booking confirmation encounters resource conflicts, the response includes conflict details:
{
"resourceConflict": {
"conflicts": [
{
"allocationId": "alloc_abc123",
"addonId": "addon_party_room",
"addonName": "Party Room",
"resourceId": "res_party_room_1",
"resourceName": "Party Room A",
"requestedStartsAt": "2025-10-15T12:45:00.000Z",
"requestedEndsAt": "2025-10-15T13:45:00.000Z",
"conflictReason": "Resource already booked for this time slot",
"alternativeWindows": [
{
"position": "BEFORE",
"startsAt": "2025-10-15T11:30:00.000Z",
"endsAt": "2025-10-15T12:30:00.000Z",
"durationMins": 60,
"price": 50.0,
"isAvailable": true
}
]
}
],
"options": [
{
"action": "SELECT_ALTERNATIVE",
"description": "Choose alternative time slots"
},
{
"action": "PROCEED_WITHOUT",
"description": "Continue without these resources"
}
],
"pricingHash": "hash_abc123"
}
}
Error Responses
400 Bad Request
{
"statusCode": 400,
"message": "Invalid time window: starts_at must be before ends_at",
"error": "Bad Request"
}
404 Not Found
{
"statusCode": 404,
"message": "Resource addon configuration not found",
"error": "Not Found"
}
409 Conflict
{
"statusCode": 409,
"message": "Resource is not available for the requested time window",
"error": "Conflict"
}
422 Unprocessable Entity
{
"statusCode": 422,
"message": "Duration 45 minutes is not allowed for this configuration",
"error": "Unprocessable Entity"
}
Duration & Pricing Modes
Duration Modes
Each resource addon configuration has a durationMode that controls how the customer selects a duration.
FIXED
A single immutable duration set by the admin. The booking request must match exactly.
| Config field | Type | Description |
|---|---|---|
fixedDurationMins | integer (>= 1) | The required duration in minutes |
Example: A party room is always 120 minutes.
SELECTABLE
The customer picks from a predefined list of allowed durations.
| Config field | Type | Description |
|---|---|---|
selectableDurations | integer[] | Allowed durations in minutes |
Example: [30, 60, 90, 120] — the customer picks one.
CUSTOM
The customer enters any duration within a min/max range, optionally constrained to a step increment.
| Config field | Type | Description |
|---|---|---|
minDurationMins | integer (>= 1) | Minimum allowed duration |
maxDurationMins | integer (>= 1) | Maximum allowed duration |
durationStepMins | integer (>= 1, default 30) | Increment step |
Example: min 30, max 180, step 15 produces valid values 30, 45, 60, …, 180.
Validation: The API rejects requests with a duration that does not satisfy the mode's constraints (exact match for FIXED, membership in the list for SELECTABLE, range and step for CUSTOM).
Pricing Modes
Each configuration has a pricingMode that determines how the price is calculated. The calculation runs inside ResourceAddonAvailabilityService.calculatePrice().
FLAT
Constant price regardless of duration or party size.
| Config field | Required | Formula |
|---|---|---|
flatPrice | Yes | price = flatPrice |
Example: Party room = €50 whether booked for 1 hour or 2 hours.
PER_HOUR
Price scales linearly with duration.
| Config field | Required | Formula |
|---|---|---|
perHourPrice | Yes | price = perHourPrice × (durationMins / 60) |
Example: Boat rental at €25/hour for 90 minutes = €37.50.
PER_PERSON
Price scales with party size, independent of duration.
| Config field | Required | Formula |
|---|---|---|
perPersonPrice | Yes | price = perPersonPrice × partySize |
Example: Guide service at €20/person for 4 people = €80.
COMPOSITE
Base fee plus a per-person surcharge.
| Config field | Required | Formula |
|---|---|---|
basePrice | Yes | price = basePrice + (perPersonPrice × partySize) |
perPersonPrice | Yes |
Example: €30 setup fee + €15/person for 4 people = €30 + €60 = €90.
Timing Constraints
Each configuration also controls when the resource can be booked relative to the main activity.
| Field | Type | Default | Description |
|---|---|---|---|
allowBefore | boolean | true | Can be booked before the activity starts |
allowDuring | boolean | true | Can be booked overlapping with the activity |
allowAfter | boolean | true | Can be booked after the activity ends |
bufferMinsBefore | integer | 0 | Setup buffer before the activity (minutes) |
bufferMinsAfter | integer | 0 | Cleanup buffer after the activity (minutes) |
Example: A party room that needs 30 min setup and 15 min cleanup would use bufferMinsBefore: 30, bufferMinsAfter: 15.
Resource Types
Each resource has a resourceType that categorizes it. This affects admin UI grouping and reporting but does not change booking logic.
| Type | Description | Examples |
|---|---|---|
ROOM | Physical spaces with capacity limits, often requiring setup/cleanup time | Party Room A, Conference Hall |
EQUIPMENT | Items needed for activities, can be shared or exclusive | Laser Tag Set 1, Cooking Station |
VEHICLE | Watercraft, land vehicles, or other transport | Kayak #3, Tour Bus, Pontoon Boat |
STAFF | Personnel required for activities, supports skill-based scheduling | Instructor Sarah, Safety Supervisor |
OTHER | Consumables, inventory, or anything that doesn't fit the above | Safety Waiver, Equipment Cleaning Kit |
Shared Modes
Each resource has a sharedMode that controls how its capacity is consumed across concurrent bookings.
EXCLUSIVE
The resource can only be used by one booking at a time. When booked, the resource is fully locked for that time window.
Use case: A private party room that cannot be double-booked.
SHARED
The resource can be used by multiple bookings simultaneously, up to its base capacity. Each booking consumes a portion of the capacity.
Use case: A set of laser tag vests shared across groups running at the same time.
POOLED
The resource is part of a capacity pool. Bookings draw from the pool's total capacity rather than from individual resource units.
Use case: General admission slots where the total venue capacity is shared across all concurrent bookings.
See the Catalog Guide for admin UI configuration of resource types and shared modes.
Notes
- Resource addon allocations are held when added to a booking hold
- Held allocations expire when the booking hold expires
- Confirmed allocations become permanent when the booking is confirmed
- Resource availability is checked in real-time during confirmation
- Price changes between hold and confirmation trigger pricing hash verification