Wire conventions β€” the shapes every QC response follows

One page for the rules every QC endpoint applies to its response. Read this once, then you know how to interpret every list, every name, every money value, and every filter across all endpoints.

Envelope

Every response has this outer shape:

{
  "status": "ok",           // or "error"
  "message": "ok",          // or an error translation key
  "data": { … }
}

Single-item read/write. data IS the resource object, flat:

{"status": "ok", "message": "ok", "data": {"external_property_id": "…", "name": "…", …}}

List read. data holds a plural-keyed array + a meta block:

{
  "status": "ok",
  "message": "ok",
  "data": {
    "bookings": [ … ],
    "meta": {"total": 4231, "page": 1, "per_page": 50, "last_page": 85}
  }
}

The plural key names the resource (bookings, properties, room_types, rate_plans, webhook_deliveries, …). Never data.data.

Identifiers

Every resource that can be addressed by an external id emits it under a canonical external_<resource>_id key AND a back-compat id alias with the same value:

{"external_property_id": "059020b9-…", "id": "059020b9-…", …}

Internal Quendoo primary keys (property_id, room_id, rate_id, …) are NEVER exposed on the wire. The partner side never sees them.

Names and translations

Every named entity (property, room type, rate plan, extra, promotion, cancellation policy, payment policy, special offer) emits:

Some entities carry additional translated fields (description_tr, title_tr, text_tr) β€” see the resource schemas in the reference.

Money

Every monetary field is {amount, currency}, never a bare float:

{"total_price": {"amount": 234.5, "currency": "EUR"}}

Booking rows carry a top-level currency field as a convenience for dashboards that render one currency badge per booking, but every per-line-item price ALSO carries its own currency in the wrapped shape. A partner storing lines in their own DB doesn't have to reach outside the row to know what a number means.

Pagination

Every list endpoint that can grow past ~100 rows takes ?page=N&limit=M and emits a meta block:

"meta": {"total": 4231, "page": 1, "per_page": 50, "last_page": 85}

Some endpoints use CURSOR pagination instead β€” the booking revisions feed is the canonical example (?since=<revision_id> returns the next batch after a known point, with next_cursor in the response). Cursor endpoints do NOT carry the meta block; they carry next_cursor and has_more. Each concept page names which pattern its endpoint uses.

Filters

Filter conventions apply across every endpoint that accepts them:

Every list endpoint's concept page names its supported filters in a table with types + notes.

Statuses & lifecycles

Two orthogonal status axes on Booking:

Errors

Errors share the envelope with status: "error":

{"status": "error", "message": "errors.qc.unknown_room_type"}

PII

Guest-level fields (guest_name, first_name, last_name, email, phone) are MASKED on QC responses even when the underlying dashboard would show them unmasked to the hotelier. The reasoning: QC tokens are held by partner PMSes, not the account holder. See Auth concepts for the full masking rules.

Idempotency

Every write endpoint honours Idempotency-Key: <uuid> on the request. See Idempotency for the contract.

Rate limits

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. See Rate limits.