Developers
Connect to the Upgrade Agent MCP server
Upgrade Agent exposes travel-upgrade intelligence — informational Q&A, live booking eligibility, pricing, and bidding — as a Model Context Protocol (MCP) server any AI agent or app can call. Powered by Plusgrade, the technology behind bid and instant upgrade programs at 65+ airlines, 18 cruise lines, and rail operators worldwide.
Endpoint
URL: https://www.upgradeagent.ai/api/mcp Transport: streamable-http Auth: OAuth 2.1 (sign-in) or Bearer token
Add it to a client
Omit any token and the connection opens a Plusgrade sign-in (OAuth 2.1, PKCE). Clients that support dynamic client registration (RFC 7591) register automatically.
claude mcp add --transport http upgrade-agent \ https://www.upgradeagent.ai/api/mcp
{
"mcpServers": {
"upgrade-agent": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://www.upgradeagent.ai/api/mcp"]
}
}
}Authentication
The server implements an OAuth 2.1 authorization-code flow with PKCE and dynamic client registration. Discovery metadata is public:
- Authorization server: /.well-known/oauth-authorization-server
- Protected-resource metadata: /.well-known/oauth-protected-resource
- MCP manifest: /.well-known/mcp.json
Tool catalog
Informational
ask_upgrade_agent— Authoritative answer to any general upgrade question.list_partners— Operators offering upgrades, filterable by vertical/region/product.get_partner_info— Full upgrade profile for one operator (products, loyalty, cabins).search_upgrade_options— Find operators by vertical, region, or product type.get_upgrade_pricing— Typical observed bid ranges for a carrier/cabin — no booking needed.partner_question_insights— Per-partner analytics report — demand, conversion, agent mix, visibility, content, pricing (partner-scoped tokens).partner_visibility_report— AI visibility for a partner: share of answer per engine, what agents say, where answers are wrong.usage_totals— Running totals of questions asked and answered.
Eligibility (secure, no PNR in transcript)
start_eligibility_check— Render a secure form so the traveler enters PNR + last name out-of-band.get_eligibility_result— Read the non-PII eligibility result for a session.check_upgrade_eligibility— Direct lookup when the traveler has already provided booking details.
Bidding
place_bid— Place an upgrade bid for a checked booking.modify_bid— Change a prepared or placed bid.get_bid_status— Existing bids and bid-eligible cabins with ranges.
Watcher
start_watch— Continuously watch a booking for upgrade improvements until departure.get_watch_status— Check a watch's status and any improvements found.
Booking transactions never expose a PNR to the model: PNR + last name are entered on a secure out-of-band page and only a non-PII session result is returned to the agent.
REST API (no MCP required)
The same quote → execute → confirm functions are available as plain HTTPS + JSON under https://www.upgradeagent.ai/api/v1, described by an OpenAPI 3.1 spec at /openapi.json. Same credentials as the MCP server (OAuth 2.1 access token or a partner-issued bearer token), and the same caller identity — a quote started over REST can be executed over MCP and vice-versa. Payment never passes through the API: every staged offer returns a paymentUrl on the operator's hosted page.
| Endpoint | What it does | MCP tool |
|---|---|---|
| POST /api/v1/quotes | Quote — live eligibility + options for a booking | check_upgrade_eligibility |
| POST /api/v1/quotes/secure | Quote without PII — traveler enters details on a secure page | start_eligibility_check |
| GET /api/v1/quotes/{quoteId} | Read a quote (or its pending status) | get_eligibility_result |
| POST /api/v1/offers | Execute — place a bid / instant / points offer | place_bid |
| PATCH /api/v1/offers/{ref} | Change an offer's amount (per-booking change cap applies) | modify_bid |
| DELETE /api/v1/offers/{ref} | Withdraw an offer (confirm=true required) | cancel_bid |
| GET /api/v1/offers | Confirm — live offer status, cart, options | get_bid_status |
| GET /api/v1/offers/{ref}/price | Itemized all-in cost before committing | get_price_breakdown |
| GET /api/v1/offers/events | Outcomes — poll bid-outcome events with a cursor | get_offer_events |
| POST /api/v1/webhooks | Outcomes — subscribe to signed bid-outcome webhooks | — |
| GET /api/v1/webhooks | List / inspect webhooks (+ DELETE /{id}, POST /{id}/test) | — |
| GET /api/v1/pricing | Typical observed bid ranges for an operator | get_upgrade_pricing |
| GET /api/v1/partners | Operators offering upgrades (filterable) | list_partners |
| GET /api/v1/partners/{code} | One operator's upgrade profile | get_partner_info |
curl -X POST https://www.upgradeagent.ai/api/v1/quotes \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"bookingReference":"ABC123","lastName":"Smith","carrier":"LX"}'
# → { "quoteId": "…", "status": "ELIGIBLE", "options": [ { "offerId": "…", "cabin": "BUSINESS",
# "bid": { "currency": "CHF", "minimum": 180, "suggested": 240, "maximum": 420 }, … } ],
# "changesRemaining": 3, … }curl -X POST https://www.upgradeagent.ai/api/v1/offers \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"quoteId":"…","purchaseType":"BID","amount":260,"cabin":"BUSINESS","confirm":true}'
# → { "offerReference": "…", "stage": "needs_payment", "status": "pending_payment",
# "paymentUrl": "https://…operator hosted page…", "changesRemaining": 3, … }curl "https://www.upgradeagent.ai/api/v1/offers?quoteId=…" -H "Authorization: Bearer $TOKEN"
# → { "offers": [ { "reference": "…", "status": "accepted", "amount": 260, "currency": "CHF", … } ], … }Webhooks & events
Bids settle 24–72 hours before departure — long after the conversation that placed them. Subscribe once with POST /api/v1/webhooks and we push offer.status_changed, offer.accepted, offer.declined, offer.cancelled and checkout.paid to your HTTPS endpoint, signed Stripe-style with a per-webhook secret. No endpoint? Poll GET /api/v1/offers/events?since=<cursor> (or the get_offer_events MCP tool) for the same ordered event log. Payloads carry references, status, amount, cabin and route — never a booking reference or last name. Deliveries retry 3× inline, then up to 8× over ~21 h with exponential backoff; 20 consecutive failures disable the webhook. Full details in the spec's webhooks section.
curl -X POST https://www.upgradeagent.ai/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"url":"https://agent.example.com/hooks/upgrade-agent","events":["offer.accepted","offer.declined","checkout.paid"],"quoteId":"…"}'
# → 201 { "id": "…", "secret": "whsec_…", ← shown once; store it
# "signing": { "header": "X-UpgradeAgent-Signature", "format": "t=<unix>,v1=<hex HMAC-SHA256(secret, t + "." + rawBody)>" } }
# Later, your endpoint receives:
# X-UpgradeAgent-Event: offer.accepted
# X-UpgradeAgent-Signature: t=1760000000,v1=5f1a…
# X-UpgradeAgent-Idempotency-Key: <quote>|<offerReference>|offer.accepted|accepted
# { "id": "…", "event": "offer.accepted", "quoteId": "…", "offerReference": "…",
# "status": "accepted", "previousStatus": "pending", "amount": 260, "currency": "CHF",
# "cabin": "Business", "segment": "ZRH-JFK", "occurredAt": "2026-…Z" }import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyUpgradeAgentWebhook(secret, signatureHeader, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.trim().split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // stale / replayed
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const got = Buffer.from(parts.v1 ?? "", "utf8"), want = Buffer.from(expected, "utf8");
return got.length === want.length && timingSafeEqual(got, want); // constant-time
}
// Verify over the RAW request bytes (before JSON.parse), answer 2xx within 10s,
// and dedupe on X-UpgradeAgent-Idempotency-Key — retries re-send the same key.
// Polling instead: GET https://www.upgradeagent.ai/api/v1/offers/events?quoteId=…&since=<nextCursor>Auth: Authorization: Bearer <token>. A missing or invalid token returns 401 with a WWW-Authenticate challenge pointing at /.well-known/oauth-protected-resource.
Errors are RFC 9457 problem documents (application/problem+json) with a machine-readable code — e.g. REASON_CHANGE_LIMIT (409) once a booking's offer-change cap is reached, REASON_PRICE_CEILING / REASON_PRICE_FLOOR (422) for an amount outside the operator's range, CONFIRMATION_REQUIRED (409) for a cancel without confirm. The booking reference and last name sent to POST /api/v1/quotes are used only for the upstream lookup — never stored, logged, or echoed.
Also machine-readable
- /openapi.json — OpenAPI 3.1 spec for the REST API
- /llms.txt — knowledge guide for LLMs
- /.well-known/ai-catalog.json — signed ARD catalog
- Upgrade Intelligence Report — observed bid ranges (dataset)