Practise in Ophelia Test City first. A hotel search there returns six practice hotels that run this same flow (payment, card, confirm, or a pay link) with nothing charged and nothing billed. Test cards there trigger a decline and an unknown outcome on purpose, and some rates sell out. Everywhere else, a test key's confirm charges a real card. See Testing.
Hotels search and availability take a date range, check_in and check_out, plus rooms (how many rooms to book, default 1), in place of dining's single datetime; party_size is total guests across all rooms (default 2). Hotels has no OTP step: POST /v1/bookings/{id}/continue takes card details and then a confirmation, not an otp_code. Nothing is placed until you confirm; see the callout below before you do.
Use these commands
curl
# Search (Hotels): check_in/check_out replace dining's single datetime,
# and rooms is unique to hotels (dining has no equivalent).
curl -X POST https://api.opheliaos.com/v1/venues/search \
The confirm is the charge. Submitting card details to /continue fills them into the checkout and returns requires_action again with next_action.type"confirm". Sending {"confirm": true} to /continue places the reservation and charges the card. Adding "confirm": true to the card body does both in one call, and a card sent up front in metadata.payment on POST /v1/bookings places the reservation in that call, with no payment or confirm round, unless you also send metadata.confirm: false, which stops at the confirm round. This is where Hotels diverges from OpenTable's /continue: OpenTable takes an otp_code there and never charges the card itself.
curl
# Submit card details. Hotels has no otp_code. This fills the card in but does
# not place the reservation yet.
curl -X POST "https://api.opheliaos.com/v1/bookings/bkg_44556677/continue" \
# Confirm: this places the reservation and charges the card.
curl -X POST "https://api.opheliaos.com/v1/bookings/bkg_44556677/continue" \
-H "Authorization: Bearer YOUR_LIVE_KEY" \
-H "Content-Type: application/json" \
-d '{ "confirm": true }'
# → "status": "confirmed"
Remember when testing
The postal_code you send in metadata at booking time and the postal_code you send with the card at /continue should match; a mismatch can trip the card issuer's address verification and cause the charge to be declined.
Answer within five minutes
Each requires_action carries next_action.expires_at, five minutes out. The checkout is held open for at most ten minutes; after that /continue fails with error_code: "session_gone" and the booking has to be started again.
POST /v1/venues/search
Finds hotels for a date range. Without providers, a travel search picks one provider for the request (hotels for a hotel search; flights are a separate provider). Pass ["hotels"] to be explicit.
FieldTypeNotes
vertical
string
required
Always "travel" for this endpoint.
providers
string[]
Filters to specific travel providers, e.g. ["hotels"].
term
string
required
Free text: a city, neighborhood, or hotel name.
location
string
required
Where to search.
check_in
date
required
In place of dining's single datetime.
check_out
date
After check_in. If missing, or not after check_in, one night is priced.
party_size
integer
Total guests across all rooms. Defaults to 2.
rooms
integer
How many rooms to book. Defaults to 1. Dining has no equivalent field.
Returns bookable room/rate combinations for a venue and date range. Each slot has kind: "room" and an offer describing the room and rate; there is no datetime on a room slot, because the dates were your input. Pass the slot's availability_id to POST /v1/bookings. offer.card_required says whether that rate needs a card.
FieldTypeNotes
venue_id
string
required
From the search response.
check_in
date
required
Same value as search, or narrowed further.
check_out
date
After check_in. If missing, or not after check_in, one night is priced.
party_size
integer
Total guests across all rooms. Defaults to 2.
rooms
integer
How many rooms to book. Defaults to the count the search priced, else 1.
Request
json
{
"venue_id":"ven_44556677",
"check_in":"2026-12-10",
"check_out":"2026-12-12",
"party_size": 2,
"rooms": 1
}
Response
json
{
"venue_id":"ven_44556677",
"slots":[
{
"availability_id":"avail_9c2f7b1a4e6d3058",
"kind":"room",
"venue_id":"ven_44556677",
"party_size": 2,
"status":"available",
"price_required": false,
"deposit_required": false,
"offer":{
"label":"King Room, City View",
"price":"$438",
"amount": 438,
"currency":"USD",
"cancellation":"Non-Refundable",
"card_required": true,
"rate_plan_id":"206117190",
"room_id":"3530079"
}
},
{
"availability_id":"avail_5e1a3c8f02b7d941",
"kind":"room",
"venue_id":"ven_44556677",
"party_size": 2,
"status":"available",
"price_required": false,
"deposit_required": false,
"offer":{
"label":"Queen Room",
"price":"$392",
"amount": 392,
"currency":"USD",
"cancellation":"Fully refundable before Dec 8",
"card_required": true,
"rate_plan_id":"206117204",
"room_id":"3530081"
}
}
],
"card_required": true
}
POST /v1/bookings
Creates the booking against an availability_id. Without a card, this call drives the checkout to the card form and stops there: nothing is placed or charged, and the booking returns next_action.type"payment". If you send the card here as metadata.payment (the same shape as dining's), the reservation is placed and charged in this call. Add "confirm": false to metadata to have the card filled in and stop at the confirm round instead.
FieldTypeNotes
availability_id
string
required
From the availability response.
venue_id
string
From the search response. Without it, send provider: "hotels".
party_size
integer
required
Total guests across all rooms.
datetime
RFC 3339
required
Arrival time, on the check_in day; another day is refused with slot_not_offered.
customer
object
required
name, phone_number, email.
metadata.postal_code
string
Billing postal code for the checkout form.
metadata.payment
object
Optional. Sending the card here places and charges the reservation in this call, unless metadata.confirm is false.
Request
json
{
"availability_id":"avail_9c2f7b1a4e6d3058",
"venue_id":"ven_44556677",
"party_size": 2,
"datetime":"2026-12-10T15:00:00Z",
"customer":{
"name":"Jane Smith",
"phone_number":"+15555555555",
"email":"jane.smith.diner@gmail.com"
},
"metadata":{"postal_code":"10014"}
}
Response
json
{
"id":"bkg_44556677",
"object":"booking",
"status":"requires_action",
"venue_id":"ven_44556677",
"provider":"hotels",
"next_action":{
"type":"payment",
"message":"hotels.com needs card details to hold this reservation.",
"expires_at":"2026-11-20T18:05:00Z"
}
}
POST /v1/bookings/{id}/continue
Called twice for a hotel. First with card details, which are filled into the checkout; the booking returns requires_action with next_action.type"confirm". Then with {"confirm": true}, which places the reservation and charges the card. Add "confirm": true to the card body to do both at once. OpenTable takes an otp_code here and never charges the card; hotels takes the card itself.
FieldTypeNotes
card_number
string
required
No otp_code field for hotels.
exp_month
string
required
Two digits.
exp_year
string
required
Four digits.
cvv
string
required
postal_code
string
required
Should match metadata.postal_code from the booking step.
name_on_card
string
Defaults to customer.name.
confirm
boolean
Send true to place the reservation, alone or with the card.
Request (card)
json
{
"card_number":"4111111111111111",
"exp_month":"12",
"exp_year":"2027",
"cvv":"123",
"postal_code":"10014",
"name_on_card":"Jane Smith"
}
Response
json
{
"id":"bkg_44556677",
"object":"booking",
"status":"requires_action",
"venue_id":"ven_44556677",
"provider":"hotels",
"next_action":{
"type":"confirm",
"message":"card details are filled and the reservation is one press from being placed ...; re-send with confirm=true to commit",
"expires_at":"2026-11-20T18:07:00Z"
}
}
Request (confirm)
json
{"confirm": true }
Response
json
{
"id":"bkg_44556677",
"object":"booking",
"status":"confirmed",
"venue_id":"ven_44556677",
"provider":"hotels",
"provider_booking_id":"73021589214630",
"confirmation":{
"confirmation_code":"73021589214630"
}
}
Modifying or cancelling a booking
No autonomous modify or cancel for hotels
Unlike OpenTable, hotels does not currently support POST /v1/bookings/{id}/modify, and POST /v1/bookings/{id}/cancel cannot cancel a confirmed hotel reservation: the booking stays confirmed with error_code: "cancellation_unsupported". Changing dates, party size, or cancelling a confirmed hotel reservation has to be done manually. A booking still waiting at the card or confirm step can be cancelled; nothing has been placed yet.