Each finding says where it came from. Confirmed live means a real call against a real property proved it. Anything less certain says so. Property-specific values (IDs, payment method codes, product IDs) are examples from one property: look yours up again.
Where these findings come from
Base URLs and versions
Cloudbeds serves the same API under more than one host and version, and not every endpoint works everywhere. These are the combinations we used successfully:
The accounting API (
https://api.cloudbeds.com/accounting/v1.0, with getFolios, listFolioTransactions and /folios/configurations) authenticated with our key but returned empty results for our property: it wasn’t on the accounting ledger. Don’t count on it for folio work. See Folios and payments.
Authentication
Use a property API key as a Bearer token. There is no token exchange:- When you create an API key, Cloudbeds also shows a client ID, client secret and redirect URI. Those are for its OAuth 2.0 flow, for partner apps that need a user’s consent. Property-level calls don’t use them. Don’t go down the OAuth
/access_tokenpath: it’s a rabbit hole. - The
propertyIDcomes from the key. Read it withgetHotels. One key can see more than one property; ours saw one each. - The key carries scopes, and a missing one fails at the call. There is no
endpoint that lists what a key may do, so a scope you did not ask for is
discovered by the write that needs it being refused. Decide the full set when
you create the key — adding one later means issuing a new key and rotating it
everywhere. Writes we have needed:
write:rate(patchRate/putRate),write:reservation(postReservation),write:payment(postPayment). Reads have their ownread:*scopes. - Keys expire after 30 days without use. If a build sits idle between a rehearsal and a cutover, call it at least monthly.
- Writes post as the key’s name: custom items posted by an integration are grouped under a category with the key’s name (ours was “Jason Imports”). Name the key deliberately, because it shows up in folio configuration.
- Keep keys in Worker secrets. Never put them in a repo or in these docs.
Requests
-
Reads are
GETwith query parameters. -
Writes are form-encoded:
-
Arrays and objects must be PHP-style nested form fields, not JSON strings.
rooms[0][roomTypeID]=123&rooms[0][quantity]=1works, whilerooms=[{…}]is rejected. This applies topostReservation(rooms,adults,children),postCustomItem(items) andpatchRate/putRate(rates). A small encoder that walks the object and writeskey[i][field]=valuepairs covers all of them. -
Status changes use
PUT(putReservation).
Responses
Every response is wrapped in an envelope:"success": false is common: invalid status changes, refused checkouts and validation errors all come back as 200. Always check success in the body, not just the HTTP status. On failure, read message. Some errors also carry error_code and details; for example, MAX_ADULTS_EXCEEDED includes details.room_type_id.
A refusal can be a 200 that reads like an outage. A suspended property
answers reads with success: false and a message naming the problem — “the
property(ies) you are accessing have an invalid status … overdue: 24924”. A
client that checks only the HTTP status sees an empty but successful import, and
every downstream comparison then reports real bookings as missing. Read
message and surface it: the one sentence that explains the outage is in the
body.
Response shapes vary between endpoints and versions. getSources returns data as an array of arrays, one inner array per property the key can see. Some endpoints return reservationID at the top level and others under data, so read both.
Idempotency differs per endpoint, and it matters
There is no general idempotency header. Each write is its own case, and the difference decides what a client may safely retry:
Build the retry policy around that table rather than around the HTTP status. A
timeout means the same thing in every row; what differs is whether you may act
on it.
Rate limits
Cloudbeds allows 5 requests per second per property and can suspend credentials if you keep breaking that limit. Space every call at least 250 ms apart (about 4 per second) with one shared throttle, and never fire calls in parallel withPromise.all. In practice the throttle, not Cloudflare’s subrequest cap, limits batch size: migrating one booking took 6–12 calls.
Adding to this section
When a build teaches you something about Cloudbeds:- Add it to the page for that area, or start a new page under
api-reference/cloudbeds/and list it indocs.json. - Say how you know it: confirmed live (and roughly when), or what is still unconfirmed.
- Add your build to the table above.
- Keep keys, guest names and personal details out.