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:

  1. X-Qc-Timestamp within 5 minutes of now.
  2. HMAC-SHA256 of "<ts>.<raw body>" matches X-Qc-Signature-256 in constant time.
  3. X-Qc-Delivery isn't one you already processed.
  4. X-Qc-Subscription is 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).

9. Clean handoff to your PMS's own logs

Every 4xx/5xx we return can be joined against your logs by:

Log both. When you file a support request, the ticket id lets us skip the "which request was it" step entirely.