We've spent two weeks integrating a companion service against the API (client-credentials service account, migration tooling, a scheduler) and hit a series of request-shape asymmetries, each costing a failed run before the shape was clear. Cataloguing them in one place — partly as a docs suggestion, partly because a couple look unintentional. All measured against current main on a live deployment.
propertyId: query on reads, body on writes. GETs accept ?propertyId=; POST /rooms/types, POST /rate-plans, POST /reservations/:id/notes validate it in the body and answer 400 propertyId must be a UUID when it's only in the query — which reads as "your UUID is malformed" rather than "wrong location". A dry run of reads proves nothing about the writes.
PATCH /reservations/:id/cancel rejects a body (property reason should not exist). A cancellation reason feels like the natural payload; if it's deliberately unsupported, a hint in the 400 would save the round-trip.
PATCH /reservations/:id rejects source — probably deliberate (provenance shouldn't be editable?) but undocumented.
- Read envelopes differ per resource: lists unwrap under
data, GET /reservations/:id under reservation, GET /reservations/:id/notes under notes (+ activeCount). A client has to know each key; a consistent envelope (or documenting the exceptions) would remove a silent-failure class — we shipped a bug where iterating the notes dict's keys silently emptied a dedup check.
- List endpoints default to
limit=20 with no indication of truncation in the payload shape a naive client reads. Fine with pagination docs front-and-centre; painful when a "list all" consumer silently gets page 1 (our fault, but a total surfaced more prominently would have caught it sooner).
- No native home for an external reservation reference on direct create/import.
bookings.external_confirmation exists and the channel-inbound path populates it, but CreateReservationDto silently drops unknown fields, so migration tooling has nowhere to store the source system's reference — we ended up carrying it in notes text. Accepting externalConfirmation on direct create/import would give integrators id-based idempotency. Happy to PR this one if you'd take it.
Related PRs already open from us: #317 (azp allow-list), #318 (ical roles), #319 (room-type move), #320 (rate-plan deactivate).
We've spent two weeks integrating a companion service against the API (client-credentials service account, migration tooling, a scheduler) and hit a series of request-shape asymmetries, each costing a failed run before the shape was clear. Cataloguing them in one place — partly as a docs suggestion, partly because a couple look unintentional. All measured against current main on a live deployment.
propertyId: query on reads, body on writes. GETs accept?propertyId=;POST /rooms/types,POST /rate-plans,POST /reservations/:id/notesvalidate it in the body and answer400 propertyId must be a UUIDwhen it's only in the query — which reads as "your UUID is malformed" rather than "wrong location". A dry run of reads proves nothing about the writes.PATCH /reservations/:id/cancelrejects a body (property reason should not exist). A cancellation reason feels like the natural payload; if it's deliberately unsupported, a hint in the 400 would save the round-trip.PATCH /reservations/:idrejectssource— probably deliberate (provenance shouldn't be editable?) but undocumented.data,GET /reservations/:idunderreservation,GET /reservations/:id/notesundernotes(+activeCount). A client has to know each key; a consistent envelope (or documenting the exceptions) would remove a silent-failure class — we shipped a bug where iterating the notes dict's keys silently emptied a dedup check.limit=20with no indication of truncation in the payload shape a naive client reads. Fine with pagination docs front-and-centre; painful when a "list all" consumer silently gets page 1 (our fault, but atotalsurfaced more prominently would have caught it sooner).bookings.external_confirmationexists and the channel-inbound path populates it, butCreateReservationDtosilently drops unknown fields, so migration tooling has nowhere to store the source system's reference — we ended up carrying it in notes text. AcceptingexternalConfirmationon direct create/import would give integrators id-based idempotency. Happy to PR this one if you'd take it.Related PRs already open from us: #317 (azp allow-list), #318 (ical roles), #319 (room-type move), #320 (rate-plan deactivate).