WiFi Odds
ConnectScore API v0

The ConnectScore API

Every ConnectScore on this site, as JSON. Free, no key, no accounts, no rate limit, CORS open to every origin. Read-only. Each response carries a sources array with the data credits — please keep it attached when you re-publish.

DATA CREDITFleet verification for the instrumented airlines comes from the independent community trackers unitedstarlinktracker.com and alaskastarlinktracker.com, built by @martinamps. All credit for that data goes to them ↗. Every other airline is compiled from public airline announcements (July 2026). WiFi Odds is unofficial and not affiliated with any airline, SpaceX/Starlink, Amazon, Viasat, or the trackers.

Try it

curl -s https://wifiodds.com/api/airlines | head -40 curl -s https://wifiodds.com/api/airlines/qatar curl -s https://wifiodds.com/api/score/UA212 curl -s https://wifiodds.com/api/score/AS15

Responses are pretty-printed and gzipped. Cache-Control: public, max-age=3600 on success (the data is refreshed once a day), 300 on errors.

Endpoints

four, all GET
MethodPathReturns
GET/apiThis index: every endpoint, the airline keys, the flight-number prefixes.
GET/api/airlinesAll 18 airlines, best ConnectScore first.
GET/api/airlines/{key}One airline. Unknown key → 404 JSON with the list of valid keys.
GET/api/score/{flightNumber}Per-flight odds where we have route history, otherwise the coarse airline score. Untracked prefix → 404 JSON.

GET /api/airlines

All 18 airlines, ordered by ConnectScore descending, ties broken by name — the same order as the leaderboard, from the same function.

{ "count": 18, "asOf": "2026-07", "order": "connectScore desc, then name", "airlines": [ … 18 objects … ], "sources": [ … ] }

GET /api/airlines/{key}

Keys are the slugs used in the site URLs: airbaltic · aircanada · airfrance · alaska · american · britishairways · delta · emirates · hawaiian · jetblue · jsx · qatar · sas · southwest · united · virginatlantic · westjet · zipair

{ "airline": { "key": "qatar", "name": "Qatar Airways", "code": "QR", "connectScore": 58, "band": "mixed", "system": { "key": "starlink", "label": "Starlink", "quality": 1.0 }, "free": { "status": "free", "factor": 1.00 }, "fleet": { "equipped": 140, "total": 241, "equippedShare": 0.5809, "equippedPct": 58, "basis": "tail-counts" }, "perFlightOdds": false, "tracker": null, "future": null, "note": "…", "asOf": "2026-07", "url": "https://wifiodds.com/airlines/qatar/" }, "sources": [ … ] }

Abridged. Qatar Airways really is 58 — 58% of the fleet × 1.0 system quality × 1.00 free-for-you. Same number on the page →

GET /api/score/{flightNumber}

Accepts UA212, ua 212, UA0212 — case, spaces, hyphens and leading zeros are all normalised away. Prefixes we know: AA AC AF AS B6 BA BT DL EK HA QR SK UA VS WN WS XE ZG

The method field is the whole point of this endpoint. It tells you how much to trust the number, and it is never blurred:

methodprobWhat it means
route-history0–100We found this exact flight number in our cached United route history. prob is the share of recent observations of that flight that were flown by a Starlink aircraft, with the observation count and confidence in evidence.
airline-coarsenullWe have no per-flight history for it, so all we can honestly offer is the airline’s fleet-wide ConnectScore. prob is null rather than a guess — inventing precision here would be the worst thing this API could do.
{ "flight": "UA212", "airline": { "key": "united", "connectScore": 27, … }, "prob": 47, "connectScore": 27, "method": "route-history", "evidence": { "route": "LAX-ORD", "observations": 20, "confidence": "high", "dataset": "routeCache", "cachedAt": "…" }, "asOf": "2026-07-24", "sources": [ … ] }

United is the only fleet with per-flight history today; every other prefix returns airline-coarse. Rank a whole route →

The score, field by field

nothing here is transcribed

connectScore = fleet.equippedShare × system.quality × free.factor, rounded

ConnectScore is the chance of getting the GOOD system, not of any wifi at all. Legacy satellite service on the not-yet-converted part of a fleet is not credited. Signed-but-unflown deals (AA Starlink 2027, DL/B6 Amazon Leo) score zero until they fly.

system.quality

keyqLabel
starlink1.0Starlink
leo1.0Amazon Leo
viasat0.6Viasat
2ku0.62Ku
intelsat0.6Intelsat
geo0.3legacy GEO
panasonic0.3Panasonic
none0.0

free.status

statusfactorMeans
free1.00free onboard
loyalty-free1.00free for loyalty members
loyalty-tier0.85free on paid status tiers
partial0.85free on some cabins/routes
unknown0.85free status unconfirmed
paid0.70paid

fleet.basis

tail-counts — the airline publishes equipped and total aircraft, so equippedShare = equipped / total.

fleetwide-coverage — no tail counts exist (Delta, jetBlue publish only “fleetwide”), so equipped and total are null and the share comes from a stated coverage fraction.

A future deal is never scored. future is reported and contributes zero until the hardware flies.

Errors

always JSON, always with sources
StatuscodeWhen
400unparseable_flightThe path segment is not shaped like a flight number.
404unknown_airlineNo airline with that key.
404unknown_airline_prefixThe flight number parses, but we do not track that carrier.
405method_not_allowedRead-only API — GET or HEAD.
503dataset_unavailableThe cached United dataset could not be read from this deploy. This should never happen; it means the deploy is broken, and we would rather say so than quietly hand back the coarse score as if nothing were wrong.
$ curl -s https://wifiodds.com/api/score/XX999 { "error": { "status": 404, "code": "unknown_airline_prefix", "message": "We do not track airline \"XX\" …" }, "docs": "https://wifiodds.com/api/docs/", "flight": "XX999", "prefix": "XX", "prefixes": [ … ], "sources": [ … ] }

MCP server

for AI assistants

There is also an MCP endpoint, so an AI assistant can look these numbers up itself instead of guessing from whatever it remembers about airline WiFi. It speaks streamable HTTP — JSON-RPC 2.0 over POST /mcp, no key, no account, CORS open, and no session required for a single call.

ToolArgumentsReturns
get_airline_scorekeyOne airline: ConnectScore, system, fleet share, cost onboard, and the confidence tier the number comes from.
list_airline_scoresAll 18 airlines, best odds first. One call instead of eighteen.
score_flightflight_numberOne flight. United returns a per-flight probability with its sample size; every other carrier returns the coarse airline score with prob: null.
curl -sS -X POST https://wifiodds.com/mcp -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' curl -sS -X POST https://wifiodds.com/mcp -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params": {"name":"score_flight","arguments":{"flight_number":"UA212"}}}'

Every tool result comes back twice: a text block for the model to relay and a structuredContent object for the client to parse. The data credits are in both, because the text is what usually reaches the user.

The instructions field is the point

What initialize returns is not a description of the endpoints — it is the decision layer: someone asking about flight WiFi is trying to maximise hours of working WiFi, so prefer the higher ConnectScore, use United's route history when there is a flight number, name which confidence tier you are quoting, never invent a per-flight number for a fleet we have no history for, and always pass the credits through. A data endpoint with no opinion gets averaged into mush by whichever model is holding it.

It is the same code as the REST API

Each tool is a thin wrapper around the very handler that serves /api/** — it builds a synthetic GET request and re-shapes the answer. So score_flight("UA212") and GET /api/score/UA212 cannot disagree: they are the same call. The acceptance suite asserts exactly that.

GET returns 405, on purpose

This server opens no server-initiated SSE stream, so there is nothing for a GET to subscribe to. It answers with a 405 whose body tells you the POST to make instead — a bare 405 with no bytes is the kind of unhelpful signal this project has been bitten by before.

Using it

Credit is the only condition

The fleet numbers for United and Alaska are not ours — they come from unitedstarlinktracker.com and alaskastarlinktracker.com, the independent community trackers built by @martinamps. The sources array is in every response so that the credit travels with the data. Keep it, or reproduce it wherever you show the numbers.

This API never calls anyone else

It reads only our own cached dataset, from the same deploy that served this page. It does not proxy a tracker, an airline, or a flight-status provider — your traffic can never become their bill. That is also why per-flight odds exist for United only: it is the fleet we have history for.

No rate limit, so please cache

There is no key and no quota. Responses are cacheable for an hour and the underlying data changes once a day, so honour the Cache-Control header rather than polling. If it ever gets abused, a limit is the thing that appears first.

v0 means v0

Fields may be added at any time. Anything already here — connectScore, method, prob, sources — will not change meaning without the path changing to /api/v1/. Every response carries an x-connectscore-api header with the version.

Not a guarantee

ConnectScores and per-flight odds are historical estimates. Aircraft assignments change until departure, and a score is the chance of getting the good system, not of getting any WiFi at all.