Guests & Loyalty
Use these endpoints to register the guest a reservation belongs to, keep their profile current, and read or spend their loyalty points. Each guest belongs to one property and can have a loyalty account that earns on confirmed stays.
Register or link a guest
POST /guestsScope: guests:write
Finds a guest by email within the property, or creates one. Calling it again with the same address returns the existing guest rather than creating a duplicate, so it is safe to call on every checkout. An existing guest's name and other fields are not changed; use PATCH /guests/{id} for that. Guests that were merged into another profile are not matched.
| Body field | Required | Notes |
|---|---|---|
email | yes | A valid email address. Used as the match key. |
first_name | yes | 1 to 100 characters. |
last_name | yes | 1 to 100 characters. |
phone | no | Up to 50 characters. |
nationality | no | Up to 100 characters. |
curl -X POST "$VRDN_BASE/guests" \
-H "Authorization: Bearer $VRDN_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "ada@example.com", "first_name": "Ada", "last_name": "Lovelace", "nationality": "GB" }'{ "guest_id": "3f8a2c1b-9d4e-4c6a-8b7f-5e2d1a9c0b34", "created": true }Returns 201 with "created": true for a new guest, or 200 with "created": false when an existing guest was linked.
Get a guest
GET /guests/{id}Scope: guests:read (PII fields additionally require guests:read:pii)
curl "$VRDN_BASE/guests/3f8a2c1b-9d4e-4c6a-8b7f-5e2d1a9c0b34" -H "Authorization: Bearer $VRDN_KEY"With guests:read only:
{
"id": "3f8a2c1b-9d4e-4c6a-8b7f-5e2d1a9c0b34",
"first_name": "Ada",
"last_name": "Lovelace",
"nationality": "GB",
"is_local_verified": false,
"merged_into_id": null
}With guests:read:pii, the response also includes email, phone, date_of_birth, id_type, and id_number. An unknown id returns 404 guest_not_found.
| Field | Type | Notes |
|---|---|---|
is_local_verified | boolean | Whether the guest is verified as a local resident (unlocks local-only rates). |
merged_into_id | string | null | Set when this profile was merged into another guest. That guest now holds the stays and loyalty, and this record can no longer be updated. |
Update a guest
PATCH /guests/{id}Scope: guests:write
Updates a guest's profile. Send only the fields that change, and at least one. Setting local_verified marks a guest as a verified local resident and records when.
| Body field | Notes |
|---|---|
first_name, last_name | 1 to 100 characters. |
phone | Up to 50 characters. |
nationality | Up to 100 characters. |
local_verified | Boolean. true unlocks local-only rates for this guest. |
curl -X PATCH "$VRDN_BASE/guests/3f8a2c1b-9d4e-4c6a-8b7f-5e2d1a9c0b34" \
-H "Authorization: Bearer $VRDN_KEY" \
-H "Content-Type: application/json" \
-d '{ "local_verified": true }'Returns the updated guest in the same shape as GET /guests/{id}. A guest that was merged into another returns 409 guest_merged; update the guest named in merged_into_id instead.
Get loyalty standing
GET /guests/{id}/loyaltyScope: loyalty:read
A guest's points balance, lifetime points, current tier, progress to the next tier, and the recent ledger. Guests with no loyalty activity yet return a zeroed account rather than a 404.
curl "$VRDN_BASE/guests/3f8a2c1b-9d4e-4c6a-8b7f-5e2d1a9c0b34/loyalty" -H "Authorization: Bearer $VRDN_KEY"{
"points_balance": 1260,
"lifetime_points": 1260,
"tier": "Coral",
"next_tier": { "name": "Lagoon", "min_points": 5000 },
"points_to_next_tier": 3740,
"recent_transactions": [
{ "type": "earn", "points": 1260, "description": "Stay B7XKQ4M2NZ", "created_at": "2026-06-19T08:46:00.000Z" }
]
}| Field | Type | Notes |
|---|---|---|
points_balance | integer | Spendable points. |
lifetime_points | integer | Cumulative earned points (drives tier; never decremented). |
tier | string | Current tier name. |
next_tier | object | null | The next tier and its threshold, or null at the top tier. |
recent_transactions | array | Up to 10 most-recent ledger entries (earn, redeem, expire, adjust). |
Redeem loyalty points
POST /loyalty/redemptionsScope: loyalty:write
Spends a guest's points and records a redeem ledger entry. The balance is checked in the same step as the debit, so a redemption can never overdraw; redeeming more than the balance returns 409 insufficient_points. The property's plan must include loyalty, or you get 403 loyalty_not_enabled.
| Body field | Required | Notes |
|---|---|---|
guest_id | yes | Must belong to this property. |
points | yes | Positive integer. |
reservation_id | no | Pays the points toward this stay. Must belong to this property and guest. |
curl -X POST "$VRDN_BASE/loyalty/redemptions" \
-H "Authorization: Bearer $VRDN_KEY" \
-H "Content-Type: application/json" \
-d '{ "guest_id": "3f8a2c1b-9d4e-4c6a-8b7f-5e2d1a9c0b34", "points": 1000, "reservation_id": "7d5dc2ea-1f4e-4bfa-9a52-6c3f08b41e27" }'With reservation_id, the points pay toward that stay: a loyalty discount line is posted to the stay's folio, the program's per-stay cap applies, and the points come back if the stay is cancelled or becomes a no-show. A redemption the program refuses (over the per-stay cap, for example) returns 409 redemption_refused with the reason.
{
"redeemed_points": 1000,
"redeemed_value_usd": 10,
"new_balance": 260,
"folio_line_amount": "-10.00",
"folio_line_currency": "USD"
}Without reservation_id, it is a points-only redemption, and the response returns the guest's tier in place of the folio fields:
{
"redeemed_points": 1000,
"redeemed_value_usd": 10,
"new_balance": 260,
"tier": "Coral"
}| Field | Type | Notes |
|---|---|---|
redeemed_value_usd | number | The value of the points in the property's base currency, whatever that currency is. The name is historical. |
folio_line_amount / folio_line_currency | string | The discount line posted to the folio, in the folio's currency. |
On a points-only redemption, apply redeemed_value_usd as the discount in your own checkout. Points are earned automatically when a stay is confirmed, on its room revenue in the base currency (see Reservations).
Related pages
- Reservations: holds and confirmations that earn loyalty.
- Authentication: which scope returns guest personal data.