CalryOTA Docs

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

PresetTiersMeaning
FLEXIBLE1 day → 100%Full refund until one day before check-in
MODERATE5 days → 100%Full refund until five days before check-in
STRICT14 days → 100%, 7 days → 50%Full refund until two weeks before, half until one week before
NON_REFUNDABLEnoneNo 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/presets

Creating 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:

ChannelMeaning
directSold on the owner's own booking site. The default.
networkSold on another tenant's site through the network
ai_agentSold 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
BodyLevel
{ "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=network

List 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:

  1. Listing on this channel
  2. Listing on every channel
  3. Every listing on this channel
  4. Tenant default
  5. 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."
  }
}
FieldDescription
policyID, name, versionThe policy applied. policyID and version are null under the non-refundable default.
sourceThe level the policy came from, as in the effective lookup.
salesChannelThe channel the stay was sold on.
timeZoneThe property's IANA timezone. Deadlines were set in it.
fixedAtWhen the terms were fixed, which is the time of the quote.
refundablefalse when cancelling already refunds nothing.
refundableTagRFN for refundable, NRFN for non-refundable.
freeCancellationUntilCancelling before this moment is free. null when it never is.
penaltiesWhat cancelling costs from each moment. Steps rise over time and the last step is always 100%.
descriptionA 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=guest
cancelledByEffect
guestThe booked terms set the charge.
hostNo charge is recorded. The refund is the host's call.
systemNo charge is recorded.
omittedRecorded 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.

API Reference

On this page