Quickstart
Five minutes from zero to your first successful call. Everything
here runs against staging (https://staging-api.quendoo.com/v1/qc).
0. Get a Bearer token
Two ways to get one — pick the one that matches your role. Both
result in a token you send as Authorization: Bearer <token> on
every subsequent call, so the rest of this page is identical after
this step. Details on both flows on the
Auth concepts page.
0a. Dashboard-minted token (hoteliers, agencies, quick tests)
Open the Quendoo dashboard → Pem systems → Quendoo Connect →
API tokens → Add. Pick your scopes, properties and endpoint
scopes; the token (a qc_… string) is shown once — copy it
before closing the dialog. Use it directly:
export TOKEN="qc_………"
No OAuth exchange, no expiry (unless you set one). This is what you want if you are testing the API from the interactive reference.
0b. OAuth 2.0 client credentials (partner PMS/CM)
Requires a client_id / client_secret provisioned by Quendoo —
contact [email protected]. There is no self-service form,
and the dashboard tokens page does NOT create these.
curl -X POST https://staging-api.quendoo.com/v1/qc/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$QC_CLIENT_ID" \
-d "client_secret=$QC_CLIENT_SECRET"
{
"access_token": "eyJhbGciOiJSUzI1NiJ9…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "properties.read inventory.write bookings.read"
}
Save the access_token. It is a JWT that lasts one hour —
refresh from the same call proactively.
1. Read a property — this is where external_property_id comes from
Every property-scoped endpoint on QC is addressed by
{external_property_id} (/properties/{external_property_id}/room_types,
/properties/{external_property_id}/inventory/*, etc). You get one
from the top-level properties list:
curl -H "Authorization: Bearer $TOKEN" \
https://staging-api.quendoo.com/v1/qc/properties
You get every property the caller can touch — copy the
external_property_id of the one you want to work with:
{
"status": "ok",
"message": "ok",
"data": {
"properties": [
{ "external_property_id": "01J2XZ0K…", "name": "Sunrise Test Hotel 1", "mode": "shadow" }
]
}
}
Getting an empty list is not a bug — it means the caller has no
enrolled properties yet. Enrol one from the dashboard under Pem
systems → Quendoo Connect → App Store, or from the API with
POST /v1/qc/properties.
On the interactive reference: paste the
value into the external_property_id variable field on any
property-scoped operation and hit Send.
export EXT_PID="01J2XZ0K…"
2. Find a room type
Inventory is written per room type, addressed by its
external_room_type_id:
curl -H "Authorization: Bearer $TOKEN" \
https://staging-api.quendoo.com/v1/qc/properties/$EXT_PID/room_types
Copy the external_room_type_id of one of them:
export ROOM_TYPE="98a87689-…"
3. Push some inventory
You send the number of free rooms for a date range (both ends inclusive):
curl -X POST https://staging-api.quendoo.com/v1/qc/properties/$EXT_PID/inventory/availability \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"values": [
{ "room_type_id": "'"$ROOM_TYPE"'", "date_from": "2027-02-14", "date_to": "2027-02-14", "availability": 3 }
]
}'
{ "status": "ok", "message": "accepted",
"data": { "written": 0, "unchanged": 0, "skipped_out_of_window": 0, "dry": true, "warnings": [] } }
A 202. "dry": true and all-zero counters mean the property is still in
shadow: the body was validated, nothing was written and nothing was
counted (the mode gate). Re-run the
identical curl → same body, plus Idempotency-Replayed: true in the response
headers. The same key with a different body is a 409. That's the core
write contract in one call.
4. What now
- Full walkthrough of every surface — Integration guide with a fictional Hotel Panorama running through it end to end.
- The domain first — Concepts: the property model, pricing, inventory writes, bookings feed, configuration surfaces, auth, webhooks.
- Errors — every key we can return, its cause, and its fix: Error dictionary.
- Coming from PMS v1? The mapping is on the migration page.
Common first mistakes
- HTTP 401. The token is missing, mistyped, expired or revoked.
- HTTP 403
errors.qc.insufficient_scope. The token does not carry the scope this endpoint needs (the analytics endpoints needanalytics.read, for example). Re-issue it with the scope, or ask us to widen the OAuth client. - HTTP 404 on
/properties/{ext}/…. The client's granted property set does not include this one.GET /propertiesreturns only what the client can see; anything else is 404. - HTTP 409
errors.qc.idempotency_conflict. You reused anIdempotency-Keywith a different body. Mint a fresh key per logical operation, replay only with the identical body. See Idempotency deep-dive. - A 422 with
errors[]. The request body failed validation. Every message is a stable machine key (error dictionary); the FE can render it in the user's locale via the JSON dictionaries.