x402-hotel-search

Hotel offers and priced offer detail over the Amadeus API. Agents shop rooms without an account or an API key — one paid HTTP call, answer in the response body. Pay in USDC on Base or Solana — your client picks the rail.

git clone https://github.com/nirholas/x402-hotel-search && cd x402-hotel-search && npm install && npm run dev

Pricing

RoutePriceYou get back
GET /search$0.005Hotel offers for a city and date range — hotel, room, board type, cancellation policy, total price
GET /offer/:offerId$0.003Priced offer detail with a per-night price breakdown
GET /healthfreeService status and which data source is live
GET /citiesfreeCity codes the fixture data models by name and location
GET /.well-known/x402freeMachine-readable price sheet, both rails

Shopping a city and drilling into one candidate is $0.008. /search already returns complete offers, so only pay for /offer when you need the breakdown.

Two rails, one challenge

Every 402 lists both networks. A caller holding USDC on Base pays on Base; a caller holding USDC on Solana pays on Solana. Same route, same price, same artifact.

EVM

USDC on Base

base-sepolia by default, base for mainnet. EIP-3009 transfer authorization.

0x40252CFDF8B20Ed757D61ff157719F33Ec332402
SVM

USDC on Solana

solana mainnet by default, solana-devnet for testing. SPL transfer.

WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW

How a query flows

402quote in USDC on
Base and Solana
signclient picks a rail
and signs
retrysame request +
X-PAYMENT
200rooms in body,
receipt in header

Quickstart

# 1. Run the service (works immediately — no keys needed)
cp .env.example .env
npm run dev                # http://localhost:4023

# 2. See the dual-rail 402 quote (no wallet needed)
curl -s "http://localhost:4023/search?cityCode=NYC&checkInDate=2026-09-15&checkOutDate=2026-09-18" \
  | jq '.accepts[] | {network, payTo, maxAmountRequired}'

# 3. Pay for real on Base Sepolia (free test USDC: faucet.circle.com)
export PRIVATE_KEY=0x…
npm run client             # searches a city, then pulls detail on the cheapest room

Live data vs fixtures

Amadeus is a keyed API, so the service is env-gated: set AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET (free sandbox keys, no card) and every route hits the real Hotel Search API. Without them you get deterministic fixtures instead, so the demo always runs. Every response carries a source field"amadeus" or "fixture" — so a caller is never guessing which it got. Never quote a fixture rate as a real price.