API safety
Errors, retries, and side effects
Recovery is operation-specific. Create checkout may replay only after an ambiguous transport result or HTTP 502, 503, or 504, with the same key and byte-identical retained body. An indeterminate Confirm becomes GET-only and a submitted Cancel becomes GET-only while pending or unknown. Never turn a timeout into a second payment, booking, or cancellation.
Common error body
Every Agent API error uses one envelope. Branch on error.code, never English text. Keep error.requestId for support and observability. retryable=true means a bounded retry may be appropriate; the transaction rules below still take precedence.
{
"error": {
"code": "payment_not_authorized",
"message": "Payment must be authorized before confirming the booking",
"status": 400,
"requestId": "req_...",
"retryable": false,
"details": []
}
}Stable error codes
The message below is representative and useful for debugging; branch only on error.code. retryable is the value in the error envelope, not permission to repeat a write without the recovery rule.
| Code | HTTP | Representative message | Endpoint / scope | retryable | Caller action |
|---|---|---|---|---|---|
invalid_request | 400 / 422 | Request validation failed | Any endpoint with invalid fields | false | Correct the reported fields; do not repeat unchanged input. |
invalid_json | 400 | Body is not valid JSON | JSON POST endpoints | false | Send syntactically valid JSON. |
unsupported_media_type | 415 | Unsupported Media Type | JSON POST endpoints | false | Send Content-Type: application/json. |
payload_too_large | 413 | Request body is too large | Any endpoint | false | Reduce the request body before retrying. |
method_not_allowed | 405 | Method Not Allowed | Wrong method on an Agent API route | false | Use the method documented for that path. |
invalid_api_key | 401 | Invalid Agent API key | All authenticated endpoints | false | Replace, rotate, or reactivate the server-side key. |
forbidden | 403 | Agent Commerce connection is not configured | Application, environment, connection, or checkout authorization | false | Inspect capabilities and application configuration; do not bypass the boundary. |
agent_commerce_live_disabled | 403 | Live Agent Commerce has been disabled for this partner. Use a Test application or contact support. | Every Live request | false | Stop. Use Test, or wait until the workspace's Live program is enabled; never fall back to a different key or environment. |
agent_commerce_terms_not_approved | 403 | Live Agent Commerce is unavailable while the current Agent API Program Terms are pending approval. Use a Test application. | Every Live request | false | Stop. Live remains fail-closed until Brek approves the offered agreement version. |
agent_commerce_terms_not_current | 403 | Accept the current Agent API Program Terms before using Live Agent Commerce. | Every Live request | false | Stop. Read capabilities and have the workspace owner review and accept the current approved version. |
agent_commerce_terms_acceptance_missing | 403 | Recorded acceptance of the current Agent API Program Terms is required before using Live Agent Commerce. | Every Live request | false | Stop. An authorized workspace owner must accept the current approved agreement before Live can run. |
not_found | 404 | Cannot GET /api/v1/agent-commerce/... | Unknown Agent API route | false | Correct the endpoint path. |
quote_not_found_or_expired | 404 | Quote not found, expired, or owned by another application | Create checkout | false | Load current offers, create a new Quote, and obtain approval again. |
checkout_not_found | 404 | Checkout session not found | Retrieve checkout or Confirm | false | Verify the application-scoped checkout ID; do not guess another identifier. |
booking_not_found | 404 | Booking not found | Retrieve, preview, or cancel booking | false | Verify the application-scoped bookingReference. |
invalid_stay_dates | 400 | checkIn and checkOut must be valid YYYY-MM-DD dates, and checkOut must be after checkIn | Search, hotel offers, or Quote | false | Send valid future ISO dates with checkOut after checkIn; do not repeat unchanged input. |
agent_commerce_invalid_occupancy | 400 | Exact-date search supports 1 to 3 rooms. | Exact Search | false | Send 1–3 rooms. Every room must have 1–8 adults age 18–120 and may have 0–8 children age 0–17; the full stay may have at most 10 adults and 10 children. |
agent_commerce_invalid_flexible_dates | 400 | At least one check-in date from destination-local today through 30 days from today is required. | Flexible search | false | Use an accepted preset or explicit date inside the published destination-local window. |
agent_commerce_invalid_flexible_occupancy | 400 | Flexible dates currently supports one room with 1–2 adults. Choose exact dates for larger groups or children. | Flexible search | false | Use one room with one or two adults, or switch to exact-date Search. |
flexible_search_unavailable | 503 | Flexible search is temporarily unavailable because the Brek cache-only inventory endpoint could not be reached. Use exact-date search and retry flexible search later. | Flexible search | true | Use exact-date Search now. Retry flexible discovery later with bounded backoff; it has no transaction side effect. |
agent_commerce_search_unavailable | 503 | The Brek retail search is temporarily unavailable. Retry this exact search before showing prices. | Exact Search | true | Retry the same read with bounded backoff. Do not show stale, partial, or inferred prices. |
agent_commerce_identity_bridge_unavailable | 503 | The Brek inventory identity bridge is temporarily unavailable. This request is safe to retry with backoff. | Exact Search | true | Retry the same read with bounded backoff. Never infer or substitute an MCP, consumer, provider, or source property identifier. |
agent_commerce_public_provider_unsupported | 400 | Only Expedia.com and Booking.com are supported. | Resolve public redirect | false | Use the exact provider and request body from a current resolve_public_redirect action; never rename or invent a provider. |
agent_commerce_public_redirect_unavailable | 400 | The selected public provider did not return a trusted HTTPS destination. Search again before redirecting the traveler. | Resolve public redirect | false | Discard the old action and Search again. Never construct, repair, or guess an external provider URL. |
idempotency_key_required | 400 | Idempotency-Key header is required and must be at least 8 characters | Create checkout, Confirm, Cancel | false | Add a unique key for this logical write. |
idempotency_conflict | 409 | Idempotency-Key was already used with different input | Create checkout, Confirm, Cancel | false | Retrieve current state. Never rotate the key to force a duplicate operation. |
quote_checkout_conflict | 409 | This Quote is already reserved for a checkout. Reuse the original create request to recover it, or create a fresh Quote for another checkout. | Create checkout | false | Recover the existing checkout identified in error.details, or obtain a fresh Quote. Never rotate the key or alter the body to create a second checkout from this Quote. |
checkout_abandon_not_allowed | 409 | Test checkout cannot be abandoned from its current status | Abandon Test checkout | false | Retrieve the same checkout and follow its typed next action. Never use Abandon after Confirm was attempted, never submit another booking write, and never create an automatic replacement checkout from the same Quote. |
payment_not_authorized | 400 | Payment must be authorized before confirming the booking | Confirm | false | Retrieve checkout and finish hosted or Stripe.js authorization. |
checkout_expired | 400 | Checkout session has expired | Payment authorization or Confirm | false | Create a fresh Quote and checkout; do not reuse the expired session. |
checkout_expiry_payment_release_pending | 409 | Checkout expiry is being reconciled and payment-hold release is not yet confirmed. | Confirm after checkout expiry | false | GET the same checkout and follow expiration.nextAction. Do not Confirm or create another checkout from this Quote. Only payment.status=voided with expiration.phase=voided proves release. |
state_conflict | 409 | Checkout session cannot be changed from its current status | Checkout, Confirm, preview, or Cancel state transition | false | Retrieve the resource and follow its current state; do not force another write. |
confirmation_in_progress | 409 | Checkout session is already being confirmed | Confirm | false | Poll the existing checkout instead of submitting another confirmation. |
confirmation_payment_pending | 409 | The hotel is booked but payment capture has not settled. Retrieve the checkout; do not create another booking. | Confirm after supplier success | false | GET-poll the same checkout. Never start another checkout or hotel booking. |
confirmation_outcome_unknown | 409 | The supplier booking outcome is not yet known. Do not retry with a new key; retrieve the checkout and contact Brek support. | Confirm after an ambiguous supplier outcome | false | Stop all booking writes, GET-poll the checkout, and contact support. |
confirmation_failed | 409 | The supplier booking failed. Retrieve the checkout for its terminal state. | Confirm recovery | false | Retrieve the failed checkout. Start a new Quote only after showing the traveler that no booking was created. |
confirmation_preflight_failed | 400 | Booking validation failed before supplier dispatch. Payment authorization was released; retrieve this checkout for its terminal state. | Confirm before any supplier dispatch | false | GET the original checkout and require status=failed and payment.status=voided. Do not retry Confirm or reuse this Quote; a new attempt needs a fresh Quote and traveler approval. |
confirmation_payment_void_pending | 409 | The supplier booking failed, but the payment release outcome is not yet known. Retrieve this checkout; do not create another booking. | Confirm or GET recovery after proven booking failure | false | GET-poll only the original checkout while confirmation.nextAction.type=poll_checkout. Payment release is proven only by payment.status=voided; never resubmit Confirm. |
invalid_approval_attestation | 400 | Confirmation requires fresh human approval bound to this checkout's exact approvalReceipt fingerprint | Confirm | false | Stop. Re-present the current approval receipt, obtain fresh human approval, and submit a correctly bound attestation only if the checkout remains payment_authorized. |
quote_expired_requote_required | 409 | The approved quote expired or changed. Its payment hold was released; quote the room again and obtain fresh traveler approval. | Confirm before supplier execution | false | Do not Confirm again. Create a new Quote and checkout, present the current terms, and obtain fresh payment and human approval. |
quote_expired_payment_release_pending | 409 | The approved quote expired or changed. Payment-hold release is not yet confirmed. | Confirm before supplier execution | false | GET the same checkout and follow expiration.nextAction. Do not Confirm or create another checkout from this Quote until Brek proves release or support resolves it. |
cancellation_approval_required | 403 | Create a cancellation preview, obtain human approval, and pass its short-lived approvalToken | Live Cancel | false | Preview, present exact consequences, obtain human approval, then send the new token. |
cancellation_human_approval_required | 403 | Live cancellation requires fresh human approval bound to the exact cancellation preview fingerprint | Live Cancel | false | Discard the attempted write; create a fresh preview, present its consequences, obtain human approval, and bind the attestation to previewFingerprint. |
invalid_cancellation_approval | 403 | Cancellation approval token is invalid. Create a fresh preview and obtain approval again. | Live Cancel | false | Discard the token; re-preview, re-present, and obtain approval again. |
cancellation_preview_expired | 409 | Cancellation preview expired. Create a fresh preview and obtain approval again. | Live Cancel | false | Discard the expired token; re-preview, re-present, and re-approve. |
cancellation_preview_stale | 409 | Cancellation consequences changed. Create a fresh preview and obtain approval again. | Live Cancel, before supplier execution | false | Show the changed refund and penalty, then obtain new approval before a new Cancel. |
cancellation_approval_unavailable | 503 | Live cancellation approval signing is not configured | Live preview or Cancel | false | Stop. Do not attempt cancellation until Brek restores approval signing. |
cancellation_approval_release_failed | 409 | Cancellation was not sent to the supplier, but the local claim could not be released. Retrieve the booking before retrying. | Live Cancel, before supplier execution | false | A new Cancel attempt is permitted only if the booking is still confirmed, after a fresh preview, consequence presentation, and human approval. |
cancellation_in_progress | 409 | Booking cancellation is in progress. Do not retry; retrieve the booking to poll its status. | Cancel | false | GET-poll the booking and follow cancellation.nextAction; never POST Cancel again. |
cancellation_requires_attention | 409 | Booking cancellation requires attention. Do not retry; retrieve the booking and contact Brek support. | Cancel replay or concurrent Cancel | false | Stop cancellation writes, retrieve the booking, and contact support. |
cancellation_outcome_unknown | 409 | The supplier cancellation outcome is unknown. Do not retry cancellation; retrieve the booking and contact Brek support. | Cancel after an ambiguous supplier outcome | false | Never retry Cancel. GET-poll the booking, then contact support. |
rate_limited | 429 | Too Many Requests | Per-application request window | true | Honor Retry-After and retry with bounded jitter. |
capability_not_enabled | 403 / 503 | Checkout mode 'native' is disabled for this application | Disabled payment, supplier, refund, or application capability | false | Stop and re-read capabilities; do not cross a disabled boundary. |
service_unavailable | 503 | The required Agent Commerce service is unavailable | Payment, supplier, refund, checkout, or preview dependency | false | For Create checkout only, this 503 permits recovery with the same key and byte-identical retained body. Otherwise follow an operation-specific zero-side-effect rule; Confirm and submitted Cancel recover with GET only. |
internal_error | 500–599 | The Agent API could not complete the request | Unexpected server failure | read-dependent | Retry reads, Search, offers, or Quote with bounded backoff. Confirm and submitted Cancel recover with GET only. For Create checkout, replay only after an ambiguous transport result or HTTP 502, 503, or 504, with the same key and byte-identical retained body. |
What to do by status
400Correct invalid input only where that operation's contract permits another attempt. If payment state changed outside Brek, retrieve checkout state; the Confirm and Cancel recovery rules below take precedence.
401The key is missing or invalid. Replace it before retrying.
403The application, environment, or checkout mode does not allow the operation.
404The resource is absent or outside this application's scope.
409Retrieve current state. Never rotate the Idempotency-Key to force a second transaction.
429Wait for Retry-After, then retry with bounded backoff.
503The required payment, supplier, or refund path is unavailable. For Create checkout only, recover with the same key and byte-identical retained body; if an identifier was returned, retrieve current state. Other writes follow their GET-only or stop rule.
5xx readFor Search and GET requests, use bounded exponential backoff with jitter.
5xx writeCreate checkout may replay only after HTTP 502, 503, or 504, with the same key and byte-identical retained body; a different 5xx is not replay permission. An indeterminate Confirm becomes GET-only. A submitted Cancel becomes GET-only while pending or unknown unless an operation-specific error explicitly proves supplier cancellation was not attempted.
Operation safety matrix
| Operation | Method | Side effect | Retry rule |
|---|---|---|---|
| Capabilities | GET | None | Safe |
| Search hotels | POST | None | Safe |
| Flexible search | POST | None | Safe |
| Hotel room offers | POST | None | Safe |
| Resolve public redirect | POST | Read-only provider recheck; creates no Brek transaction | Safe as a fresh read; use only when the returned public-rate action requires it |
| Quote | POST | Creates a short-lived quote; no payment or booking | Safe; create a new quote after expiry |
| Create checkout | POST | Creates a payment session | One checkout per Quote. Only after an ambiguous transport result or HTTP 502, 503, or 504, replay the same Idempotency-Key with the byte-identical retained body; changed input conflicts and a different checkout requires a fresh Quote |
| Authorize payment | Stripe.js / hosted | Changes payment state outside the Brek API | Retrieve checkout before asking the traveler to retry |
| Abandon Test checkout | POST | Releases an unconfirmed Stripe Test authorization and makes the checkout terminal | Use only before Confirm. Repeat the same bodyless operation or GET the same checkout until terminal void proof is returned; never use it in Live |
| Confirm booking | POST | May capture payment and book a hotel | After an indeterminate response or typed confirmation 409, GET-poll only the original checkout and never submit another booking write |
| Retrieve checkout or booking | GET | None | Safe |
| Preview cancellation | GET | None; calculates current consequences and may issue a sensitive five-minute Live approval token | Safe to repeat. Never log or expose the token; use only the newest human-approved preview |
| Cancel booking | POST | May cancel and refund | Preview, present consequences, and obtain approval before the first submission. After submission, use GET only while pending or unknown. A new attempt is allowed only for an explicitly documented pre-supplier failure, after a fresh preview, presentation, and approval |
Idempotency rules
- Create checkout, confirm, and cancel require an
Idempotency-Keywhose trimmed length is at least eight characters. That is the current published input constraint; no maximum length or restricted character set is established. For portability, use a conservative ASCII UUID-like token with no leading or trailing whitespace, and retain its exact bytes. - An idempotency key identifies one logical write; it does not make a replay safe. Follow the operation-specific recovery rule below.
- Submit Create checkout serially and retain the exact serialized request body before the first attempt. The key alone is not permission to send a second request.
- A Quote can own exactly one checkout. A different logical create for that Quote returns
409 quote_checkout_conflict. Read the typeddetails[].checkoutSessionIdand matchingdetails[].nextAction.type=get_checkout, then retrieve that checkout. Never parse the humanreasontext. If the typed identifier is absent, stop or obtain a fresh Quote before creating another checkout. - Only after an ambiguous transport result or HTTP
502,503, or504may trusted code recover Create checkout with the same key and the byte-identical retained body. A changed body returns409 idempotency_conflict. v1 does not return a replay header; compare the checkout identifier and retrieve current state. - There is no universal replay-retention promise. Lookup scope and recovery lifetime depend on the Quote, checkout session, booking, and operation. Key rotation inside the same application does not bypass those rules. Do not rely on replay after a checkout expires.
- After an indeterminate Confirm response or typed confirmation 409, make only GET requests for the original checkout. Never issue another booking write, even with the same key.
- When checkout status is
expiry_requires_attention, followexpiration.nextAction. Do not Confirm or create another checkout from the same Quote. Onlypayment.status=voidedtogether withexpiration.phase=voidedproves the hold was released. - Once Cancel is submitted, use GET booking only while its outcome is pending or unknown. Never resubmit it, even with the same key.
- Cancellation has a stricter recovery rule. When the booking is
cancellation_in_progress, GET-poll the existing booking and do not call Cancel again—even with the same key. When it iscancellation_requires_attention, or whencancellation.outcome=unknown, stop all cancellation writes and contact support. - There is no published timeout after which a cancellation becomes safe to resubmit. If an in-progress state does not change, treat it as stale and escalate with the redacted booking reference and request ID.
- Only an error explicitly documented as occurring before supplier cancellation may start a new Cancel attempt. Create a fresh preview, present the current consequences, and obtain fresh human approval first; if the operation-specific error says to stop, stop.
Idempotency does not replace traveler approval
Use authenticated support without sending secrets
contact_support, stop writes and open authenticated Partner Console support. Initially share the request ID, error code, UTC timestamp, environment, and only the minimum redacted identifier suffix needed. Never send an API key, Authorization header, signed hosted-checkout URL or token, Stripe client secret, card data, traveler PII, model prompt, or full response body. If support needs an exact opaque checkout or booking reference, enter it only in the authenticated form's designated field.