Recipes
Common workflows in copy-pasteable form. Every recipe assumes you
have $TOKEN (see Quickstart) and
$EXT_PID (the property's external_property_id).
1. Nightly inventory sync
Every hotel eventually needs a "belt-and-braces" nightly job that re-pushes the whole current-through-N-days window. Delta pushes during the day, full snapshot at 03:00 to cover races.
#!/usr/bin/env bash
set -euo pipefail
# 2-year window: legacy PMS v1 hard-caps here too. QC follows.
FROM=$(date -u -d "today" +%Y-%m-%d)
TO=$(date -u -d "+2 years" +%Y-%m-%d)
for room_id in 4211 4212 4213; do
KEY="nightly-$(date -u +%Y%m%d)-r${room_id}"
# Pull YOUR PMS's calendar for that room here (skipped for brevity)
# and build a `values[]` for that room across the window.
BODY=$(build_body "$room_id" "$FROM" "$TO")
curl -sS -X POST \
"https://api.quendoo.com/v1/qc/properties/$EXT_PID/inventory/availability" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
--data "$BODY"
# Pace against inventory-writes bucket β see /v1/qc/docs/rate-limits
sleep 0.5
done
Why the Idempotency-Key looks like that: replayable within 24 h,
distinct per (day, room). If the job crashes mid-run, restart:
already-shipped rooms replay their cached response, the rest post
fresh.
2. Push a new promo when the hotelier confirms a campaign
KEY=$(uuidgen)
curl -sS -X POST \
"https://api.quendoo.com/v1/qc/properties/$EXT_PID/promotions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{
"name": "Autumn 2027",
"type": "",
"discount_percent": 15,
"stay_dates_from": "2027-09-01",
"stay_dates_to": "2027-11-30",
"resv_from_time": "00:00",
"resv_to_time": "24:00"
}'
The response carries the created id; store it in your PMS if you
later want to PATCH or DELETE it. Skipping the second call and
posting a new promotion instead is the more common pattern β full
history in one place.
3. Pull today's arrivals, ack every revision
Poll every 60 s. If the feed is empty, the call is a cheap 200
against the reads bucket.
while true; do
RESP=$(curl -sS \
"https://api.quendoo.com/v1/qc/properties/$EXT_PID/bookings?revisions=1&checkin_from=$(date -u +%F)" \
-H "Authorization: Bearer $TOKEN")
echo "$RESP" | jq -r '.data.items[]' | while read -r rev; do
booking_id=$(echo "$rev" | jq -r '.booking_id')
revision_id=$(echo "$rev" | jq -r '.revision_id')
# Process the revision (write to your PMS)β¦
# β¦then ack:
curl -sS -X POST \
"https://api.quendoo.com/v1/qc/bookings/$booking_id/ack" \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: ack-$booking_id-$revision_id" \
-d "{\"revision_id\":\"$revision_id\"}"
done
sleep 60
done
Idempotency key ack-<booking>-<revision> is deliberate β a crash
between "wrote to PMS" and "acked to Quendoo" is safe to retry
because the ack replays instead of erroring.
4. Assign a physical room number when the hotelier picks one
Front-desk action in your PMS β one call.
curl -sS -X POST \
"https://api.quendoo.com/v1/qc/bookings/161345/room-assignment" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: assign-161345-4211-$(date -u +%Y%m%d)" \
-d '{
"booking_item_id": 4211,
"room_number": "217",
"self_checkin_code": "5810"
}'
Late reassignments overwrite β send again with the new
room_number, it replaces the previous one.
5. Sync your PMS's own catalogue at onboarding
One-time (repeat when your PMS renames a room):
KEY="catalogue-$EXT_PID-v$(date -u +%s)"
curl -sS -X PATCH \
"https://api.quendoo.com/v1/qc/properties/$EXT_PID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{
"partner_code": "PMS-HOTEL-4211",
"partner_name": "Hotel Panorama"
}'
for room in "PMS-STANDARD-QUEEN:Standard Queen" \
"PMS-DELUXE-KING:Deluxe King"; do
code=${room%%:*}; name=${room#*:}
curl -sS -X POST \
"https://api.quendoo.com/v1/qc/properties/$EXT_PID/room_types" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: room-$code" \
-d "{\"title\":\"$name\",\"count_of_rooms\":10,\"occ_adults\":2,\"kind\":\"room\",\"partner_code\":\"$code\"}"
done
After this, every subsequent URL segment {external_room_type_id}
accepts your PMS-STANDARD-QUEEN as the identifier β see the
bidirectional-id contract.
6. Verify a webhook you just received
See the full multi-language walkthrough on Webhooks. The four-step checklist:
X-Qc-Timestampwithin 5 minutes of now.- HMAC-SHA256 of
"<ts>.<raw body>"matchesX-Qc-Signature-256in constant time. X-Qc-Deliveryisn't one you already processed.X-Qc-Subscriptionis one of yours.
Only then 2xx.
7. Rotate a webhook signing secret without downtime
# Step 1 β mint the new secret. Old secret keeps validating for 24 h.
curl -sS -X POST \
"https://api.quendoo.com/v1/qc/subscriptions/$SUB_ID/rotate-secret" \
-H "Authorization: Bearer $TOKEN"
# β { "data": { "signing_secret": "whsec_new_β¦" } }
# Step 2 β deploy the new secret in your webhook handler.
# Verify against BOTH secrets for the overlap window.
# Step 3 β after 24 h, remove the old secret from your handler.
8. Debug a shadow-diff on a PMS v1 method
You saw a byte-diff row in
qc_v1_shadow_diffs.<method_key> (surfaced to you on request).
- If the diff is on
accommodation_nameforgetBookingOffers: check the property's locale β the port is English-only until the shadow-diff pass surfaces which other locales callers use. - If the diff is on
linkforgetBookingOffers: your property probably has a custom booking domain we're not resolving; open a support ticket with the diff row and we'll patchcustom_domains. - Every other diff on any method: open a support ticket with the row. Byte-parity is a promise; we own the fix.
9. Clean handoff to your PMS's own logs
Every 4xx/5xx we return can be joined against your logs by:
X-Qc-Request-Idon every response β unique per request, safe to log verbatim.X-Qc-Support-Ticketon any 5xx β a pre-opened ticket id you can quote to us.
Log both. When you file a support request, the ticket id lets us skip the "which request was it" step entirely.