Response shape
How resources, lists, and relationships are structured in API responses.
Successful resource responses are application/json. The exceptions are
GET /reports/files/{id}, which streams the report export bytes
(application/csv or application/json, depending on the run's format), and
DELETE /bookings/{id}, which returns an empty 204. Resources are flat objects —
attributes sit at the top level alongside id and object. Request bodies
follow the same flat shape.
Resource objects
Each resource is led by two identifiers:
id— the prefixed public ID (e.g.acc_9fKbV3aQx7)object— a stable type string (e.g.accommodation,booking)
Everything else on the object is an attribute of that resource:
{
"id": "acc_9fKbV3aQx7",
"object": "accommodation",
"name": "Sunset Hotel Barcelona",
"state": "draft",
"created_by_app_installation": "inst_abc123"
}The object value is the same whether the resource arrives on its own or inside
a list, so you can branch on type without knowing where in the response tree
you found it.
List envelopes
Index endpoints and to-many relationships return a list envelope:
{
"object": "list",
"url": "/accommodations",
"has_more": true,
"next_cursor": "eyJpZCI6Mn0",
"data": [ /* … */ ]
}Only five index endpoints are cursor-paginated; the rest return the full result
set in one response. All use this envelope shape, so check
Pagination for which paths honour limit and
next_cursor. Paginated responses set has_more and next_cursor accordingly;
non-paginated index endpoints and inline lists always set has_more to false
and next_cursor to null.
Deleting things
A DELETE either hands you back the state you now need or hands you nothing,
depending on whether anything is left to describe:
| Request | Response |
|---|---|
DELETE /bookings/{id} | Empty 204 |
DELETE /bookings/{booking_id}/line-items/{id} | 200 with the booking the item was removed from |
DELETE /custom-fields/{id} | 200 with the deleted custom field |
DELETE /voucher-codes/{id} | 200 with the deleted voucher code |
Cancelling a booking returns nothing because there may be nothing left to
return: an unpaid booking is deleted outright, while a paid one moves to
cancelled. Removing a line item returns the whole booking so you can see the
remaining items and the new total without a follow-up request, and deleting a
custom field or a voucher code hands the record back one last time, since you
cannot fetch it again afterwards.
Relationships
Related data is either a reference you follow, or embedded inline.
To-one (belongs_to)
By default, a to-one relationship is the related record's prefixed ID:
{
"created_by_app_installation": "inst_abc123"
}When no standalone endpoint exists for the related record, the full object is embedded instead (see below).
To-many (has_many)
By default, a to-many relationship is a reference envelope — url points at
the endpoint that serves the collection; data and has_more are null until
you fetch that URL (or until client-controlled expansion ships):
{
"variants": {
"object": "list",
"url": "/accommodations/acc_9fKbV3aQx7/variants",
"has_more": null,
"next_cursor": null,
"data": null
}
}Fetch the url to load the collection. The response uses the same list
envelope shape as a top-level index.
Embedded collections
Some relationships have no endpoint of their own — booking line items are the
current example, being meaningless apart from their booking. These always arrive
embedded: data holds the items, has_more is false, and url is null:
{
"line_items": {
"object": "list",
"url": null,
"has_more": false,
"next_cursor": null,
"data": [
{
"id": "li_abc123",
"object": "line_item",
"variant_id": "expv_xyz789",
"quantity": 2
}
]
}
}Expansion (coming soon)
An expand query parameter will let you request embedded objects or
collections inline instead of following references. Until it ships, whether a
relationship is referenced or embedded is fixed per relationship in the
serializer — check the endpoint reference for the resource you are integrating
with.