Appearance
Checkout & booking
Five UCP checkout tools are registered when the storefront takes instant bookings (the profile advertises dev.ucp.shopping.checkout):
| Tool | Does |
|---|---|
create_checkout | Price a stay into a session: checkout.line_items + optional checkout.buyer. |
get_checkout | Fetch current session state by id. |
update_checkout | Replace line_items and/or buyer (per-key PUT semantics); reprices. |
complete_checkout | Place the held booking. Requires meta["idempotency-key"]. |
cancel_checkout | Cancel an open session. Requires meta["idempotency-key"]. |
json
{
"meta": { "ucp-agent": { "profile": "…" } },
"checkout": {
"line_items": [{ "item": { "id": "stay:42:2026-08-03:2026-08-10:2:0" }, "quantity": 1 }],
"buyer": { "first_name": "Ada", "last_name": "Lovelace", "email": "[email protected]", "phone_number": "+359888123456" }
}
}The session
Responses are the full UCP checkout resource: id (pass it to the other tools), status, currency, line_items, buyer, totals, messages, links (the storefront's policy pages), and — once completed — order and continue_url.
Statuses: incomplete → ready_for_complete → complete_in_progress → completed, or canceled (explicit cancel, or 24 h of inactivity). requires_escalation is reserved and unused in v1.
Buyer requirements: non-empty first_name, last_name, and a valid email. phone_number is optional. Until those are present the session stays incomplete with a missing message at $.buyer.
Totals
totals is the itemized price in minor units with signed amounts: subtotal (accommodation), optional negative discount, one fee entry per mandatory fee, one tax entry per added tax (taxes already included in the nightly rate are not added again), and total. The non-total entries always sum exactly to total — render them as given, don't recompute.
Messages
Problems come back as structured messages, not tool errors:
code | Typical cause | severity |
|---|---|---|
out_of_stock | Dates no longer available (also on completion races) | recoverable — try other dates |
item_unavailable | Unknown/inactive item id | recoverable |
capacity_exceeded | Party larger than max_guests × quantity | requires_buyer_input |
missing | Buyer fields absent/invalid | requires_buyer_input |
min_stay, max_stay, closed_to_arrival, closed_to_departure, stop_sell | Stay restrictions | recoverable |
invalid | Mixed dates across lines; mutating a finished session | recoverable / unrecoverable |
Tool-level errors are reserved for transport problems: unknown checkout id, missing idempotency-key, malformed arguments, or calling a checkout tool on a storefront that doesn't register them.
Completing
complete_checkout re-validates availability under lock, creates the booking, and returns the session with:
json
{
"status": "completed",
"order": {
"id": "BKG-7F3A2C",
"checkout_id": "9c2e…",
"permalink_url": "https://stay.example.com/pay/status/eyJpdiI6…"
},
"continue_url": "https://stay.example.com/pay/status/eyJpdiI6…",
"messages": [{ "type": "info", "code": "payment_required", "content": "Booking is held. Payment must be completed at the permalink to confirm the stay." }]
}Hand the guest the permalink_url. The booking is held but unpaid; the guest pays there through the host's payment providers, and unpaid holds are released automatically (~2 h). Retrying complete_checkout on a completed session returns the same order — bookings are never duplicated. If the inventory was taken between pricing and completion, the session drops back to incomplete with an out_of_stock message and no booking is created.