List holds
const url = 'https://api.useservice.app/v1/holds';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://api.useservice.app/v1/holds \ --header 'Authorization: Bearer <token>'Every hold the restaurant has that did not become a booking — live, released and
expired — newest first. Requires the reservations:read scope.
A hold that CONVERTED is absent, deliberately: it is a reservation now and it is in
GET /v1/reservations. Its hold id still resolves on GET /v1/holds/{id}, answering
status: "converted" and naming the booking.
Order: by when the hold was taken, newest first (created_at descending) — NOT by
service date or seating time.
Authorizations
Section intitulée « Authorizations »Parameters
Section intitulée « Parameters »Query Parameters
Section intitulée « Query Parameters »One of held, released, expired. converted is a 400 — those are reservations and are not listed here.
The SERVICE date held, YYYY-MM-DD. For a range use date[gte] and/or date[lte] instead. ?service_date= is not a filter and is a 400 naming it.
Service date on or after this day, YYYY-MM-DD. Combine with date[lte].
Service date on or before this day, YYYY-MM-DD.
How many objects to return: 1 to 100, default 20. A larger value is lowered to 100 rather than refused; 0, a negative number or anything that is not an integer is a 400.
The id of an object in this list — normally the last one on the page you have. Returns the objects that come after it, in the order the operation description gives. has_more: false means you have reached the end. An id this list cannot find is a 400.
The id of an object in this list — normally the first one on the page you have. Returns the objects that come just before it, walking the same order backwards; the page itself still reads in list order. Ignored when starting_after is also sent. An id this list cannot find is a 400.
Header Parameters
Section intitulée « Header Parameters »Pin this request to a dated version of the contract, e.g. 2026-09-01. Omit it and the version pinned on the API key applies. A version we do not publish is a 400 bad_request with param: "Service-Version". The version that was applied comes back on the Service-Version response header, always.
Responses
Section intitulée « Responses »Holds listed
object
A claim on a slot that occupies capacity without being a booking. It holds a real table, so it counts against availability for as long as it lives.
object
The object type: always hold.
Opaque hold id (hold_…). A hold and the reservation it becomes are the same row and share this id’s suffix, so hold_AbC converts into resv_AbC.
basket releases the table the instant expires_at passes; commitment keeps it until the sweep that runs every five minutes picks the hold up. Both lapse on their own; only the minutes between the deadline and the table reopening differ.
Lifecycle of the hold itself, not of a reservation.
held: live and occupying a table. The only state a conversion works from.expired: the deadline passed and the table is back on sale. On abaskethold this is reported from the momentexpires_atpasses, because that kind reopens its table at read time. On acommitmenthold the table is held past the deadline until the five-minute sweep resolves it, so the hold readsheldfor up to five minutes afterexpires_atandexpiredfrom then on. Either way it is the same lapse, andhold.expiredannounces it.released: handed back deliberately — you calledDELETE /v1/holds/{id}, or the restaurant released it from its own board.converted: it became the booking named inreservation.
How many guests the held seating is for.
The service day of the held seating, YYYY-MM-DD in the restaurant’s timezone. A seating after midnight keeps the date its service began on.
Start of the SEATING the hold covers — not the hold deadline. ISO 8601 with the restaurant’s UTC offset.
End of the seating the hold covers, in the same format. null when no seating duration applies.
When the hold lapses. Authoritative: the server sets it, clamping whatever was requested. Null once the hold is over.
When the hold was released or lapsed. Null while it is live, and null once it has converted — a converted hold did not end, it became the booking named in reservation.
The booking this hold became (resv_…), or null.
The tables the hold occupies, each with its seating area.
object
The object type: always table.
The table’s id, tbl_…. Opaque and stable.
The table’s name on the restaurant’s floor plan.
The smallest party the table is meant for. Always a number, never null.
The largest party the table seats on its own — a larger party needs tables joined together. Always a number, never null, and never below min_capacity.
The seating area the table is in.
object
The object type: always section.
Pass this as section_id to GET /v1/availability or to a create — any member of a group resolves to the whole group, so a sec_… read off a reservation’s table works without knowing which row it is.
The area’s name, in the restaurant’s primary_language.
The kind of space, as the restaurant classified it: indoor, outdoor, terrace,
bar, lounge, private_room (a room of its own), patio or rooftop. For a group,
take it from the row where group_id == id.
Whether THIS row is offered for online booking. Per row, never per group: it is deliberately not cascaded between group members, so a group is worth offering when ANY of its rows is true.
The sec_… id that identifies the conceptual seating area this row belongs to.
Rows sharing a group_id are ONE choice to a guest — the same area drawn on more than
one floor plan — so group by it before rendering a picker, or the same area is offered
twice.
Exactly one row per group has group_id == id: take that row’s name and
area_type as the group’s. The others are kept in step by the product and are
normally identical, but that is a behaviour of its editing paths rather than a
constraint, so the naming row is the answer that cannot go stale.
True when this hold does not count against the shift’s covers caps, with the same meaning as on a reservation: they neither charge it nor refuse it. It is carried onto the booking when the hold converts, so converting an exempt hold needs the reservations:write:override scope.
Your own reconciliation data, echoed verbatim. Carried onto the reservation when the hold converts. ⚠️ Readable by every integration this restaurant has authorised, not only by the one that wrote it: holds are scoped to the restaurant, so another of its API keys can read this hold and this object. Put reconciliation ids here, not commercial terms or anything you would not show a competitor.
object
When the hold was taken: ISO 8601 with the restaurant’s UTC offset.
When the hold last changed, in the same format.
Example
{ "object": "list", "data": [ { "object": "hold", "kind": "basket", "status": "held", "tables": [ { "object": "table", "section": { "object": "section", "area_type": "indoor" } } ] } ]}The Service-Version header names a version we do not publish
The envelope every public-API error answers with, whatever the status.
object
Every error response carries this one object.
object
The broad class of the error, from the HTTP status.
invalid_request_error: a400,403,404,409or422— something about the request to change.authentication_error: a401— the key is missing, invalid, revoked or expired.rate_limit_error: a429— slow down and retry.api_error: any other status, a fault on our side.
The specific error, stable and machine-readable, e.g. not_found. Branch on this, not on message.
A human-readable explanation for the developer. Its wording can change.
The request parameter the error is about, when there is one — e.g. expand or limit. Absent — not null — when the error names no field.
A link to this code in the errors reference. Always present.
Example
{ "error": { "type": "invalid_request_error" }}Unique id for this request. Quote it to support: it is how a specific call is found in our logs. Generated per request, or echoed from the X-Request-Id you sent.
No API key, or not a live one
The envelope every public-API error answers with, whatever the status.
object
Every error response carries this one object.
object
The broad class of the error, from the HTTP status.
invalid_request_error: a400,403,404,409or422— something about the request to change.authentication_error: a401— the key is missing, invalid, revoked or expired.rate_limit_error: a429— slow down and retry.api_error: any other status, a fault on our side.
The specific error, stable and machine-readable, e.g. not_found. Branch on this, not on message.
A human-readable explanation for the developer. Its wording can change.
The request parameter the error is about, when there is one — e.g. expand or limit. Absent — not null — when the error names no field.
A link to this code in the errors reference. Always present.
Example
{ "error": { "type": "invalid_request_error" }}Unique id for this request. Quote it to support: it is how a specific call is found in our logs. Generated per request, or echoed from the X-Request-Id you sent.
The API key does not carry the reservations:read scope
The envelope every public-API error answers with, whatever the status.
object
Every error response carries this one object.
object
The broad class of the error, from the HTTP status.
invalid_request_error: a400,403,404,409or422— something about the request to change.authentication_error: a401— the key is missing, invalid, revoked or expired.rate_limit_error: a429— slow down and retry.api_error: any other status, a fault on our side.
The specific error, stable and machine-readable, e.g. not_found. Branch on this, not on message.
A human-readable explanation for the developer. Its wording can change.
The request parameter the error is about, when there is one — e.g. expand or limit. Absent — not null — when the error names no field.
A link to this code in the errors reference. Always present.
Example
{ "error": { "type": "invalid_request_error" }}Unique id for this request. Quote it to support: it is how a specific call is found in our logs. Generated per request, or echoed from the X-Request-Id you sent.
Too many requests on the read budget
The envelope every public-API error answers with, whatever the status.
object
Every error response carries this one object.
object
The broad class of the error, from the HTTP status.
invalid_request_error: a400,403,404,409or422— something about the request to change.authentication_error: a401— the key is missing, invalid, revoked or expired.rate_limit_error: a429— slow down and retry.api_error: any other status, a fault on our side.
The specific error, stable and machine-readable, e.g. not_found. Branch on this, not on message.
A human-readable explanation for the developer. Its wording can change.
The request parameter the error is about, when there is one — e.g. expand or limit. Absent — not null — when the error names no field.
A link to this code in the errors reference. Always present.
Example
{ "error": { "type": "invalid_request_error" }}Requests permitted in the read burst
Requests remaining in the read burst
Seconds until the read bucket refills
Which budget the numbers describe. read here.
Seconds to wait before retrying
Unique id for this request. Quote it to support: it is how a specific call is found in our logs. Generated per request, or echoed from the X-Request-Id you sent.