Overview
Strait is an API for AI agents that need to book hotel rooms.
Cancellation (call 5) is a one-shot operation against an existing booking — see Cancel booking.
Mental model
Steps 1–2 are read-only. The agent calls them as often as it wants, comparing options, swapping dates, refining the user's choice. Step 3 (create session) freezes a specific offer and returns a confirmation card — this is the human-in-the-loop step. Step 4 (execute) only fires after the user clicks confirm; that's when the card is charged and the PMS booking is created.
This split exists for a reason. Agents hallucinate. Letting an agent atomically resolve, price, and book in one shot is how guests end up at the wrong hotel on the wrong date. Strait's API forces a confirmation step because the alternative isn't safe.
Where Strait fits in your stack
- You own the agent. Strait is just tools the agent calls. Bring any model, any framework.
- The hotel owns the guest. Bookings land directly in their PMS, with rate codes and policies intact. No bedbank, no rewrap.
- Strait owns resolve and the rails. We resolve fuzzy hotel queries to stable IDs, normalize PMS quirks, and handle HITL checkout, payment, and cancellation.
Quickstart
Five curl commands, end to end. Replace $STRAIT_KEY with the key from your welcome email.
1. Resolve a hotel name
curl -sS https://api.getstrait.dev/v1/properties/resolve \
-H "Authorization: Bearer $STRAIT_KEY" \
-H "Content-Type: application/json" \
-d '{"query": {"name": "Arlo Soho", "city": "New York"}}'{
"results": [{
"property_id": "mews_3edbe1b4-...",
"confidence_score": 0.91,
"verification_data": {
"official_name": "Arlo Soho",
"address": "231 Hudson Street, New York"
}
}]
}Save property_id for the next call.
2. Check availability
curl -sS https://api.getstrait.dev/v1/properties/availability \
-H "Authorization: Bearer $STRAIT_KEY" \
-H "Content-Type: application/json" \
-d '{
"property_id": "mews_3edbe1b4-...",
"check_in": "2026-06-10",
"check_out": "2026-06-12",
"guests": 2
}'{
"available_offers": [{
"offer_id": "off_8f72b9a1",
"room": { "name": "King", "max_occupancy": 2 },
"pricing": { "currency": "USD", "total_price": 514.0 },
"policies": { "cancellation": "Free until 24h before arrival" }
}]
}Save offer_id. The PMS books at its own rate. See Earnings & billing for how revenue share flows back to you.
3. Create a checkout session
curl -sS https://api.getstrait.dev/v1/checkout/sessions \
-H "Authorization: Bearer $STRAIT_KEY" \
-H "Content-Type: application/json" \
-d '{
"offer_id": "off_8f72b9a1",
"guest_details": {
"name": "Jane Doe",
"email": "jane@example.com",
"notes": "high floor, dairy-free breakfast"
}
}'{
"session_id": "cs_abc123",
"status": "requires_human_approval",
"ui_render_data": {
"title": "Confirm your booking — Arlo Soho",
"total_due": 514.0,
"currency": "USD",
"disclaimer": "Charged on confirmation. Cancellation per hotel policy."
}
}Render the ui_render_data as a confirmation card to your end user. The session won't execute until they approve and your agent calls execute.
4. Execute the booking
curl -sS https://api.getstrait.dev/v1/checkout/sessions/cs_abc123/execute \
-H "Authorization: Bearer $STRAIT_KEY" \
-H "Content-Type: application/json" \
-d '{"idempotency_key": "your-unique-id"}'{
"session_id": "cs_abc123",
"status": "confirmed",
"confirmation_code": "MEWS-PNR-78421",
"pms_source": "mews"
}5. Cancel (if needed)
curl -sS https://api.getstrait.dev/v1/bookings/MEWS-PNR-78421/cancel \
-H "Authorization: Bearer $STRAIT_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "guest schedule change"}'{
"status": "cancelled",
"confirmation_code": "MEWS-PNR-78421",
"message": "Cancelled per the hotel's flexible policy. Full refund issued."
}MCP / Claude Desktop
If you're shipping inside Claude (Desktop, Code, or any MCP-aware client), skip the curl. Plug Strait in as an MCP server and the agent gets the same five calls as named tools — find_property, check_availability, book, cancel_booking.
Hosted MCP server
https://mcp.getstrait.devOne key, both surfaces (REST and MCP).
Claude Desktop (HTTP transport)
Open ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, or %APPDATA%\Claude\claude_desktop_config.json on Windows. Paste your strait_live_… key into the Authorization header:
{
"mcpServers": {
"strait": {
"transport": "http",
"url": "https://mcp.getstrait.dev",
"headers": { "Authorization": "Bearer strait_live_..." }
}
}
}Restart Claude Desktop. The Strait tools appear in the tool picker. If they don't, the most common cause is a missing or wrong key — Claude Desktop's MCP log will show a 401 from mcp.getstrait.dev.
Claude Code (CLI)
Same key, passed as a header on the mcp add command:
claude mcp add strait https://mcp.getstrait.dev \
--header "Authorization: Bearer strait_live_..."Authentication
Every request carries your key in an Authorization header. Keys are issued in the form strait_live_<32-hex>.
Authorization: Bearer strait_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- Don't ship keys client-side. Treat them like a database password. The dashboard stores its key in browser localStorage because it acts on behalf of the key owner; agents should never embed the key in client code.
- Revoked keys 401. Once an admin marks a key revoked, every subsequent request fails. There is no soft-deprecation window.
- Rotation. Reply to your welcome email and we'll mint a fresh key and revoke the old one in the same step.
HITL checkout
Human-in-the-loop is the reason session and execute are separate calls. Here's what's happening underneath:
- Create session freezes the offer's price, taxes, currency, dates, and rate code into a
checkout_sessionsrow. The status isrequires_human_approval. - The agent renders
ui_render_datato the user. This is the confirmation card. - The user confirms. Your agent calls
execute, which authorizes payment and creates the PMS reservation.
Idempotency
execute requires an idempotency_key. The first call with a given key creates the booking; subsequent calls with the same key return the same response without double-booking.
Use a unique value per logical user action — typically a hash of the agent turn ID plus the session ID. Reusing a key across different sessions is a bug; you'll get the original session's response back.
Earnings & billing
Strait charges the hotel a single commission per completed stay. Most of it is paid back to you as revenue share. The shape of the model:
- Total commission to the hotel. A single-digit percent of booking value, billed post-stay. Well below what an OTA charges.
- Your revenue share. The majority of that commission, paid by Strait monthly on every completed stay you drive.
- What the consumer sees. The PMS rate, same number they'd see on the hotel's own website. No markup, no two-tier pricing, no rate-parity headaches. The hotel is merchant of record throughout.
- Post-stay only. Nothing is owed on cancellations, no-shows, or chargebacks. Earnings accrue on bookings that actually result in a stay.
Earnings accumulate post-stay and pay out monthly. Track pending vs. paid on the dashboard. Specific rates are still being finalized with our first design partners. Contact us for current pricing.
Errors
Errors return JSON with a detail field:
{ "detail": "Offer not found or expired" }Common codes:
| Status | Meaning |
|---|---|
400 | Malformed request body. Validation hint in detail. |
401 | Missing, malformed, or revoked API key. |
404 | Resource (property, offer, session, booking) not found. |
410 | Session expired. Create a new one. |
409 | Already executed (idempotency). Response body is the original. |
5xx | Strait or upstream PMS error. Safe to retry with the same idempotency key. |
API reference
All endpoints are https://api.getstrait.dev + the path. Bodies are JSON. Required header: Authorization: Bearer <key>.
/v1/properties/resolveMatch a free-text hotel name (and optional city / address / lat-lng) to a stable property ID.
{
"query": {
"name": "Arlo Soho",
"city": "New York",
"address": "231 Hudson Street",
"geo": { "lat": 40.7259, "lng": -74.0086 }
},
"limit": 3
}{
"results": [{
"property_id": "mews_3edbe1b4-...",
"confidence_score": 0.91,
"match_reason": "name + city trigram",
"verification_data": {
"official_name": "Arlo Soho",
"address": "231 Hudson Street, New York",
"phone": "+1 212-555-0100",
"coordinates": { "lat": 40.7259, "lng": -74.0086 }
},
"bookable": true
}]
}Notes. Pass as much as the user gave you — name + city alone usually resolves boutique hotels with high confidence. bookable: false means we know about the hotel but aren't connected to its PMS yet.
Availability
/v1/properties/availabilityLive rooms and pricing for a property over a date range.
{
"property_id": "mews_3edbe1b4-...",
"check_in": "2026-06-10",
"check_out": "2026-06-12",
"guests": 2,
"filters": {
"refundable_only": true,
"max_price": 400
}
}{
"property_id": "mews_3edbe1b4-...",
"available_offers": [{
"offer_id": "off_8f72b9a1",
"room": { "name": "King", "max_occupancy": 2 },
"pricing": {
"currency": "USD",
"base_rate": 229.0,
"taxes_and_fees": 28.0,
"total_price": 257.0
},
"policies": { "cancellation": "Free until 24h before arrival" }
}]
}Notes. Each offer is a snapshot. The offer_id is what you pass to create session.
Create session
/v1/checkout/sessionsCreate a HITL session from an offer_id. Returns a UI render bundle and a session ID.
{
"offer_id": "off_8f72b9a1",
"customer_id": "your-internal-user-id",
"guest_details": {
"name": "Jane Doe",
"email": "jane@example.com",
"notes": "high floor, sparkling water in the room"
}
}{
"session_id": "cs_abc123",
"status": "requires_human_approval",
"expires_at": "2026-06-09T18:00:00Z",
"ui_render_data": {
"title": "Confirm your booking — Arlo Soho",
"line_items": [
{ "label": "King · 2 nights", "amount": 458.0 },
{ "label": "Taxes & fees", "amount": 56.0 }
],
"total_due": 514.0,
"currency": "USD",
"disclaimer": "Charged on confirmation. Cancellation per hotel policy."
}
}Execute
/v1/checkout/sessions/{session_id}/executeFinalize the booking after the user confirms. Authorizes payment, writes the reservation to the PMS.
Path parameters. session_id — the session_id returned by create session.
{
"idempotency_key": "agent-turn-7c92...",
"guest_details": { "name": "Jane Doe", "email": "jane@example.com" }
}{
"session_id": "cs_abc123",
"status": "confirmed",
"confirmation_code": "MEWS-PNR-78421",
"pms_source": "mews"
}Notes. idempotency_key is required. Repeated calls with the same key return the same response without double-booking.
Get booking
/v1/bookings/{confirmation_code}Fetch booking details for a cancellation preview. Use this before cancel so the user sees what they're cancelling.
Path parameters. confirmation_code — the code returned by execute.
No request body required.
Cancel booking
/v1/bookings/{confirmation_code}/cancelCancel a confirmed booking. The PMS makes the policy decision (refund or fee).
Path parameters. confirmation_code — the code returned by execute. The booking to cancel is identified entirely by the URL; the body only carries an optional reason.
{ "reason": "guest schedule change" }{
"status": "cancelled",
"confirmation_code": "MEWS-PNR-78421",
"message": "Cancelled per the hotel's flexible policy. Full refund issued."
}Dashboard
Read-only views scoped to your key. The web dashboard at getstrait.dev/dashboard is built on top of these.
| Method | Path | Description |
|---|---|---|
| GET | /v1/dashboard/summary | Booking counts and revenue rollup. |
| GET | /v1/dashboard/hotels | Per-hotel bookings, gross revenue, commission earned. |
| GET | /v1/dashboard/bookings?limit=50 | Recent bookings, newest first. |
| GET | /v1/dashboard/settings | Current commission rate. |
| PUT | /v1/dashboard/settings | Update commission rate (0.0–1.0). Takes effect on the next session. |