Errors, idempotency and pagination
The conventions to build against before you scale up a Partner API integration.
These three conventions are consistent across the whole Partner API. Getting them right once, in a shared client, is far less work than handling them per endpoint.
Errors
Every error uses the same envelope:
{
"type": "validation_error",
"timestamp": "2026-10-09T18:22:41.218Z",
"code": "invalid_parameter",
"message": "Human-readable summary.",
"details": {},
"errors": []
}type, timestamp, code and message are always present. details and
errors[] are optional.
Branch on code, never on message. Messages are written for humans and
can change. In production, 5xx messages are deliberately masked, so there is
nothing useful to parse there either.
What the statuses mean
| Status | Meaning |
|---|---|
| 400 | Validation failed, or an Idempotency-Key longer than 255 characters |
| 401 | Missing, invalid, expired or revoked credential |
| 403 | Authenticated, but missing a required scope — or the wrong key level |
| 404 | Not found, or outside your organization or venue reach |
| 409 | Idempotency conflict (see below) |
| 413 | Upload above 10 MB |
| 415 | Unsupported upload type |
| 429 | Rate limited — back off and retry |
404 is doing two jobs
Out-of-scope resources return 404, not 403. This is deliberate: a caller cannot use the error code to discover whether a reservation exists at a property they cannot reach.
The practical consequence is that a 404 does not prove something was deleted. If you are reconciling, treat 404 as "not visible to me" rather than "gone".
The one exception: passing propertyIds outside your scope on the reservations
list returns 403 rather than filtering silently.
Idempotency
Idempotency-KeyheaderstringOptional. Supported on POST, PUT, PATCH and DELETE under /partner.
Maximum 255 characters.
Send one on every write. On a replay you get the stored response back, with:
X-Idempotent-Replay: trueWhat counts as "the same request"
Method, path, sorted query string and canonical body. Two details matter:
- JSON key order is ignored — reserialising your body will not break a replay.
- Array order matters —
[a, b]and[b, a]are different requests. If you build arrays from a set or a map, sort them before sending, or your retry will be rejected as a conflict.
Conflicts
You get a 409 when:
- the same key is reused with a different request, or
- the same key is sent while the first request is still in flight.
An in-flight request holds its lease for a few minutes. If a request times out on your side, retrying with the same key shortly afterwards may legitimately return 409 — wait and retry rather than generating a new key, which would risk performing the operation twice.
Keys are scoped to your tenant, so they cannot collide with another partner's.
The Idempotency-Key header is not currently declared in the OpenAPI
document, so you will not see it in the generated reference. It is supported
regardless, as described here.
Already-idempotent operations
Some endpoints are naturally safe to repeat: acknowledging an arrival, and adding codes to a pool (duplicates are reported rather than failing). Send a key anyway for the replay semantics.
Pagination
Two styles, depending on the endpoint.
Offset
The default: limit (1–100) with offset. Fine for small, bounded lists like
venues or programs.
Keyset cursor
GET /v1/partner/reservations supports a cursor, and you should use it for
anything large:
- Send
cursor, readnextCursor, repeat untilnextCursoris null. - Ordered by check-in date, then external reservation id.
- A cursor overrides
offset; the response echoesoffset: 0.
A malformed cursor is ignored, not rejected. The request succeeds and returns the first page. A paging loop that mangles its cursor will therefore run forever over page one rather than erroring. Assert that each page advances, and cap the number of iterations.
Rate limits and backoff
Limits exist at both the per-key and edge layers, and a few individual routes carry their own — custom-domain recheck and retry, and the public guest-invite send.
Rather than coding to specific numbers, which can change:
- treat 429 as a normal condition under load, not an error to alert on;
- retry with exponential backoff and jitter;
- keep a concurrency ceiling in your client, so a burst of work does not turn into a burst of requests;
- remember that most data changes on a multi-hour cycle, so polling faster rarely gets you anything. See how the data flows.