# Flight402 > Flight booking API for AI agents. Search, book, and manage flight tickets using USDC payments via FluxA x402 protocol. ## Prerequisites Before using this API, you must install the **FluxA Agent Wallet skill (≥ 0.4.5)** and the **`fluxa-wallet` CLI (≥ 0.4.5)**. Older versions do not support the `agent-vc` subcommand that this API requires for authentication. Read and install the skill from: https://fluxapay.xyz/skill.md This gives you an `agent_id`, a FluxA Wallet for USDC payments via the x402 protocol, and the ability to mint short-lived Agent Verifiable Credentials (VCs) for third-party services like flight402. Verify your CLI version: ``` fluxa-wallet --version # must be >= 0.4.5 ``` ## Authentication All `/v1/*` endpoints require a **FluxA Agent Verifiable Credential (VC)**. A VC is scoped by `typ=agent-vc`, `aud`, `challenge`, and short `exp`, so it is safe to hand to third-party services like flight402. Mint a VC bound to this API with the FluxA wallet CLI: ``` fluxa-wallet agent-vc \ --audience urn:flight402:api \ --challenge flight402 \ --ttl 3600 ``` - `--audience` **must** be `urn:flight402:api`; VCs with any other audience are rejected. - `--challenge` is required by the CLI but **flight402 does not validate it** — pass any non-empty string (the example above uses the literal `flight402`). - `--ttl` is seconds; max 86400. A short TTL (e.g. 3600) is recommended — re-mint when it expires. Send the returned `vc` in the Authorization header: ``` Authorization: Bearer ``` flight402 verifies the VC locally against FluxA's JWKS (`https://agentid.fluxapay.xyz/.well-known/jwks.json`): RS256 signature, `typ=agent-vc` header, `aud=urn:flight402:api`, and unexpired `exp`. The verified agent id is taken from the VC's `sub` claim. Base URL: `https://flight402-production.up.railway.app` ## API Endpoints ### Search Flights `POST /v1/flights/search` Find available flights between cities. Request body: - `from` (string, required): Departure city IATA code, e.g. "PEK" - `to` (string, required): Arrival city IATA code, e.g. "LAX" - `date` (string, required): Departure date in YYYY-MM-DD format - `returnDate` (string, optional): Return date for round trips - `adults` (integer, optional, default 1): Number of adult passengers (1-9) - `children` (integer, optional, default 0): Number of child passengers - `infants` (integer, optional, default 0): Number of infant passengers - `airlines` (string[], optional): Filter by airline IATA codes Response: `{ flights: [{ flightId, price: { currency: "USDC", adult, child, infant }, outbound, inbound, cabinClass, rules }] }` ### Verify Flight Price `POST /v1/flights/{flightId}/verify` Confirm current price and availability before booking. The `flightId` comes from search results. Response: `{ verifiedFlightId, priceChanged, price: { currency: "USDC", adult, child }, maxSeats, bookingRequirements, outbound, inbound }` ### Query Seats `GET /v1/flights/{verifiedFlightId}/seats` Get seat map and availability after verification. Response: `{ segments: [{ flightNumber, seatMap: [{ row, seats: [{ code, productCode, available, position, price }] }] }] }` ### Query Luggage `GET /v1/flights/{verifiedFlightId}/luggage` Get available luggage add-on options after verification. Response: `{ segments: [{ flightNumber, options: [{ productCode, type, weight, piece, price }] }] }` ### Create Order `POST /v1/orders` Book a flight and receive a USDC payment link. Request body: - `verifiedFlightId` (string, required): From verify response - `passengers` (array, required): Each with `firstName`, `lastName`, `type` ("adult"/"child"/"infant"), `gender`, `birthday` (YYYY-MM-DD), `nationality`, `documentType`, `documentNumber`, `documentExpiry`, `documentCountry` - `contact` (object, required): `name`, `email`, `phone` - `selectedSeats` (array, optional): `[{ passengerIndex, productCode }]` - `selectedLuggage` (array, optional): `[{ passengerIndex, productCode }]` - `couponCode` (string, optional): Coupon code for discount, e.g. "WELCOME10" Response: `{ orderId, paymentUrl, totalPrice, currency: "USDC", expiresAt, pnr, flights, coupon?: { code, discount, priceBeforeCoupon } }` ### Complete Payment `POST /v1/orders/{orderId}/complete-payment` Call this after paying the `paymentUrl` from the order response. The system verifies your USDC payment was received and triggers ticket issuance. Response: `{ status: "ticketing" }` on success, or `{ error: "Payment not yet received", paymentUrl }` if payment not found. ### Query Order `GET /v1/orders/{orderId}` Check order status and get ticket details. Response: `{ orderId, status, pnr, totalPrice, currency, paymentUrl?, flights, tickets, passengers, createdAt }` `paymentUrl` is included only when status is `pending_payment`. Order statuses: `pending_payment` → `paid` → `ticketing` → `ticketed` → `cancelled` ### Stop Ticketing `POST /v1/orders/{orderId}/stop-ticketing` Attempt to cancel ticket issuance (only works if tickets haven't been issued yet). Check order status after 8 minutes for result. ### Request Refund Quote `POST /v1/orders/{orderId}/refund-quote` Get a refund estimate before submitting. Passenger and flight details are auto-populated from the order — you only need to specify the reason. The order must be in `ticketed` status. Request body: - `reason` (string, optional): "voluntary" (default) or "involuntary" Response: `{ refundable, refundQuoteId, refundAmount: { currency: "USDC", amount }, penalty }` ### Submit Refund `POST /v1/orders/{orderId}/refund` Submit a refund request. Refunds go through manual review before USDC is returned to your wallet. Request body: - `refundQuoteId` (string, required): From refund-quote response - `reason` (string, optional): Refund reason Response: `{ refundId, status: "pending_review", refundAmount }` ### Query Refund Status `GET /v1/orders/{orderId}/refund/{refundId}` Check refund review and payment status. Response: `{ refundId, reviewStatus, refundAmount, txHash, createdAt }` Review statuses: `pending` → `approved` → `paid` (or `rejected`) ### Search Post-Booking Ancillary `POST /v1/orders/{orderId}/ancillary/search` Search for add-on baggage after tickets are issued. Response: `{ sessionId, ancillaries: [{ productCode, type, weight, piece, price }] }` ### Purchase Ancillary `POST /v1/orders/{orderId}/ancillary/purchase` Purchase add-on baggage. Returns a separate payment link. Request body: - `sessionId` (string, required): From ancillary search - `selections` (array, required): `[{ passengerIndex, productCode }]` Response: `{ ancillaryOrderId, paymentUrl, totalPrice, currency: "USDC" }` ### Complete Ancillary Payment `POST /v1/orders/{orderId}/ancillary/{ancillaryOrderId}/complete-payment` Confirm payment for ancillary purchase, same flow as order payment. ## Coupons ### Check Coupon `POST /v1/coupons/check` Verify a coupon code is valid and preview the discount before ordering. Request body: - `code` (string, required): Coupon code, e.g. "WELCOME10" - `estimatedTotal` (number, optional): Estimated order total in USDC for accurate discount preview Response: `{ valid: true, code, type: "fixed"|"percent", value, maxDiscount, minOrder, estimatedDiscount }` or `{ valid: false, error }` To use a coupon, pass `couponCode` in the `POST /v1/orders` request body. The discount is applied automatically and reflected in `totalPrice`. Coupon rules: - One coupon per order - Each coupon has a per-agent usage limit (typically 1) - Coupons have a total quota, valid date range, and minimum order amount - Coupon discounts only apply to flight orders, not ancillary purchases ### Browse Discount Promotions `GET /v1/promotions` Public bulletin board describing the discount campaigns flight402 is currently running. Use this to discover which coupon codes are worth checking. Query parameters: - `scope` (string, optional, default `current`): `current` returns only promotions whose start/end window covers right now; `upcoming` also includes ones that haven't started yet; `all` includes already-expired entries (still active flag only). Response: `{ promotions: [{ id, title, description, startsAt, endsAt, active }] }` ### Get Promotion Detail `GET /v1/promotions/{id}` Fetch a single promotion announcement. Response: `{ id, title, description, startsAt, endsAt, active }` Participation is opt-in: the promotion just describes the activity. To actually claim a discount, take the coupon code mentioned in the promotion's `description` and pass it via `POST /v1/coupons/check` and then as `couponCode` on `POST /v1/orders`. ## Booking Flow The typical end-to-end flow: 1. `POST /v1/flights/search` — find flights 2. `POST /v1/flights/{flightId}/verify` — confirm price 3. (Optional) `GET /v1/flights/{verifiedFlightId}/seats` — view seats 4. (Optional) `GET /v1/flights/{verifiedFlightId}/luggage` — view luggage options 5. (Optional) `POST /v1/coupons/check` — verify a coupon code and preview discount 6. `POST /v1/orders` — create order (with optional `couponCode`), get `paymentUrl` 7. Pay USDC at the `paymentUrl` using your FluxA Wallet 8. `POST /v1/orders/{orderId}/complete-payment` — trigger ticketing 9. `GET /v1/orders/{orderId}` — poll until status is `ticketed` ## Payment All prices are in USDC. Payments use FluxA Payment Links: - When you create an order, you receive a `paymentUrl` - Open the URL and pay with your FluxA Wallet (x402 protocol, zero gas fees) - After payment, call `complete-payment` to confirm and trigger ticket issuance - Refunds are returned to your wallet via Unify Payment Link after manual approval ## Admin API Platform admins can manage orders, coupons, refunds, and admin users via the Admin API. See admin documentation at: `GET /admin/llms.txt`