cwsi.net Developer API

Fare class charts, mileage and status calculators, great circle distances and airport lounges, as JSON.

Overview

The API serves the same data as the cwsi.net website. It is plain REST over HTTPS, every response is JSON, and every request needs a key.

  • Base URL: https://api.cwsi.net. All endpoints live under /v1/.
  • Fares come from cwsi.net's fare class charts. Calculators run the same rules as the mileage calculators on the site.
  • Most successful responses carry a source field: the cwsi.net page for the same data, for you to link back to.
  • The full machine-readable description is the OpenAPI file.

Getting a key

Keys are issued by hand. Contact us, tell us what you are building, and say whether you need a secret key (server-side code) or a public key (a web page, in which case also list the website domains that will call the API). We will send the key by email.

A new key defaults to 10,000 requests a month and 60 requests a minute. Ask if you need more.

Authentication

There are two kinds of key.

KeyStarts withUse it
Secretcwsi_sk_From your server only, in an Authorization: Bearer header. It is rejected if the request carries an Origin header (a browser), and the API sends no CORS headers for it.
Publiccwsi_pk_From a web page, as ?key= in the URL or as a Bearer header. A browser request is accepted only when its Origin (or Referer, when there is no Origin) is one of the domains registered for the key. A public key request with no Origin and no Referer is refused with origin_not_allowed.

Secret key, from a server

curl https://api.cwsi.net/v1/airlines \
  -H "Authorization: Bearer YOUR_KEY"

Public key, from a web page

curl "https://api.cwsi.net/v1/airlines?key=YOUR_KEY" \
  -H "Origin: https://example.com"

Allowed domains are exact hosts, such as example.com, or wildcards such as *.example.com. A secret key sent as ?key= is refused with 401 invalid_key; send it as a Bearer header. A wildcard matches any subdomain but not example.com itself. Matching ignores case and port. The API answers browsers with CORS headers for the allowed origin.

Never put a secret key in a web page, a mobile app or a public repository. Anyone who can view the page can copy it. Use a public key in the browser. A public key is not a secret: the allowed-domains check stops other websites from using it in their visitors' browsers, but the key is visible in your page source, so someone could still call the API with it from a script. Each key has its own monthly allowance, and a key can be revoked and replaced within minutes, so tell us if one is being misused.

Limits and headers

Each key has a monthly allowance and a per-minute rate. Months are UTC. Every request that passes authentication counts toward the month, errors included; requests rejected for a missing, wrong or revoked key, or by a limit, do not count.

A guard also caps every /v1 request at 120 per minute per client IP address, whatever key is used (the OpenAPI document and CORS preflights are exempt).

Responses made with a valid key carry these headers:

HeaderMeaning
X-RateLimit-LimitThe key's monthly allowance.
X-RateLimit-RemainingRequests left this month.
X-RateLimit-ResetWhen the allowance resets: the first of next month, 00:00 UTC, as an ISO date-time.
Retry-AfterOn a 429 only: seconds to wait before trying again.

When a limit is reached you get a 429 with Retry-After, and the error carries a reset time:

{
  "error": {
    "code": "monthly_limit",
    "message": "The monthly allowance for this key is used up.",
    "reset": "2026-11-01T00:00:00Z"
  }
}

Endpoints

All endpoints are GET except the calculator, which is POST. The samples below are trimmed to a few items.

List airlines

GET/v1/airlines

No parameters. Returns every airline with a fare chart, with its code, name and the cwsi.net pages for its fares and calculator (calculator is left out when the airline has none).

curl https://api.cwsi.net/v1/airlines \
  -H "Authorization: Bearer YOUR_KEY"
{
  "airlines": [
    {
      "code": "AS",
      "name": "Alaska Airlines",
      "fares_url": "https://cwsi.net/fares/alaska/?utm_source=api&utm_medium=api&utm_campaign=airlines",
      "calculator": "https://cwsi.net/calc/asa/?utm_source=api&utm_medium=api&utm_campaign=airlines"
    },
    {
      "code": "G4",
      "name": "Allegiant Air",
      "fares_url": "https://cwsi.net/fares/allegiant/?utm_source=api&utm_medium=api&utm_campaign=airlines",
      "calculator": "https://cwsi.net/calc/g4/?utm_source=api&utm_medium=api&utm_campaign=airlines"
    }
  ]
}

Fare chart

GET/v1/fares/{airline}

GET/v1/fares/{airline}/{class}

airline is a code from /v1/airlines, such as DL. The first form returns the airline's whole chart. The second returns only the matching fare, by letter such as K (or by fare name where an airline uses names). An unknown airline or fare gives 404. Each fare has a fare, a group, a description and a list of attributes (name and value pairs that vary by airline).

curl https://api.cwsi.net/v1/fares/DL/K \
  -H "Authorization: Bearer YOUR_KEY"
{
  "airline": "DL",
  "name": "Delta Air Lines",
  "columns": ["Fare", "Cabin / fare type", "Miles %", "MQD %"],
  "fares": [
    {
      "fare": "K",
      "group": "Delta Main",
      "description": "Delta Main",
      "attributes": [
        { "name": "Miles %", "value": "75%" },
        { "name": "MQD %", "value": "15%" }
      ]
    }
  ],
  "source": "https://cwsi.net/fares/delta/?utm_source=api&utm_medium=api&utm_campaign=fares"
}

Compare fare classes

GET/v1/fares/compare?a=AIRLINE:FARE&b=AIRLINE

a is an airline and a fare, joined by a colon. b is the airline to compare against; it uses the same fare letter as a unless you write b=AIRLINE:FARE to pick a different one. Compare supports only AA, AS, DL, WN and UA; any other airline in a or b gives 422 invalid_input, as does a missing or malformed parameter. If a fare letter is not on one of the two airlines' charts, the answer is 404 not_found.

curl "https://api.cwsi.net/v1/fares/compare?a=DL:K&b=UA:M" \
  -H "Authorization: Bearer YOUR_KEY"
{
  "a": {
    "airline": "DL",
    "airline_name": "Delta Air Lines",
    "fares": [
      {
        "fare": "K", "group": "Delta Main", "description": "Delta Main",
        "attributes": [
          { "name": "Miles %", "value": "75%" },
          { "name": "MQD %", "value": "15%" }
        ]
      }
    ]
  },
  "b": {
    "airline": "UA",
    "airline_name": "United Airlines",
    "fares": [
      {
        "fare": "M", "group": "Economy", "description": "Economy",
        "attributes": [ { "name": "Miles %", "value": "100%" } ]
      }
    ]
  },
  "source": "https://cwsi.net/farecompare/?a=DL&f=K&b=UA&g=M&utm_source=api&utm_medium=api&utm_campaign=compare"
}

List calculators

GET/v1/calculators

Lists each airline's calculator and the inputs it accepts. An input is a number (with min and max; the price input also names its currency), a choice (with its options and a default), segments (the flights, an array of objects with the listed fields) or text (free text, such as a booking class letter). An input that must be sent carries "required": true. Use this list to build the body for the calculator call below.

curl https://api.cwsi.net/v1/calculators \
  -H "Authorization: Bearer YOUR_KEY"
{
  "calculators": [
    {
      "airline": "DL",
      "name": "Delta Air Lines",
      "path": "https://cwsi.net/calc/dl/?utm_source=api&utm_medium=api&utm_campaign=calculators",
      "inputs": [
        { "key": "price", "label": "Ticket price", "type": "number", "min": 0, "max": 1000000, "currency": "USD" },
        {
          "key": "fare", "label": "Fare type", "type": "choice", "default": "CR",
          "options": [
            { "value": "CR", "label": "Classic" },
            { "value": "EX", "label": "Extra" }
          ]
        }
      ]
    }
  ]
}

Estimate miles and status

POST/v1/calc/{airline}

Send a JSON body with price (a number above 0, up to 1,000,000) plus any of the airline's other inputs from /v1/calculators. The price is in US dollars, except British Airways (BA, pounds, GBP), Air Canada (AC, Canadian dollars, CAD), and Lufthansa (LH), Air France-KLM (AF), Iberia (IB) and Finnair (AY, euros, EUR), Air India (AI, Indian rupees, INR), China Eastern (MU, yuan, CNY), Malaysia Airlines (MH, ringgit, MYR), Air Europa (UX, euros, EUR), South African Airways (SA, rand, ZAR) and Xiamen Airlines (MF, yuan, CNY); the price input's currency says which. Lufthansa and Air France-KLM also need the flights, sent as segs: an array with one object per one-way flight, in order, holding fromCountry and toCountry (the ISO two-letter country codes of its two airports, such as "DE") and optionally from and to airport codes for the breakdown labels. Air France-KLM also needs each flight's miles (great-circle distance, which /v1/distance gives you), and takes an optional per-flight cabin; it decides Domestic from the two countries and the band from the miles. Lufthansa decides continental or intercontinental from the two countries; for a country on two continents, such as Turkey, add kind "C" or "I", or send only the counts cont and icf instead of segs. For KLM, call /v1/calc/KL (or send "airline": "KL" to /v1/calc/AF). Cathay Pacific (CX) is the exception: it earns by distance and booking class, so send no price; send miles (the flight distance, which /v1/distance gives you), cls and, for Economy classes, fare. For flights of 751 to 2,750 miles to or from Japan, Indonesia, Sri Lanka, Nepal, Bangladesh or India, also send t2 with that country code (JP, ID, LK, NP, BD or IN), or the flight is counted as Type 1. Korean Air (KE), Japan Airlines (JL), Singapore Airlines (SQ), ANA (NH) and Qantas (QF) also take no price: they earn by distance, so send segs (required), one object per one-way flight with its miles (from /v1/distance), fromCountry and toCountry, plus from and to, the IATA codes of its two airports. Send the codes: JAL and ANA use them to spot Vladivostok (VVO), and Qantas picks its route tables from them, so without them a flight such as Sydney to London falls back to the distance band. For Qantas, a flight may also carry table, the key of a route table (listed with the segs input in /v1/calculators), for example EWC or EEC for the West or East Coast USA/Canada, which Qantas does not define by city, or BND for the distance band; Qantas takes the earn category cat rather than a booking class. Qatar Airways (QR), Turkish Airlines (TK), Virgin Atlantic (VS) and EVA Air (BR) work the same way: no price, and segs with miles and the two country codes (Turkish Airlines treats a flight with both airports in TR as domestic). Iberia (IB) takes the price in euros and also needs segs with miles, the two country codes and from and to: Iberia uses the airport codes to spot the Madrid-Barcelona Air Shuttle (MAD-BCN), which earns its own Avios and Extra Elite Points; a flight may also carry cabin (Y, W or C). Finnair (AY) needs only the price in euros (plus optional extras). Aeromexico (AM) and Avianca (AV) need only the price in US dollars. Copa Airlines (CM), TAP Air Portugal (TP) and LOT Polish Airlines (LO) take no price: send segs with miles and the two country codes (TAP and LOT read the zone, such as within Europe, from the countries). SAS (SK) takes no price and no miles: it pays a fixed number of points per flight by zone, so send segs with the two country codes and, for long-haul flights, from and to, since SAS names those tables by city (New York, Chicago, Tokyo and so on). Air India (AI) and China Eastern (MU) need only the price, in rupees and in yuan. Air China (CA), China Airlines (CI) and Thai Airways (TG) take no price: send segs with miles and the two country codes (Air China uses them to tell a flight within Mainland China, CN to CN, from an international one). Air New Zealand (NZ) takes no price and no miles: it pays a fixed number of Airpoints and Status Points per flight from its own tables, keyed by the airport pair, so send segs with from and to, the IATA codes of each flight's two airports (the country codes are optional there); Airpoints can be decimals, such as 10.8. ITA Airways (AZ), Royal Air Maroc (AT) and Vietnam Airlines (VN) take no price: send segs with miles and the two country codes (ITA reads its European Domestic and Cross-Border zones from them, Vietnam Airlines its domestic rate). Malaysia Airlines (MH) takes the price in ringgit and also needs segs with miles, the two country codes and from and to: Elite Points are worked out only for a flight to or from Kuala Lumpur (KUL), and any other flight shows as not worked out; a flight may also carry cabin (Y, C or S). Air Europa (UX) needs only the price in euros. Aerolineas Argentinas (AR), Royal Jordanian (RJ) and SriLankan Airlines (UL) take no price: send segs with miles and the two country codes (Aerolineas Argentinas reads its domestic, regional and international zones from them and prices only flights to or from Argentina; Royal Jordanian also uses from and to to count Amman to Aqaba or Tel Aviv as half a segment). Aegean Airlines (A3) takes no price and no miles: it pays a fixed number of miles per airport pair from its own route table, so send segs with from and to (the country codes are optional; Gold members need toCountry for the Schengen bonus). South African Airways (SA) needs only the price, the base fare plus carrier-imposed fees in rand. Croatia Airlines (OU) and Shenzhen Airlines (ZH) take no price: send segs with miles and the two country codes (Croatia Airlines pays fixed miles by booking class within Croatia or between European countries and refuses other routes; Shenzhen Airlines tells a flight within Mainland China, CN to CN, from an international one). Xiamen Airlines (MF) needs only the price, the base fare plus fuel surcharge in yuan for one flight. Etihad Airways (EY) takes no price and no miles: it pays a share of Etihad's own base number for each route from Abu Dhabi, so send segs with from and to on Etihad's network (a journey between two other cities is priced as two flights via Abu Dhabi). Inputs you leave out take their default. The result has a headline, the line-by-line rows (the same list also appears as breakdown), and, for some airlines, progress toward status and notes. The inputs field echoes what was used after defaults; a choice with no value, such as no status, appears as "" there although you send it as "none".

Credit this flight to a partner program. Every calculator whose airline has partner earning in our data also takes creditTo, the key of a partner program (american, alaska, delta, united, aeroplan, flying-club, flying-blue or ba-club; /v1/calculators lists the ones each airline offers, never its own program), and creditStatus, your status there by name (the base level if you leave it out). The answer is then what the flights earn in that program, from its published earning on that airline, by each flight's distance and the booking class, as on our partner pages: no price is needed. The class is the calculator's own cls where it has one, otherwise send creditClass (one letter). The flights are the segs where they carry miles; otherwise send creditFlights, one object per one-way flight with its miles (from /v1/distance) and optional from and to. Lufthansa, Air France-KLM and Air India use the pair of the airline named by op, airline or carrier (SWISS flights earn as SWISS, KLM flights as KLM).

curl https://api.cwsi.net/v1/calc/AA \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"creditTo": "alaska", "creditStatus": "Atmos Gold", "creditClass": "Y", "creditFlights": [{"miles": 3442, "from": "LHR", "to": "JFK"}]}'
curl https://api.cwsi.net/v1/calc/DL \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"price": 450, "fare": "EX", "status": "GM"}'
{
  "airline": "DL",
  "calculator": "Delta SkyMiles Calculator",
  "inputs": { "price": 450, "fare": "EX", "status": "GM", "card": "" },
  "headline": [
    "4,500 miles",
    "$450 MQDs toward Medallion Status"
  ],
  "rows": [
    { "label": "Extra fare", "detail": "7 miles per $1", "value": "3,150 mi" },
    { "label": "Gold bonus", "detail": "+3 miles per $1", "value": "1,350 mi" }
  ],
  "breakdown": [
    { "label": "Extra fare", "detail": "7 miles per $1", "value": "3,150 mi" },
    { "label": "Gold bonus", "detail": "+3 miles per $1", "value": "1,350 mi" }
  ],
  "progress": {
    "title": "Toward 2027 Medallion Status",
    "earned": "$450",
    "tiers": [
      { "name": "Silver", "threshold": "$5,000" },
      { "name": "Gold", "threshold": "$10,000" }
    ]
  },
  "notes": [],
  "source": "https://cwsi.net/calc/dl/?utm_source=api&utm_medium=api&utm_campaign=calc"
}

A bad input gives 422 invalid_input. The message names every bad field and error.fields lists them:

{
  "error": {
    "code": "invalid_input",
    "message": "foo: unknown field; price: must be 1000000 or less",
    "fields": ["foo", "price"]
  }
}

Great circle distance

GET/v1/distance?route=ATL-JFK

route is airport codes (IATA or ICAO) joined by -. Separate trips with ~, for example ATL-SFO~SJC-ATL. Up to 5 trips and 20 airports. Distances are in statute miles (mi), nautical miles (nm) and kilometres (km), per leg, per trip and in total. Codes the service does not know are listed in unknown.

curl "https://api.cwsi.net/v1/distance?route=ATL-JFK" \
  -H "Authorization: Bearer YOUR_KEY"
{
  "ok": true,
  "unknown": [],
  "mi": 748,
  "nm": 650,
  "km": 1204,
  "trips": [
    {
      "mi": 748, "nm": 650, "km": 1204,
      "legs": [
        { "from": { "code": "ATL" }, "to": { "code": "JFK" }, "mi": 748, "nm": 650, "km": 1204 }
      ]
    }
  ],
  "source": "https://cwsi.net/gcdc/?r=ATL-JFK&utm_source=api&utm_medium=api&utm_campaign=distance"
}

Airport lounges

GET/v1/lounges/{airport}

airport is a 3 letter IATA code. Returns the airport and its lounges, grouped by cwsi.net's Lounge Finder. An airport we do not cover gives 404.

curl https://api.cwsi.net/v1/lounges/ATL \
  -H "Authorization: Bearer YOUR_KEY"
{
  "airport": "ATL",
  "code": "ATL",
  "name": "Hartsfield-Jackson",
  "city": "Atlanta",
  "state": "GA",
  "country": "US",
  "region": "us",
  "lounges": [
    {
      "name": "Delta Sky Club",
      "brand": "delta",
      "brandName": "Delta Sky Club",
      "group": "skyteam",
      "terminal": "Domestic",
      "concourse": "B",
      "gate": "B",
      "airside": true,
      "hours": "5am-10pm",
      "access": "Members",
      "url": "https://delta.com"
    }
  ],
  "source": "https://cwsi.net/lounges/atl/?utm_source=api&utm_medium=api&utm_campaign=lounges"
}

Errors

Every error has the same shape, and the HTTP status matches:

{ "error": { "code": "not_found", "message": "No such endpoint. See /v1/openapi.json." } }
StatusCodeMeaning
401missing_keyNo key was sent, or it is not a well-formed key.
401invalid_keyThe key is not recognised.
403revoked_keyThe key has been revoked.
403origin_not_allowedA secret key was used from a browser, or a public key was used from a domain that is not registered for it.
404not_foundNo such endpoint, airline, fare class (including a compare fare missing from either chart) or airport.
422invalid_inputA parameter or body field is missing or not valid, or a compare airline is not one of the five supported. Calculator validation errors, and errors for a missing number such as Alaska's miles flown, list the fields in error.fields.
429rate_limitedToo many requests in a minute. Wait Retry-After seconds before retrying; error.reset is the monthly reset date, not the end of the minute. The per-IP guard also answers rate_limited, with Retry-After: 60 and no X-RateLimit-* headers.
429monthly_limitThe monthly allowance is used up. error.reset says when it renews.
502upstream_unavailableThe data behind the call could not be reached. Try again shortly.

An unexpected failure on our side answers 500 internal_error.

OpenAPI description

The whole API is described in an OpenAPI 3.1 file. It needs no key.

https://api.cwsi.net/v1/openapi.json

Data notes

  • Fares come from the fare class charts on cwsi.net, which are compiled from each airline's published rules. They can lag a change the airline made yesterday.
  • Calculator results are estimates. Airlines change earning rates, and your actual award depends on the booking. Treat the numbers as guidance.
  • Lounge hours and access change often. Link your users to the response's source or to the lounge's own url to confirm before they travel.
  • Attribution is required. Where you show our data, credit cwsi.net and link to the source URL from the response.
  • Airline names and logos belong to their owners. cwsi.net is not affiliated with any airline.

Questions, or a key request? Contact us.