# Flight402 Admin API > Admin endpoints for managing Flight402 platform: admins, coupons, and refund approvals. ## Prerequisites Install the **FluxA Agent Wallet skill (≥ 0.4.5)** and the **`fluxa-wallet` CLI (≥ 0.4.5)** — earlier versions do not support the `agent-vc` subcommand required for admin authentication: https://fluxapay.xyz/skill.md Verify your CLI version with `fluxa-wallet --version`. Your agent_id must be added to the admins list before you can access these endpoints. The first admin is bootstrapped via server configuration. ## Authentication All `/admin/*` endpoints require a **FluxA Agent Verifiable Credential (VC)** and admin privileges. Mint a VC bound to this API. `--challenge` is required by the CLI but flight402 does not validate it — pass any non-empty string (the literal `flight402` works): ``` fluxa-wallet agent-vc \ --audience urn:flight402:api \ --challenge flight402 \ --ttl 3600 ``` Send the VC in the Authorization header: ``` Authorization: Bearer ``` flight402 verifies the VC locally against FluxA's JWKS (RS256, `typ=agent-vc`, `aud=urn:flight402:api`, unexpired). The admin's agent id is taken from the VC's `sub` claim and must exist in the `admins` table. Base URL: `https://flight402-production.up.railway.app` ## Admin Management ### List Admins `GET /admin/admins` List all admin users. Response: `{ admins: [{ agentId, addedBy, note, createdAt }] }` ### Add Admin `POST /admin/admins` Add a new admin. Request body: - `agentId` (string, required): Agent ID to grant admin access - `note` (string, optional): Label for this admin, e.g. "ops-lead" Response: `{ agentId, status: "added" }` ### Remove Admin `DELETE /admin/admins/{agentId}` Remove an admin. You cannot remove yourself. Response: `{ agentId, status: "removed" }` ## Promotion Bulletin Board Public-facing announcements of ongoing discount campaigns. Promotions are pure metadata — they describe the activity and (typically in the description) reference the coupon code agents should use. Creating, editing, or deleting a promotion does not touch coupons or usage records. ### Create Promotion `POST /admin/promotions` Request body: - `title` (string, required): Headline shown to users - `description` (string, required): Body text — explain the campaign and which coupon code applies - `startsAt` (string, optional): ISO timestamp; null means "live immediately" - `endsAt` (string, optional): ISO timestamp; null means "no end date" - `active` (boolean, optional, default `true`): Set false to hide from the public scope filters without deleting Response: `{ id, title, status: "created" }` ### List Promotions `GET /admin/promotions` Returns every promotion, including inactive and expired ones. Response: `{ promotions: [{ id, title, description, startsAt, endsAt, active, createdAt, updatedAt }] }` ### Get Promotion `GET /admin/promotions/{id}` Response: full promotion object. ### Update Promotion `PATCH /admin/promotions/{id}` All fields optional: `title`, `description`, `startsAt`, `endsAt`, `active`. Pass `null` for `startsAt`/`endsAt` to clear that bound. Response: updated promotion object. ### Delete Promotion `DELETE /admin/promotions/{id}` Hard-delete the announcement. Use this when a promotion was published in error; for normal end-of-campaign cleanup, prefer `PATCH active=false`. Response: `{ id, status: "deleted" }` ## Coupon Management ### Create Coupon `POST /admin/coupons` Create a new coupon. Request body: - `code` (string, required): User-facing coupon code, e.g. "WELCOME10" - `type` (string, required): "fixed" (fixed USDC amount off) or "percent" (percentage off) - `value` (number, required): Discount value — e.g. 10 means $10 off (fixed) or 10% off (percent) - `totalQuota` (integer, required): Total number of times this coupon can be used - `maxDiscount` (number, optional): Maximum discount in USDC (for percent type) - `minOrder` (number, optional): Minimum order amount in USDC to use this coupon - `perAgentLimit` (integer, optional, default 1): Max uses per agent - `validFrom` (string, optional): ISO timestamp for when coupon becomes valid - `validTo` (string, optional): ISO timestamp for when coupon expires - `description` (string, optional): Internal note Response: `{ id, code, status: "created" }` ### List Coupons `GET /admin/coupons` List all coupons with usage stats. Response: `{ coupons: [{ id, code, type, value, maxDiscount, minOrder, totalQuota, usedCount, remaining, perAgentLimit, validFrom, validTo, active, description, createdAt }] }` ### Get Coupon Details `GET /admin/coupons/{id}` Get a single coupon's details. ### Update Coupon `PATCH /admin/coupons/{id}` Update coupon settings. All fields are optional. Request body: - `active` (boolean): Enable or disable the coupon - `totalQuota` (integer): Change total available uses - `perAgentLimit` (integer): Change per-agent limit - `validFrom` / `validTo` (string): Change validity window - `maxDiscount` (number): Change discount cap - `minOrder` (number): Change minimum order amount - `description` (string): Change internal note Response: Updated coupon object. ### View Coupon Usages `GET /admin/coupons/{id}/usages` List all usage records for a coupon. Response: `{ usages: [{ id, agentId, orderId, discountUsdc, createdAt }] }` ## Order Management ### List All Orders `GET /admin/orders` List all orders across all agents with optional filters and pagination. Query parameters: - `status` (string, optional): Filter by order status — "pending_payment", "paid", "ticketing", "ticketed", or "cancelled" - `agentId` (string, optional): Filter by agent ID - `page` (integer, optional, default 1): Page number - `limit` (integer, optional, default 20, max 100): Orders per page Response: `{ orders: [{ orderId, agentId, status, pnr, atripOrderNo, totalPriceUsdc, couponId, couponDiscount, createdAt }], pagination: { page, limit, total, totalPages } }` ### Get Order Details `GET /admin/orders/{orderId}` Get full order details including passengers, flights, tickets, and payment info. Not restricted by agent — admins can view any order. Response: `{ orderId, agentId, status, pnr, atripOrderNo, totalPriceUsdc, priceBeforeCoupon, couponId, couponDiscount, atripPrice, atripCurrency, paymentLinkId, paymentLinkUrl, tktLimitTime, passengers, contact, flights, tickets, createdAt, updatedAt }` ## Refund Management Refunds submitted by agents go through manual review before USDC is returned. ### List Pending Refunds `GET /admin/refunds/pending` List all refunds awaiting review. Response: `{ reviews: [{ refundId, orderId, agentId, refundAmountUsdc, atripRefundCode, atripRefundStatus, reason, createdAt }] }` ### Approve Refund `POST /admin/refunds/{refundId}/approve` Approve a refund. Returns a UPL (Unify Payment Link) URL to send USDC back to the agent. Request body: - `reviewer` (string, optional): Reviewer identifier Response: `{ refundId, status: "approved", uplUrl, amountUsdc, agentId, message }` After approving, use the `uplUrl` to send the USDC refund to the agent via x402 payment, then call mark-paid. ### Reject Refund `POST /admin/refunds/{refundId}/reject` Reject a refund request. Request body: - `reviewer` (string, optional): Reviewer identifier - `reason` (string, optional): Rejection reason Response: `{ refundId, status: "rejected" }` ### Mark Refund as Paid `POST /admin/refunds/{refundId}/mark-paid` After sending USDC via the UPL URL, record the transaction hash. Request body: - `txHash` (string, required): On-chain transaction hash of the USDC transfer Response: `{ refundId, status: "paid", txHash }` ## Refund Approval Flow 1. `GET /admin/refunds/pending` — review pending refunds 2. `POST /admin/refunds/{refundId}/approve` — approve, get `uplUrl` 3. Pay the `uplUrl` via x402 to send USDC to the agent's wallet 4. `POST /admin/refunds/{refundId}/mark-paid` — record the `txHash`