Skip to content
Log in
Rate Management

Rate Management

Use these endpoints to manage your rates from your own site or back office: create rate plans, layer seasonal pricing, bundle inclusions and credits, and run promotions with codes. The catalog and offer endpoints only read; these write.

Every endpoint here needs the rates:write scope, except the GET reads, which need rates:read. Updating a plan, or changing its intervals, queues a rate update to your connected channels.

A promotion is a rate plan

A promotion is a rate plan with kind: "promotion" and a derivation from a base plan. There is no separate promotion endpoint: you create and edit one through the same /rate-plans endpoints, setting kind, derived_from_id, derivation_type, and derivation_value.

Idempotency

Every write here is a mutating request. Send an Idempotency-Key header to make retries safe, as described in Conventions.


Create a rate plan

POST /rate-plans
FieldRequiredNotes
room_type_idyesThe room type this plan prices.
nameyesInternal name, 1 to 100 characters.
base_priceyesNightly price as a decimal string, for example "120.00".
currencynoMust be the property's base currency or a configured currency. Defaults to base.
public_namenoGuest-facing name, up to 200 characters.
descriptionnoGuest-facing description, up to 2,000 characters.
image_urlnoAbsolute URL.
extra_guest_chargenoPer-night charge per guest above base occupancy. Defaults to "0.00".
single_guest_discountnoDefaults to "0.00".
single_guest_discount_typenofixed or percent. Defaults to fixed.
min_los / max_losnoLength-of-stay bounds in nights, 1 to 365.
booking_window_start / _endnoWhen the plan is on sale (YYYY-MM-DD).
stay_window_start / _endnoWhen the stay may fall (YYYY-MM-DD).
active_for_booking_enginenoDefaults to false.
active_for_channelsnoDefaults to false.
requires_promo_codenotrue hides the plan from listings and makes it bookable only with one of its promo codes. Defaults to false.
kindnostandard (default) or promotion.
derived_from_idnoFor a promotion: the base plan it derives from.
derivation_typenopercent, fixed, or perPerson. Required with derived_from_id.
derivation_valuenoSigned decimal string. Negative is a discount, for example "-20.00". Required with derived_from_id.
curl -X POST "$VRDN_BASE/rate-plans" \
  -H "Authorization: Bearer $VRDN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "room_type_id": "9b2f6c3e-8d1a-4f5b-a7c9-4e0d2b8f1a63",
    "name": "Bed & Breakfast",
    "base_price": "120.00",
    "public_name": "Bed & Breakfast",
    "description": "A full breakfast for two each morning.",
    "active_for_booking_engine": true
  }'

Returns 201:

{ "rate_plan_id": "5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02" }

An unknown room_type_id returns 404 room_type_not_found.

Create a promotion

Set kind, the parent, and a negative derivation. Gate it behind a code by adding one (see below) and setting requires_promo_code: true.

curl -X POST "$VRDN_BASE/rate-plans" \
  -H "Authorization: Bearer $VRDN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "room_type_id": "9b2f6c3e-8d1a-4f5b-a7c9-4e0d2b8f1a63",
    "name": "Advance Purchase, save 20%",
    "base_price": "120.00",
    "kind": "promotion",
    "derived_from_id": "5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02",
    "derivation_type": "percent",
    "derivation_value": "-20.00",
    "requires_promo_code": true,
    "active_for_booking_engine": true
  }'

A derived plan needs both derivation_type and derivation_value, or the request is rejected. Its price is always the parent's current price with the derivation applied, so it follows the parent's seasonal pricing automatically.

Update a rate plan

PATCH /rate-plans/{id}

Send only the fields you want to change. Accepts the same fields as create (except room_type_id), plus is_active.

{ "rate_plan_id": "5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02", "updated": true }

Delete a rate plan

DELETE /rate-plans/{id}

The default rate plan cannot be deleted (403 cannot_delete_default). An unknown id returns 404 rate_plan_not_found. Reservations already made on a deleted plan keep the price they were quoted.

{ "rate_plan_id": "5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02", "deleted": true }

Seasonal intervals

Intervals layer date-specific pricing and stay rules over a plan's base price. Intervals on the same plan must not overlap; an overlapping range returns 409 interval_overlap.

List intervals

GET /rate-plans/{id}/intervals

Requires rates:read. Returns { "data": [...] }, one entry per interval with its id, rate_plan_id, and every field below.

Create an interval

POST /rate-plans/{id}/intervals
FieldRequiredNotes
nameyes1 to 100 characters, for example "High Season".
start_date / end_dateyesYYYY-MM-DD. Start must be before end.
price_mon to price_sunnoPer-weekday price. A day you leave out falls back to the base price.
min_stay / max_staynoLength-of-stay rules for this range, at least 1. min_stay cannot exceed max_stay.
closed_to_arrival / closed_to_departure / stop_sellnoAvailability flags. Default false.
extra_guest_charge / single_guest_discountnoPer-season overrides. Default "0.00".
curl -X POST "$VRDN_BASE/rate-plans/5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02/intervals" \
  -H "Authorization: Bearer $VRDN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "High Season",
    "start_date": "2026-12-01",
    "end_date": "2027-03-31",
    "price_mon": "180.00", "price_fri": "210.00",
    "min_stay": 3
  }'

Returns 201:

{ "interval_id": "d8a4c2f6-5e9b-4d7a-8f1c-3b6e0a9d5c28" }

Update and delete an interval

PATCH /intervals/{id}
DELETE /intervals/{id}

PATCH accepts any interval field and checks the non-overlap rule again. Both return 404 interval_not_found for an unknown id.


Inclusions and credits

An inclusion is a perk (a fixed included item) or a credit (a spendable allowance for a category, such as a spa credit). You set inclusions as a whole list: a POST replaces the plan's entire set, up to 30 entries. Send an empty list to remove them all.

List inclusions

GET /rate-plans/{id}/inclusions

Requires rates:read. Returns { "data": [...] }, one entry per inclusion with its id and the fields below.

Replace inclusions

POST /rate-plans/{id}/inclusions
FieldRequiredNotes
labelyesGuest-facing label, up to 120 characters, for example "Spa credit".
allocated_amountyesValue as a decimal string. "0.00" for a free perk.
frequencyyesperStay, perNight, perGuest, or perGuestPerNight.
inclusion_kindnoperk (default) or credit.
revenue_categoryyesA category name of 1 to 40 characters, for example room, fnb, or spa. For a credit, this is the category it applies to.
service_item_idnoLink to a catalogue service item.
currencynoDefaults to the plan currency.
included_quantityno1 to 99. Defaults to 1.
curl -X POST "$VRDN_BASE/rate-plans/5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02/inclusions" \
  -H "Authorization: Bearer $VRDN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inclusions": [
      { "label": "Daily breakfast for two", "allocated_amount": "20.00", "frequency": "perGuestPerNight", "revenue_category": "fnb" },
      { "label": "Spa credit", "allocated_amount": "150.00", "frequency": "perStay", "inclusion_kind": "credit", "revenue_category": "spa" }
    ]
  }'
{ "rate_plan_id": "5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02", "count": 2 }

Promo codes

A code unlocks a promotion. The discount lives in the promotion, so a code never carries a saving of its own. Attach codes to the plan they unlock.

List codes

GET /rate-plans/{id}/promo-codes

Requires rates:read. Returns { "data": [...] }, one entry per code with id, code, rate_plan_id, valid_from, valid_to, max_redemptions, redemption_count, max_per_guest, and is_active.

Create a code

POST /rate-plans/{id}/promo-codes
FieldRequiredNotes
codeyes3 to 40 characters. Trimmed and stored in upper case, and must be unique in the property (409 code_exists otherwise).
valid_from / valid_tonoYYYY-MM-DD window. valid_to cannot be before valid_from.
max_redemptionsnoTotal-use cap. Omit for unlimited.
max_per_guestnoPer-guest cap. Omit for unlimited.
curl -X POST "$VRDN_BASE/rate-plans/e2a9d7c4-3f6b-4c1e-9a8d-7b5f0c2e4a19/promo-codes" \
  -H "Authorization: Bearer $VRDN_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "EARLYBIRD25", "max_redemptions": 100 }'

Returns 201:

{ "promo_code_id": "a5e2c8f1-7d4b-4a6e-b9c3-0f8d2a5e7c41", "code": "EARLYBIRD25" }

Update and delete a code

PATCH /promo-codes/{id}
DELETE /promo-codes/{id}

PATCH accepts valid_from, valid_to, max_redemptions, max_per_guest, and is_active. The code string itself cannot be changed, because reservations freeze the code they were made with; delete it and create a new one instead. Deleting a code does not affect reservations already made with it. Both return 404 promo_code_not_found for an unknown id.