Concepts
What is the envelope contract?
Every API response follows the same shape — success or failure. Agents never need to guess the response structure.
Every response from the API follows the envelope contract (ADR-0011). Agents can parse any response with the same logic.
Success envelope
{
"data": { "booking_id": "b_123", "confirmation_code": "HCX-A1B2C3", "state": "confirmed" },
"trace_id": "9f8e7d6c5b4a3210",
"next_actions": [
{ "rel": "get", "method": "GET", "href": "/v1/bookings/b_123" },
{ "rel": "cancel", "method": "POST", "href": "/v1/bookings/b_123/cancel" }
],
"expires_at": null,
"receipt": { "kind": "booking_confirmed", "subject": "b_123", "signature": "hmac-sha256:..." }
}| Field | Always present | Description |
|---|---|---|
data | Yes | The business payload |
trace_id | Yes | W3C trace ID |
next_actions | Yes | What the agent can do next |
expires_at | No | ISO datetime if the resource has a TTL |
receipt | No | HMAC-signed proof for high-value mutations |
Error envelope
{
"error": {
"code": "hold_expired",
"message": "Hold h_abc123 expired at 2026-04-16T10:15:00Z",
"detail": { "hold_id": "h_abc123", "expired_at": "2026-04-16T10:15:00Z" },
"remediation": "Create a new quote via POST /v1/quotes, then a new hold.",
"retry_after": null,
"docs_url": "https://docs.agenthotel.dev/errors#hold_expired",
"trace_id": "9f8e7d6c5b4a3210",
"next_actions": [{ "rel": "requote", "method": "POST", "href": "/v1/quotes" }]
}
}| Field | Description |
|---|---|
error.code | Stable identifier — match on this, not HTTP status |
error.detail | Structured data — IDs, states, amounts as typed fields |
error.remediation | Imperative instruction for the agent |
error.next_actions | Recovery calls the agent should make |
next_actions vocabulary
| Rel | Meaning |
|---|---|
search | Search for availability |
quote / requote | Create a new quote |
create_hold | Hold inventory against a quote |
confirm | Confirm a booking from a hold |
cancel | Cancel a booking |
release | Release a hold back to inventory |
retry | Retry the same operation |
get / get_audit | Retrieve the resource or audit trail |
How to process responses
if "data" in response:
process(response["data"])
else:
error = response["error"]
match error["code"]:
"hold_expired" → follow next_actions[rel=requote]
"payment_failed" → retry with new token
"rate_limited" → sleep(error["retry_after"])
_ → log(trace_id), read remediation, follow next_actionsKey rules:
- Match on
error.code, not HTTP status — multiple codes share 409. detailis structured — access fields directly, never regex the message.next_actionstells you what to call next — prefer over hardcoded URLs.