Cancellation Policies
Refund rules per listing and sales channel, fixed at quote time and applied when a guest cancels
Introduction
A cancellation policy is a named set of refund tiers owned by a tenant. Zepl works out which policy applies to a stay, dates its deadlines against the stay's check-in, returns the resulting terms on every quote and reservation, and calculates the charge when the guest cancels.
Policies are resolved per listing and per sales channel, so the same listing can be sold under different terms on the owner's own site, through the network and through AI agents.
Policy Shape
A policy is an ordered list of refund tiers. Each tier says: cancelling at least this many days before check-in refunds this percentage of the booked total.
- daysBeforeCheckIn — integer, 0 to 365
- refundPercentage — 0 to 100
Tiers are kept furthest from check-in first. Closer to check-in the refund may stay the same or drop, never rise. An empty list means non-refundable.
Presets
| Preset | Tiers | Meaning |
|---|---|---|
FLEXIBLE | 1 day → 100% | Full refund until one day before check-in |
MODERATE | 5 days → 100% | Full refund until five days before check-in |
STRICT | 14 days → 100%, 7 days → 50% | Full refund until two weeks before, half until one week before |
NON_REFUNDABLE | none | No refund at any time |
A preset is written with exactly these tiers. To use other numbers, send custom tiers instead. List the templates with:
GET /api/v1/cancellation-policies/presetsCreating a Policy
Send either a preset or custom tiers. The name defaults to the preset's name.
POST /api/v1/cancellation-policies{
"preset": "MODERATE"
}{
"name": "Summer strict",
"tiers": [
{ "daysBeforeCheckIn": 30, "refundPercentage": 100 },
{ "daysBeforeCheckIn": 7, "refundPercentage": 50 }
]
}Every policy carries a version. Replacing the tiers with PUT /api/v1/cancellation-policies/{id} bumps it. Renaming does not. Reservations record the policy ID and version they were booked under.
Sales Channels
A stay's sales channel decides which assignment applies. The built-in channels are:
| Channel | Meaning |
|---|---|
direct | Sold on the owner's own booking site. The default. |
network | Sold on another tenant's site through the network |
ai_agent | Sold by an AI agent |
Pass salesChannel in the quote and reservation request bodies. When it is omitted, the stay is treated as direct.
For network sales the policy of the tenant that owns the listing applies. The selling tenant cannot override it.
Assigning Policies
An assignment attaches a policy to a level. There is one assignment per level and a new one replaces the previous one at that level.
PUT /api/v1/cancellation-policies/assignments| Body | Level |
|---|---|
{ "policyID" } | Tenant default, every listing on every channel |
{ "policyID", "channel" } | Every listing on one channel |
{ "policyID", "listingID" } | One listing on every channel |
{ "policyID", "listingID", "channel" } | One listing on one channel |
Only the tenant that owns a listing can assign a policy to it.
Remove an assignment with the same scope as a query string, so the next level applies again:
DELETE /api/v1/cancellation-policies/assignments?listingID={listingId}&channel=networkList the current assignments with GET /api/v1/cancellation-policies/assignments, optionally filtered by listingID and channel.
Resolution Order
When a stay is quoted, the most specific assignment wins:
- Listing on this channel
- Listing on every channel
- Every listing on this channel
- Tenant default
- Nothing configured: the stay is sold non-refundable
Check what a listing sells under, and where that came from, with:
GET /api/v1/cancellation-policies/effective?listingID={listingId}&channel=direct{
"listingID": "2b7a9b1e-7d45-4f4b-9b1a-1c3d5e7f9a0b",
"salesChannel": "direct",
"source": "TENANT",
"policyID": "2f8d6b1a-9c3e-4d7f-8a1b-5e6c7d8f9a0b",
"name": "Strict",
"version": 1,
"tiers": [
{ "daysBeforeCheckIn": 14, "refundPercentage": 100 },
{ "daysBeforeCheckIn": 7, "refundPercentage": 50 }
]
}source is one of LISTING_CHANNEL, LISTING, TENANT_CHANNEL, TENANT or DEFAULT.
DEFAULT means nothing is configured for that listing and channel. Such stays are quoted and booked as non-refundable. Assign a tenant default policy to avoid it.
Terms on Quotes and Reservations
Every quote returns the terms the stay is sold under as cancellationPolicy. The tiers are turned into dated penalty steps, and the percentages into amounts against the quoted total.
{
"cancellationPolicy": {
"policyID": "2f8d6b1a-9c3e-4d7f-8a1b-5e6c7d8f9a0b",
"name": "Strict",
"version": 1,
"source": "TENANT",
"salesChannel": "direct",
"timeZone": "Europe/Sofia",
"fixedAt": "2026-10-21T10:00:00.000Z",
"refundable": true,
"refundableTag": "RFN",
"freeCancellationUntil": "2026-11-21T13:00:00.000Z",
"penalties": [
{
"from": "2026-11-21T13:00:00.000Z",
"percentage": 50,
"amount": 243,
"currency": "EUR"
},
{
"from": "2026-11-28T13:00:00.000Z",
"percentage": 100,
"amount": 486,
"currency": "EUR"
}
],
"description": "Full refund if cancelled before 2026-11-21 15:00 (Europe/Sofia). 50% refund (243.00 EUR) if cancelled before 2026-11-28 15:00 (Europe/Sofia). No refund after that."
}
}| Field | Description |
|---|---|
policyID, name, version | The policy applied. policyID and version are null under the non-refundable default. |
source | The level the policy came from, as in the effective lookup. |
salesChannel | The channel the stay was sold on. |
timeZone | The property's IANA timezone. Deadlines were set in it. |
fixedAt | When the terms were fixed, which is the time of the quote. |
refundable | false when cancelling already refunds nothing. |
refundableTag | RFN for refundable, NRFN for non-refundable. |
freeCancellationUntil | Cancelling before this moment is free. null when it never is. |
penalties | What cancelling costs from each moment. Steps rise over time and the last step is always 100%. |
description | A summary with the deadlines in the property's local time, ready to show to the guest. |
Deadlines
A tier's deadline falls at the property's check-in time on the relevant day, in the property's timezone. The timezone is the listing's timeZone, or is derived from its coordinates when the listing has none. The check-in time is the listing's checkIn, or 15:00 when it has none. All timestamps are returned as UTC instants.
Fixed at Quote
The terms are fixed when the quote is created and are carried by the quoteToken. Send the token on the reservation and the booking is made under exactly the quoted terms, even if the policy was changed in between. Without a token, the terms are fixed at booking time for the salesChannel sent with the reservation.
Amounts follow the booked total. The reservation's cancellationPolicy is returned by Create and by Get, together with its salesChannel.
Cancelling
Say who asked for the cancellation with the cancelledBy query parameter:
DELETE /api/v1/reservation/{reservationId}?cancelledBy=guestcancelledBy | Effect |
|---|---|
guest | The booked terms set the charge. |
host | No charge is recorded. The refund is the host's call. |
system | No charge is recorded. |
| omitted | Recorded without an initiator. No charge. |
When the guest cancels, the response includes the charge worked out from the terms stored on the reservation at the moment the request was received:
{
"id": "a3f1c2d4-5b6e-4f70-8a9b-0c1d2e3f4a5b",
"status": "CANCELLED",
"cancelledAt": "2026-11-25T09:00:00.000Z",
"cancelledBy": "GUEST",
"cancellationCharge": {
"penaltyPercentage": 50,
"penaltyAmount": 243,
"refundPercentage": 50,
"refundAmount": 243,
"currency": "EUR"
}
}The charge is informational. Zepl records what the policy says is due in the booking currency. Collecting or refunding money is up to you.
Archiving a Policy
A policy can be archived once it is no longer assigned anywhere:
DELETE /api/v1/cancellation-policies/{policyId}Archived policies are hidden from the list unless you pass includeArchived=true. Reservations booked under an archived policy keep their terms.