strait

Developer Docs

Hotel booking for agents.

One key. Resolve a hotel name, check availability, confirm with the user, charge, and book.

Overview

Strait is an API for AI agents that need to book hotel rooms.

1
Resolve
Hotel name → property ID
2
Availability
Rooms & rates from the PMS
3
Session
User confirms
4
Execute
Charge & book

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

Requestbash
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"}}'
Responsejson
{
  "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

Requestbash
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
  }'
Responsejson
{
  "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

Requestbash
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"
    }
  }'
Responsejson
{
  "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

Requestbash
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"}'
Responsejson
{
  "session_id": "cs_abc123",
  "status": "confirmed",
  "confirmation_code": "MEWS-PNR-78421",
  "pms_source": "mews"
}

5. Cancel (if needed)

Requestbash
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"}'
Responsejson
{
  "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

URLtext
https://mcp.getstrait.dev

One 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:

claude_desktop_config.jsonjson
{
  "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:

Terminalbash
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>.

Headerhttp
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:

  1. Create session freezes the offer's price, taxes, currency, dates, and rate code into a checkout_sessions row. The status is requires_human_approval.
  2. The agent renders ui_render_data to the user. This is the confirmation card.
  3. 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:

Examplejson
{ "detail": "Offer not found or expired" }

Common codes:

StatusMeaning
400Malformed request body. Validation hint in detail.
401Missing, malformed, or revoked API key.
404Resource (property, offer, session, booking) not found.
410Session expired. Create a new one.
409Already executed (idempotency). Response body is the original.
5xxStrait 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>.

POST/v1/properties/resolve

Match a free-text hotel name (and optional city / address / lat-lng) to a stable property ID.

Request bodyjson
{
  "query": {
    "name":    "Arlo Soho",
    "city":    "New York",
    "address": "231 Hudson Street",
    "geo":     { "lat": 40.7259, "lng": -74.0086 }
  },
  "limit": 3
}
Responsejson
{
  "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

POST/v1/properties/availability

Live rooms and pricing for a property over a date range.

Request bodyjson
{
  "property_id": "mews_3edbe1b4-...",
  "check_in":  "2026-06-10",
  "check_out": "2026-06-12",
  "guests": 2,
  "filters": {
    "refundable_only": true,
    "max_price": 400
  }
}
Responsejson
{
  "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

POST/v1/checkout/sessions

Create a HITL session from an offer_id. Returns a UI render bundle and a session ID.

Request bodyjson
{
  "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"
  }
}
Responsejson
{
  "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

POST/v1/checkout/sessions/{session_id}/execute

Finalize 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.

Request bodyjson
{
  "idempotency_key": "agent-turn-7c92...",
  "guest_details":   { "name": "Jane Doe", "email": "jane@example.com" }
}
Responsejson
{
  "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

POST/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

POST/v1/bookings/{confirmation_code}/cancel

Cancel 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.

Request bodyjson
{ "reason": "guest schedule change" }
Responsejson
{
  "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.

MethodPathDescription
GET/v1/dashboard/summaryBooking counts and revenue rollup.
GET/v1/dashboard/hotelsPer-hotel bookings, gross revenue, commission earned.
GET/v1/dashboard/bookings?limit=50Recent bookings, newest first.
GET/v1/dashboard/settingsCurrent commission rate.
PUT/v1/dashboard/settingsUpdate commission rate (0.0–1.0). Takes effect on the next session.