Rate Management
The catalog and offer endpoints are read-only. These endpoints let an integration manage the rate stack: create rate plans, layer seasonal pricing, bundle inclusions and credits, and run promotions with codes. They are how a hotel drives its Veridien pricing from its own site or back office.
Every endpoint here requires the rates:write scope, except the management reads (GET), which require rates:read.
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.
POST /rate-plans| Field | Required | Notes |
|---|---|---|
room_type_id | yes | The room type this plan prices. |
name | yes | Internal name (1 to 100 characters). |
base_price | yes | Nightly price as a decimal string, for example "120.00". |
currency | no | Must be the property's base currency or a configured currency. Defaults to base. |
public_name | no | Guest-facing name. |
description | no | Guest-facing blurb. |
image_url | no | Absolute URL. |
extra_guest_charge | no | Per-night charge per guest above base occupancy. Defaults to "0.00". |
single_guest_discount | no | Defaults to "0.00". |
single_guest_discount_type | no | fixed or percent. Defaults to fixed. |
min_los / max_los | no | Length-of-stay bounds in nights. |
booking_window_start / _end | no | When the plan may be booked (YYYY-MM-DD). |
stay_window_start / _end | no | When the plan may be stayed. |
active_for_booking_engine | no | Defaults to false. |
active_for_channels | no | Defaults to false. |
kind | no | standard (default) or promotion. |
derived_from_id | no | For a promotion: the base plan it derives from. |
derivation_type | no | percent, fixed, or perPerson. Required with derived_from_id. |
derivation_value | no | Signed 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
}'{ "rate_plan_id": "5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02" }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",
"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.
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 /rate-plans/{id}The default rate plan cannot be deleted (403). Reservations already booked on a deleted plan keep the price they were quoted.
{ "rate_plan_id": "5c1e8f4a-2b7d-4a9c-8e6f-1d3b9a5c7e02", "deleted": true }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.
GET /rate-plans/{id}/intervalsRequires rates:read.
POST /rate-plans/{id}/intervals| Field | Required | Notes |
|---|---|---|
name | yes | For example "High Season". |
start_date / end_date | yes | YYYY-MM-DD. Start must be before end. |
price_mon … price_sun | no | Per-weekday price. A blank day falls back to base price. |
min_stay / max_stay | no | Length-of-stay rules for this range. |
closed_to_arrival / closed_to_departure / stop_sell | no | Availability flags. Default false. |
extra_guest_charge / single_guest_discount | no | Per-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
}'{ "interval_id": "d8a4c2f6-5e9b-4d7a-8f1c-3b6e0a9d5c28" }PATCH /intervals/{id}
DELETE /intervals/{id}PATCH accepts any interval field and re-checks the non-overlap rule.
An inclusion is a perk (a fixed included item) or a credit (a spendable allowance for a category, such as a spa credit). Inclusions are set as a whole list: a POST replaces the plan's entire inclusion set.
GET /rate-plans/{id}/inclusionsRequires rates:read.
POST /rate-plans/{id}/inclusions| Field | Required | Notes |
|---|---|---|
label | yes | Guest-facing label, for example "Spa credit". |
allocated_amount | yes | Value as a decimal string. "0.00" for a free perk. |
frequency | yes | perStay, perNight, perGuest, or perGuestPerNight. |
inclusion_kind | no | perk (default) or credit. |
revenue_category | yes | room, fnb, spa, excursions, activities, or other. For a credit this is the category it applies to. |
service_item_id | no | Link to a catalogue service item. |
currency | no | Defaults to the plan currency. |
included_quantity | no | 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 }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.
GET /rate-plans/{id}/promo-codesRequires rates:read.
POST /rate-plans/{id}/promo-codes| Field | Required | Notes |
|---|---|---|
code | yes | 3 to 40 characters. Stored and matched in upper case. |
valid_from / valid_to | no | YYYY-MM-DD window. |
max_redemptions | no | Total-use cap. Omit for unlimited. |
max_per_guest | no | Per-guest cap. |
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 }'{ "promo_code_id": "a5e2c8f1-7d4b-4a6e-b9c3-0f8d2a5e7c41", "code": "EARLYBIRD25" }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 bookings freeze the code they were made with; delete it and create a new one instead. Deleting a code does not affect reservations already booked with it.