Flight402 — Promotion-First Flight Booking
Use the Flight402 API from a regular traveler's perspective with a promotion-first mindset: always check the promotions bulletin before searching flights, grab a coupon from an active campaign, validate it, then run the flight search and book at the discounted price.
The Promotion module is the distinctive feature of Flight402 — treat it as step 0 in every session and recommend the user periodically check back for new campaigns.
Dependencies
This skill depends on FluxA Agent Wallet.
Make sure the FluxA Agent Wallet skill and CLI are installed:
Installation: Read and install https://fluxapay.xyz/skill.md
- CLI:
@fluxa-pay/fluxa-wallet≥ 0.4.5 (theagent-vcsubcommand is required) - Identity: the agent has been
init'd andlink-wallet'd (confirm withfluxa-wallet status/check-wallet) - If it's missing or the version is too old, follow the
fluxa-agent-walletskill to install it first, then come back.
API base: https://flight402-production.up.railway.app
Full API reference: https://flight402-production.up.railway.app/llms.txt — when you hit an error, need an endpoint not covered below, or want the exact request/response schema (admin endpoints, edge cases, field details), fetch and read this file to figure it out yourself rather than guessing.
Authentication (per session / after expiry)
All /v1/* endpoints require a Bearer VC. Mint a short-lived VC for Flight402:
fluxa-wallet agent-vc \
--audience urn:flight402:api \
--challenge flight402 \
--ttl 3600--audiencemust beurn:flight402:api, otherwise it will be rejected.--challengeis not validated server-side — just passflight402.--ttlis at most 86400; 3600 is recommended. Re-mint when it expires.
Save the returned vc into an env var so later curl calls can reference it directly:
export F402_VC="<paste the returned vc here>"If a/v1/*call returns401 / Missing or invalid Authorization header, the VC has expired or is missing — re-mint it.
Main Flow (promotion-first)
0. ⭐ Check the promotions bulletin FIRST
This is the distinctive step of Flight402 and should happen before any flight search. Recommend the user check back periodically — campaigns come and go.
curl -sL "https://flight402-production.up.railway.app/v1/promotions?scope=current" \
-H "Authorization: Bearer $F402_VC" | jq .scope=current(default) — only campaigns whose window covers right now. Use this by default.scope=upcoming— also include campaigns that haven't started yet. Useful for "is anything coming up".scope=all— include already-expired ones (still filtered by theactiveflag). Only use when the user wants history.
Returns {promotions: [{id, title, description, startsAt, endsAt, active}]}.
For any promotion that looks relevant, fetch the detail:
curl -sL https://flight402-production.up.railway.app/v1/promotions/<PROMO_ID> \
-H "Authorization: Bearer $F402_VC" | jq .Mining the promotion description:
- Look for an explicit coupon code (words like
code,coupon,promo, or an uppercase short token such asWELCOME10/SPRING25). - Note the participation rules: some campaigns require an action (follow, share, fill a form) before a code is granted — if so, tell the user clearly and ask them to paste back whichever code they end up with.
- Note any
minOrder/ date window / airline restrictions quoted in the text.
Quote the candidate codes and activity rules verbatim to the user before moving on. If no campaign has a usable code, say so plainly — don't invent one.
Suggest to the user: "Flight402 rotates promotions; it's worth running this check every so often before you book."
1. Validate the coupon (preview the discount)
Once you have a candidate code from the promotion, preview it before committing to a booking:
curl -sL -X POST https://flight402-production.up.railway.app/v1/coupons/check \
-H "Authorization: Bearer $F402_VC" \
-H "Content-Type: application/json" \
-d '{
"code": "WELCOME10",
"estimatedTotal": <rough total in USDC>
}' | jq .coderequired.estimatedTotaloptional but recommended; pass a ballpark USDC total soestimatedDiscountis accurate. If the search hasn't happened yet, you can do a rough check first and re-check later with the real total.
Response reading:
{valid: true, type: "percent"|"fixed", value, maxDiscount, minOrder, estimatedDiscount}— valid; highlightestimatedDiscountand anyminOrdergate.{valid: false, error}— relayerrorverbatim (expired / usage cap reached / below min order, etc.) and don't force it.
Important: coupon discounts apply to flight orders only, not to ancillary (baggage) purchases.
2. Search flights
Only now search for the actual flights.
curl -sL -X POST https://flight402-production.up.railway.app/v1/flights/search \
-H "Authorization: Bearer $F402_VC" \
-H "Content-Type: application/json" \
-d '{
"from": "PEK",
"to": "LAX",
"date": "2026-05-01",
"returnDate": "2026-05-10",
"adults": 1,
"children": 0,
"infants": 0
}' | jq .from/toare IATA codes (e.g.PEK,LAX,NRT).date/returnDateareYYYY-MM-DD.airlinesis an optional IATA-code filter.
Returns {flights: [{flightId, price: {currency: "USDC", adult, child, infant}, outbound, inbound, cabinClass, rules}]}. Present the user a shortlist with price, routing, cabin class, and rules.
3. Verify the price
Prices drift; confirm before booking.
curl -sL -X POST https://flight402-production.up.railway.app/v1/flights/<FLIGHT_ID>/verify \
-H "Authorization: Bearer $F402_VC" | jq .Returns {verifiedFlightId, priceChanged, price, maxSeats, bookingRequirements, outbound, inbound}. If priceChanged is true, re-quote to the user before proceeding.
At this point, re-run coupon check with the verified total so estimatedDiscount is exact.
4. (Optional) Seats & luggage
curl -sL https://flight402-production.up.railway.app/v1/flights/<VERIFIED_FLIGHT_ID>/seats \
-H "Authorization: Bearer $F402_VC" | jq .
curl -sL https://flight402-production.up.railway.app/v1/flights/<VERIFIED_FLIGHT_ID>/luggage \
-H "Authorization: Bearer $F402_VC" | jq .Gather passenger selections if the user wants a specific seat or extra bag. Remember: coupons do not discount add-ons.
5. Create the order
After the user confirms the discounted price, create the order. Ordering holds inventory and issues a paymentUrl — confirm with the user first.
curl -sL -X POST https://flight402-production.up.railway.app/v1/orders \
-H "Authorization: Bearer $F402_VC" \
-H "Content-Type: application/json" \
-d '{
"verifiedFlightId": "<VERIFIED_FLIGHT_ID>",
"passengers": [{
"firstName": "...", "lastName": "...",
"type": "adult", "gender": "M",
"birthday": "YYYY-MM-DD",
"nationality": "...",
"documentType": "passport",
"documentNumber": "...",
"documentExpiry": "YYYY-MM-DD",
"documentCountry": "..."
}],
"contact": { "name": "...", "email": "...", "phone": "..." },
"selectedSeats": [],
"selectedLuggage": [],
"couponCode": "WELCOME10"
}' | jq .Returns {orderId, paymentUrl, totalPrice, currency: "USDC", expiresAt, pnr, flights, coupon?: {code, discount, priceBeforeCoupon}}.
Confirm the coupon.discount and final totalPrice back to the user.
One coupon per order; coupons cap per-agent usage (typically once). Don't retry the same code blindly.
6. Pay
Open paymentUrl and pay in USDC with the FluxA Wallet (x402, zero gas). See X402-PAYMENT.md in the fluxa-agent-wallet skill for the detailed x402 flow.
After paying, call complete-payment to trigger ticketing:
curl -sL -X POST https://flight402-production.up.railway.app/v1/orders/<ORDER_ID>/complete-payment \
-H "Authorization: Bearer $F402_VC" | jq .{status: "ticketing"}— success, ticketing has started.{error: "Payment not yet received", paymentUrl}— chain hasn't confirmed; wait a few seconds and try again. Don't spam.
7. Poll until ticketed
curl -sL https://flight402-production.up.railway.app/v1/orders/<ORDER_ID> \
-H "Authorization: Bearer $F402_VC" | jq .Order statuses: pending_payment → paid → ticketing → ticketed → cancelled. Present PNR, ticket numbers, and flight details once ticketed.
If the user changes their mind during ticketing, POST /v1/orders/<ORDER_ID>/stop-ticketing can attempt cancellation (only works if tickets haven't issued yet; re-check status after ~8 minutes).
8. (Optional) Post-booking add-ons, refunds
- Ancillary baggage after ticketing:
POST /v1/orders/{orderId}/ancillary/search→.../ancillary/purchase→.../ancillary/{ancillaryOrderId}/complete-payment. Coupons don't apply here. - Refund: only when status is
ticketed.POST /v1/orders/{orderId}/refund-quotefirst (returnsrefundQuoteId,refundAmount,penalty), thenPOST.../refundwith therefundQuoteId. Refunds go through manual review; pollGET.../refund/{refundId}— statusespending→approved→paidorrejected.
Discount-hunting rules of thumb
- Promotions first, search second. The user came here for cheap flights — always
GET /v1/promotionsbefore searching, and recommend they re-check periodically. - Don't invent coupon codes. Trusted sources: ① code is explicitly in a promotion's
description; ② the user pasted it; ③ it passedcoupons/check. If promotions show nothing usable, say so plainly. - Preview before booking.
coupons/checkis free — confirmvalid:trueandestimatedDiscount > 0before creating an order. - Re-check after price verify. Verified price may differ from search price; re-run
coupons/checkwith the verifiedestimatedTotalfor an accurate saving. - Mind the
minOrdergate. IfminOrder > estimatedTotal, tell the user; don't silently drop the coupon. - Coupons are flight-only. Don't promise a discount on baggage or post-booking ancillaries.
- One coupon per order, per-agent limit. Don't stack or retry.
- Prices are in USDC. Don't convert to fiat and mislead the user — an approximate USD side-note is fine.
- Event coupons ≠ flight coupons. Separate namespaces; don't try an Event402 code here.
Sample phrasing for the user
- "Before searching for flights, let me check Flight402's promotions — that's where the active coupons live. (Worth re-running this every now and then; campaigns rotate.)"
- "There's an active
SPRING25promotion — 25% off flight orders of 200 USDC+, valid through 2026-05-31. Want me to validate it against your route?" - "No usable promotions right now. Want me to just search at list price, or do you have a code you'd like me to validate?"
- "Verified price changed from 180 to 195 USDC. Re-checking the coupon — still valid, expected saving now ~49 USDC. Shall I book?"
Don'ts
- Don't book or pay on the user's behalf — confirm before every step. Ordering holds inventory; payment spends USDC; both are irreversible.
- Don't brute-force guess codes — only validate codes surfaced by the promotions board or pasted by the user.
- Don't apply coupons to ancillary purchases — they don't work there.
- Don't mix event coupons and flight coupons — separate namespaces.
- Don't touch
/admin/*— that's operator-side, out of scope.