x402-transit

Transit trip planning and live delay snapshots, straight from open GTFS and GTFS-Realtime feeds. 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-transit && cd x402-transit && npm install && npm run dev

Pricing

RoutePriceYou get back
GET /plan$0.002Itinerary options — legs, routes, scheduled times, and realtime predictions where the feed has them
GET /delays$0.001Live delay snapshot for a stop: prediction minus schedule per arrival, plus a summary verdict
GET /healthfreeFeed and agency in use
GET /stopsfreeSearch stops by name — never pay to find a stop id
GET /routesfreeRoutes, optionally just those at one stop
GET /.well-known/x402freeMachine-readable price sheet, both rails

Resolving stops and routes is free. Only planning and delay lookups cost, and a delay look is a tenth of a cent — cheap enough to poll, so bound the loop yourself.

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
200itinerary in body,
receipt in header

Quickstart

# 1. Run the service (works immediately — the feed is keyless)
cp .env.example .env
npm run dev                # http://localhost:4024

# 2. Free: find a stop id
curl -s "http://localhost:4024/stops?query=Porter" | jq '.stops[0]'

# 3. See the dual-rail 402 quote (no wallet needed)
curl -s "http://localhost:4024/plan?from=South%20Station&to=Porter" \
  | jq '.accepts[] | {network, payTo, maxAmountRequired}'

# 4. Pay for real on Base Sepolia (free test USDC: faucet.circle.com)
export PRIVATE_KEY=0x…
npm run client             # plans a trip, then checks live delays

Live feeds, and no fixtures on purpose

The feeds here are public and keyless, so they are called live with no signup. The default agency is the MBTA, whose V3 API is a JSON projection of its GTFS static feed plus GTFS-Realtime TripUpdates. Point GTFS_API_BASE at another agency running the same API to switch cities.

There is deliberately no fixture fallback. If the feed is unreachable you get a 502, never invented departure times — a wrong train time is worse than no train time. For the same reason, /plan marks each leg realtime: true|false so you know whether a prediction is live or just the timetable, and /delays reports status: "unknown" for added service that has no schedule to be measured against.