Skip to main content

Cancellation & Reschedule Policies

Policies are the rules that decide whether a booking can be cancelled or rescheduled, and how much the customer is refunded when they do. They live at the supplier level and can be scoped to specific products, experiences, categories, or "all".

Tiered policies (Phase 1.9+)โ€‹

A policy can carry a tier ladder โ€” multiple refund thresholds keyed by how many hours before the booking start the customer cancels. The matching tier is the highest applies_after_hours whose value is less than or equal to the actual hours-before-cancellation. Cancellations that miss every tier (e.g. cancelling 1 hour before when the lowest tier is "24h+ before") receive 0% refund.

Each tier has its own optional refund_method. The policy-level refund_method is used as the default when the tier doesn't specify one. A non_refundable_fee (per-policy fixed amount) can be deducted from any refund.

Legacy single-tier fields (refund_percentage, time_before_booking, time_before_booking_unit, applies_up_to) are retained on the response for backwards compatibility with integrators that pre-date tiers. New clients should read tiers[] and ignore the legacy fields.

Endpointsโ€‹

MethodPathScope
GET/v1/settings/policiespolicies:read or settings:read
GET/v1/settings/policies/applicablepolicies:read or settings:read
GET/v1/settings/policies/{id}policies:read or settings:read
POST/v1/settings/policiespolicies:write
PUT/v1/settings/policies/{id}policies:write
DELETE/v1/settings/policies/{id}policies:write

List Policiesโ€‹

GET /v1/settings/policies

Response (200 OK)โ€‹

{
"data": [
{
"id": 1,
"name": "Flexible",
"description": "Full refund up to 7 days before, 50% up to 24h before.",
"applies_to": "all-experiences",
"allow_reschedule": true,
"reschedule_cutoff_hours": 48,
"allow_cancellations": true,
"tiers": [
{ "id": 11, "applies_after_hours": 168, "human_threshold": "1 week", "refund_percentage": 100.0, "refund_method": "original_payment" },
{ "id": 12, "applies_after_hours": 24, "human_threshold": "1 day", "refund_percentage": 50.0, "refund_method": "original_payment" },
{ "id": 13, "applies_after_hours": 0, "human_threshold": "Within window", "refund_percentage": 0.0, "refund_method": "original_payment" }
],
"non_refundable_fee": 5.00,
"refund_method": "original_payment",
"currency": "USD",
"refund_percentage": null,
"time_before_booking": null,
"time_before_booking_unit": null,
"applies_up_to": "cutOff",
"applications": [],
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-04-01T00:00:00+00:00"
}
],
"meta": { "request_id": "req_abc123", "timestamp": "2026-04-22T12:00:00+00:00" }
}

Response Fieldsโ€‹

FieldTypeDescription
idintegerPolicy ID
namestringPolicy name
descriptionstring|nullCustomer-facing description
applies_tostringScope. One of: product, category, experience, experience-category, all-products, all-experiences, all-products-experiences, all
allow_reschedulebooleanWhether rescheduling is allowed
reschedule_cutoff_hoursinteger|nullHours before the booking start that a customer may still self-reschedule. null = any time before start. Customer self-service only โ€” staff are never blocked.
allow_cancellationsbooleanWhether cancellations are allowed
tiersarrayRefund tiers, sorted descending by applies_after_hours (most-generous first)
tiers[].idintegerTier ID
tiers[].applies_after_hoursintegerLead-time threshold (hours before booking start)
tiers[].human_thresholdstringHuman-readable label e.g. "1 week", "24 hours", "Within window"
tiers[].refund_percentagefloat0โ€“100
tiers[].refund_methodstringoriginal_payment or store_credit
non_refundable_feefloatFixed amount deducted from any refund (default 0)
refund_methodstring|nullDefault refund method when a tier doesn't set its own
currencystring|nullISO 4217 currency code
applicationsarrayItems this policy is scoped to (see below)
created_at, updated_atstringISO 8601 datetimes
refund_percentage (legacy)float|nullSingle-tier % โ€” deprecated; use tiers
time_before_booking (legacy)integer|nullSingle-tier threshold value โ€” deprecated
time_before_booking_unit (legacy)string|nullSingle-tier threshold unit (hours, days, weeks) โ€” deprecated
applies_up_to (legacy)string|nullcutOff or any โ€” deprecated

Application Objectsโ€‹

FieldTypeDescription
typestringApplication type: product, category, experience, experience-category, all-products, all-experiences, all-products-experiences, all
product_id, product_nameinteger, stringSet when type is product
category_id, category_nameinteger, stringSet when type is category
experience_id, experience_nameinteger, stringSet when type is experience
experience_category_id, experience_category_nameinteger, stringSet when type is experience-category

Get Applicable Policy For Itemโ€‹

Resolves which policy will apply to a single product / experience / bundle / store product, using the same hierarchy as the widget detail page and portal cart:

  1. Specific product / experience policies
  2. Category / experience-category policies
  3. all-products / all-experiences policies
  4. all-products-experiences / all (catch-all)

Within a bucket, the most-generous top-tier wins as a deterministic tiebreaker.

GET /v1/settings/policies/applicable?item_type={type}&item_id={id}

Query Parametersโ€‹

ParamTypeDescription
item_typestringproduct | experience | bundle | store_product
item_idintegerThe item ID

Response (200 OK)โ€‹

{
"data": {
"item_type": "experience",
"item_id": 42,
"policy": {
"id": 1,
"name": "Flexible",
"tiers": [ ... ],
"non_refundable_fee": 5.00,
"...": "..."
}
},
"meta": { "request_id": "req_abc123", "timestamp": "..." }
}

policy is null when no policy applies. Returns 404 when the item itself doesn't exist for this supplier.


Get Single Policyโ€‹

GET /v1/settings/policies/{id}

Returns the same shape as one element in the list response.


Create Policyโ€‹

POST /v1/settings/policies

Request Bodyโ€‹

{
"name": "Flexible",
"description": "Full refund up to 7 days before, 50% up to 24h before.",
"applies_to": "all-experiences",
"allow_cancellations": true,
"allow_reschedule": true,
"reschedule_cutoff_hours": 48,
"non_refundable_fee": 5.00,
"refund_method": "original_payment",
"currency": "USD",
"tiers": [
{ "applies_after_hours": 168, "refund_percentage": 100, "refund_method": "original_payment" },
{ "applies_after_hours": 24, "refund_percentage": 50, "refund_method": "original_payment" },
{ "applies_after_hours": 0, "refund_percentage": 0 }
],
"applications": [
{ "type": "all-experiences" }
]
}

Body Fieldsโ€‹

FieldTypeRequiredNotes
namestringyesMax 255 chars
descriptionstringnoMax 5000 chars
applies_toenumyesSee enum list above
allow_cancellationsbooleannoDefault true
allow_reschedulebooleannoDefault true
reschedule_cutoff_hoursintegernoHours before start a customer may still self-reschedule. Omit / null = any time before start. Staff are never blocked.
non_refundable_feenumbernoโ‰ฅ 0, default 0
refund_methodenumnooriginal_payment | store_credit
currencystringnoISO 4217, 3 chars
tiersarraynoWhen omitted with no legacy fields, no refund will run
tiers[].applies_after_hoursintegeryes (in tier)โ‰ฅ 0
tiers[].refund_percentagenumberyes (in tier)0โ€“100
tiers[].refund_methodenumnoFalls back to policy refund_method
applicationsarraynoOne row per scope binding
applications[].typeenumyes (in app)product, category, experience, experience-category, all-products, all-experiences, all-products-experiences, all
applications[].{product_id,category_id,experience_id,experience_category_id}integerconditionalRequired for the matching type

Response (201 Created)โ€‹

Returns the created policy with tiers and applications populated.


Update Policyโ€‹

PUT /v1/settings/policies/{id}

Same body as create. Any field omitted is left unchanged. Sending tiers (or applications) replaces the full set atomically โ€” partial updates per row are not supported. To preserve existing tiers, fetch them first then send them back.

Response (200 OK)โ€‹

The updated policy.


Delete Policyโ€‹

DELETE /v1/settings/policies/{id}

Deletes the policy plus all of its tiers and applications. Returns 204 No Content.


Errorsโ€‹

StatusCodeDescription
401authentication_requiredMissing API key
403insufficient_scopeAPI key lacks the required scope
404resource_not_foundPolicy not found for your supplier
422validation_errorInvalid body (e.g. tiers[].refund_percentage > 100)
422policy_create_failed / policy_update_failedDB-level failure (constraint violation, etc.)