WiFi Odds
Inflight WiFi as a forecast · figures checked daily · this build 25 Jul 2026
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.

Reported Fleet and per-tail verification for United, Alaska and Hawaiian: unitedstarlinktracker.com and alaskastarlinktracker.com (@martinamps), 25 JUL 2026. Every other airline from public airline announcements, Jul 2026. Full credit and citation →

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. Same order as the leaderboard, from the same function.

{ "count": 18, "asOf": "2026-03", "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": 61, "band": "good", "nextGenScore": 58, "nextGen": { "score": 58, "system": "starlink", "pct": 58 }, "serviceTier": "mixed", "service": { "tier": "mixed", "label": "mixed", "rest": "basic" }, "system": { "key": "starlink", "label": "Starlink", "quality": 1.00 }, "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, and the response carries more: floor, ceiling, resolution and the segments[] array the ledger is drawn from. Qatar Airways really is 61: Starlink 140 at 58.1 + Inmarsat or SITA 53 at 3.4 + no wifi 48 at 0.0 points. 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 offer is the airline’s fleet-wide ConnectScore. prob is null and not a guess. Inventing precision here would be the worst thing this API could do.
{ "flight": "UA212", "airline": { "key": "united", "connectScore": 48, … }, "prob": 47, "connectScore": 48, "method": "route-history", "evidence": { "route": "LAX-ORD", "observations": 20, "confidence": "high", "dataset": "routeCache", "cachedAt": "…" }, "asOf": "2026-07-25", "sources": [ … ] }

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

Two numbers, not one

read both before you quote either

Every airline object carries connectScore and nextGenScore, and they answer different questions. Next-gen odds = share of the fleet flying Starlink or Amazon Leo today × free-for-you. Signed-but-unflown deals count zero. The second line is what the fleet actually delivers today: next-gen, streaming-class, basic, or mixed.

FieldWhat it isDelta, as an example
nextGenScoreOdds of a Starlink or Amazon Leo aircraft × free-for-you. A signed deal contributes nothing.0
connectScoreThe whole fleet, segment by segment: share × system quality × free-for-you, added up. Credits streaming-class geostationary service at 0.55 and no connectivity at 0.49
serviceTiernext-gen · streaming · basic · mixed. What the fleet delivers today. service.rest is the tier on the part that is not next-gen yet, and "unknown" where we have not verified it.streaming

Delta is the case worth internalising: 0 next-gen odds and free, fleetwide, genuinely streaming-class Viasat. Reporting either number without the other misleads. What the hardware actually does → · when each fleet finishes →

The score, field by field

nothing here is transcribed

connectScore = Σ segments[].share × segments[].quality × segments[].free.factor, rounded. It is the FLOOR; ceiling is the same sum at the top of every unpublished split.

ConnectScore is an expected value over a whole fleet, not a prediction about one flight: United measured 320, 56 and 15 Mbps on three systems in one livery in one reporting period. Aircraft whose system an airline does not publish are left out of the denominator rather than assumed. Signed-but-unflown deals (AA Starlink 2027, DL/B6 Amazon Leo) score zero until they fly.

system.quality

keyqLabelTier
starlink1.00Starlinklow-earth orbit
leo1.00Amazon Leolow-earth orbit
viasat0.55Viasatmodern geostationary
intelsat0.55Intelsatmodern geostationary
2ku0.552Kumodern geostationary
hughes0.55Hughesmodern geostationary
thales0.55Thales FlytLIVEmodern geostationary
panasonic0.22Panasoniclegacy geostationary
inmarsat0.22Inmarsatlegacy geostationary
sita0.22SITAlegacy geostationary
anuvu0.22Anuvulegacy geostationary
geo0.22legacy GEOlegacy geostationary
atg0.12Gogo ATG-4air-to-ground
ean0.12EANair-to-ground
none0.00no wifino connectivity

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_scoresnoneAll 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, so 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 fleet-wide expected value is not a prediction about the aircraft you are assigned.