OpheliaDocs
Search docs⌘K
DashboardCreate an account
Verticals · Travel

Hotels

Card charged on confirmation · no OTP required

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 \
-H "Authorization: Bearer YOUR_LIVE_KEY" \
-H "Content-Type: application/json" \
-d '{
"vertical": "travel",
"providers": ["hotels"],
"term": "downtown Chicago",
"location": "Chicago, IL",
"check_in": "2026-12-10",
"check_out": "2026-12-12",
"party_size": 2,
"rooms": 1
}'
# → venues[].metadata.image = "https://images.hotels-cdn.com/lodging/..."
curl
# Check availability (use a ven_ id from the search response): same call
# shape as other verticals, with check_in/check_out/rooms swapped in.
curl -X POST https://api.opheliaos.com/v1/availability/search \
-H "Authorization: Bearer YOUR_LIVE_KEY" \
-H "Content-Type: application/json" \
-d '{
"venue_id": "ven_44556677",
"check_in": "2026-12-10",
"check_out": "2026-12-12",
"party_size": 2,
"rooms": 1
}'
# → slots[].availability_id (kind "room"), pass this to POST /v1/bookings
curl
# Book (use the availability_id from the previous step). datetime must be on the
# check_in day. Without a card this call places nothing: it stops at the card form.
curl -X POST "https://api.opheliaos.com/v1/bookings" \
-H "Authorization: Bearer YOUR_LIVE_KEY" \
-H "Idempotency-Key: travel-001" \
-H "Content-Type: application/json" \
-d '{
"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" }
}'
# → "status": "requires_action", next_action.type "payment" (submit card details to /continue)
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" \
-H "Authorization: Bearer YOUR_LIVE_KEY" \
-H "Content-Type: application/json" \
-d '{
"card_number": "4111111111111111",
"exp_month": "12",
"exp_year": "2027",
"cvv": "123",
"postal_code": "10014",
"name_on_card": "Jane Smith"
}'
# → "status": "requires_action", next_action.type "confirm"
curl
# 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.
Request
json
{
"vertical": "travel",
"providers": ["hotels"],
"term": "downtown Chicago",
"location": "Chicago, IL",
"check_in": "2026-12-10",
"check_out": "2026-12-12",
"party_size": 2,
"rooms": 1
}
Response
json
{
"venues": [
{
"id": "ven_44556677",
"name": "The Langham, Chicago",
"rating": 9.6,
"city": "River North",
"provider": "hotels",
"url": "https://www.hotels.com/ho.../",
"bookable": true,
"metadata": {
"image": "https://images.hotels-cdn.com/lodging/44556677/wide.jpg",
"rating": 9.6,
"price_nightly": "$219",
"price_total": "$438",
"checkin": "2026-12-10",
"checkout": "2026-12-12"
}
}
],
"count": 1
}

POST /v1/availability/search

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.