Error handling

Caching and monitoring: Caching & monitoring.

Platform errors (401, 403, 422, 429, 5xx, retry/backoff, rate-limit headers): Error Handling.

Aircraft lookup: found is not a 404

The registration endpoint always returns 200 when the request itself is valid — even when the tail number is not in the SkyLink aircraft registry. Do not treat a missing aircraft as an HTTP error.

SituationHTTP statusHow to detect
Registration known200found: true
Registration unknown200found: false
Malformed path segment404HTTP status code

Always check found before reading aircraft.* fields. Reading aircraft.icao_type on a found: false response will raise a KeyError.

Aircraft performance: 422 on unknown type

GET /aircraft/performance/{icao_type} returns 422 when the ICAO type designator is not recognised. This is a normal outcome when you chain from a registry lookup: not every icao_type value in the registry has a corresponding performance record.

ResponseMeaning
200Performance data returned
422Type designator not in performance database — degrade gracefully

Live ADS-B: empty aircraft[] is not an error

GET /adsb/aircraft returns 200 with aircraft: [] when no aircraft match the requested filter (radius, bbox, or callsign). This is a valid empty result — do not show an error banner. The feed is live; aircraft move in and out of your filter area in seconds.

Additionally, never cache live ADS-B responses. The feed refreshes every few seconds; a cached position is stale by the time the next render fires.

Historical ADS-B: plan gating and window errors

The /ultra/history/... endpoints require Pro plan or above. A 401 or 403 response includes a message key explaining the restriction — surface that message rather than a generic "access denied" string.

The /ultra/history/airport/{icao}/traffic endpoint requires the correct direction value:

CorrectIncorrect
direction=arrdirection=arrivals
direction=depdirection=departures

The /ultra/history/flights search endpoint returns 422 when the requested time window exceeds the path's maximum retention (90 days on /ultra/...). Narrow the window and retry.

import os
import requests

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"


def lookup_registration(registration: str) -> dict | None:
    """
    Returns aircraft dict or None when not found.
    Always checks the `found` field — never treats found=false as an error.
    """
    r = requests.get(
        f"{BASE}/aircraft/registration/{registration}",
        headers=HEADERS,
        timeout=(10, 15),
    )
    r.raise_for_status()
    data = r.json()
    if not data.get("found"):
        print(f"Registration {registration} not in registry (found=false).")
        return None
    return data["aircraft"]


def fetch_performance(icao_type: str) -> dict | None:
    """
    Returns performance data or None on 422 (type not in database).
    """
    r = requests.get(
        f"{BASE}/aircraft/performance/{icao_type}",
        headers=HEADERS,
        timeout=(10, 15),
    )
    if r.status_code == 422:
        print(f"Performance data not available for type {icao_type!r} (422).")
        return None
    r.raise_for_status()
    return r.json()


def fetch_history_with_plan_gate(registration: str) -> list[dict] | None:
    """
    Fetches recent historical flights. Surfaces the plan gate message on 401/403.
    """
    r = requests.get(
        f"{BASE}/ultra/history/flights",
        headers=HEADERS,
        params={"registration": registration, "limit": 5},
        timeout=(10, 25),
    )
    if r.status_code in (401, 403):
        msg = r.json().get("message", "Plan not entitled.")
        print(f"Access denied: {msg}")
        print("Upgrade to Pro or above for /ultra/history/... endpoints.")
        return None
    r.raise_for_status()
    return r.json().get("flights", [])


def main() -> None:
    # 1. Registration lookup — check found field
    ac = lookup_registration("N636JB")
    if ac:
        print(f"Found: {ac['registration']} / {ac['icao_type']} / {ac['owner_operator']}")
        # 2. Chain to performance — handle 422
        perf = fetch_performance(ac["icao_type"])
        if perf:
            print(f"Cruise speed: {perf.get('cruise_speed_ktas')} kt")
        else:
            print("Showing registration data only — no performance record.")
    else:
        print("Show empty state to user — aircraft not in registry.")

    # 3. Historical search — handle 401/403 plan gate
    flights = fetch_history_with_plan_gate("N636JB")
    if flights is not None:
        print(f"Last {len(flights)} archived flights retrieved.")


if __name__ == "__main__":
    main()

For 429 and 5xx responses, use the retry helper on Error Handling.


Related: Aircraft Lookup · Aircraft Performance · ADS-B · Historical ADS-B · Production patterns