Historical tracks

Runnable examples for searching archived flights, pulling full flight detail, and querying historical airport traffic. See the full Historical ADS-B reference for retention windows and plan requirements.

#QuestionData used
1What were the last 5 flights operated by tail number N636JB?/ultra/history/flights
2Full metadata for the most recent departure from EGLL/ultra/history/flights → /ultra/history/flight/{flight_id}
3How many arrivals did KJFK receive in the last 24 hours?/ultra/history/airport/{icao}/traffic

Requirements

Auth
x-api-key on every request (direct subscription). Learn more →
Plan

/ultra/history/... requires Pro, Ultra, or Mega — 401/403 means your plan is below the threshold.

Learn more →

Install

The scripts below use requests for API calls and rich for terminal output. Install once:

pip install requests rich

1. Last 5 flights by tail number

Question: What routes has N636JB flown most recently, and how long did each flight take?

Provide a registration filter and set limit=5 to retrieve only the most recent archived flights.

import os

import requests
from rich.console import Console
from rich.table import Table

console = Console()

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"

REGISTRATION = "N636JB"


def fetch_recent_flights(registration: str, limit: int = 5) -> list[dict] | None:
    r = requests.get(
        f"{BASE}/ultra/history/flights",
        headers=HEADERS,
        params={"registration": registration, "limit": limit},
        timeout=(10, 25),
    )
    if r.status_code in (401, 403):
        msg = r.json().get("message", "Plan not entitled.")
        console.print(
            f"[red]Access denied:[/red] {msg}\n"
            "[yellow]Upgrade to Pro or above for /ultra/history/... endpoints.[/yellow]"
        )
        return None
    r.raise_for_status()
    return r.json().get("flights", [])


def fmt_duration(seconds: int | None) -> str:
    if seconds is None:
        return "—"
    h, m = divmod(seconds // 60, 60)
    return f"{h}:{m:02d}"


def main() -> None:
    flights = fetch_recent_flights(REGISTRATION)
    if flights is None:
        return

    if not flights:
        console.print(f"[yellow]No archived flights found for {REGISTRATION}.[/yellow]")
        return

    table = Table(title=f"Recent Flights — {REGISTRATION}", border_style="cyan")
    table.add_column("Flight ID", style="dim")
    table.add_column("From")
    table.add_column("To")
    table.add_column("Departure (UTC)")
    table.add_column("Duration", justify="right")

    for f in flights:
        table.add_row(
            (f.get("flight_id") or "")[:8],
            f.get("departure_icao") or "—",
            f.get("arrival_icao") or "—",
            (f.get("departure_time") or "—")[:16],
            fmt_duration(f.get("duration_sec")),
        )

    console.print(table)


if __name__ == "__main__":
    main()

Output: A table of the five most recent archived flights: Flight ID (first 8 chars) | From | To | Departure time | Duration (h:mm).


2. Search → detail chain: full metadata for the most recent EGLL departure

Question: What aircraft, route, and timing details describe the most recent flight that departed from London Heathrow?

Two-step chain: search for the latest EGLL departure to get a flight_id, then fetch the full detail record including aircraft type and GPS spoofing flag.

import os

import requests
from rich.console import Console
from rich.panel import Panel

console = Console()

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"

DEPARTURE_ICAO = "EGLL"


def handle_plan_gate(r: requests.Response) -> bool:
    """Return True if request was blocked by plan gating."""
    if r.status_code in (401, 403):
        msg = r.json().get("message", "Plan not entitled.")
        console.print(
            f"[red]Access denied:[/red] {msg}\n"
            "[yellow]Upgrade to Pro or above for /ultra/history/... endpoints.[/yellow]"
        )
        return True
    return False


def fetch_latest_departure(departure_icao: str) -> str | None:
    r = requests.get(
        f"{BASE}/ultra/history/flights",
        headers=HEADERS,
        params={"departure_icao": departure_icao, "limit": 1},
        timeout=(10, 25),
    )
    if handle_plan_gate(r):
        return None
    r.raise_for_status()
    flights = r.json().get("flights", [])
    if not flights:
        return None
    return flights[0]["flight_id"]


def fetch_flight_detail(flight_id: str) -> dict | None:
    r = requests.get(
        f"{BASE}/ultra/history/flight/{flight_id}",
        headers=HEADERS,
        timeout=(10, 25),
    )
    if r.status_code == 404:
        return None
    if handle_plan_gate(r):
        return None
    r.raise_for_status()
    return r.json()


def fmt_duration(seconds: int | None) -> str:
    if seconds is None:
        return "—"
    h, m = divmod(seconds // 60, 60)
    return f"{h}h {m:02d}m"


def main() -> None:
    flight_id = fetch_latest_departure(DEPARTURE_ICAO)
    if not flight_id:
        console.print(f"[yellow]No recent departures found for {DEPARTURE_ICAO}.[/yellow]")
        return

    detail = fetch_flight_detail(flight_id)
    if detail is None:
        console.print(f"[yellow]Flight detail not available for ID {flight_id[:8]}.[/yellow]")
        return

    spoofing = detail.get("gps_spoofing_suspected")
    spoofing_str = "[red]YES — review track[/red]" if spoofing else "No"

    lines = [
        f"[bold]Registration:[/bold]    {detail.get('registration') or '—'}",
        f"[bold]Callsign:[/bold]        {detail.get('callsign') or '—'}",
        f"[bold]Aircraft Type:[/bold]   {detail.get('aircraft_type') or '—'}",
        f"[bold]Route:[/bold]           {detail.get('departure_icao') or '—'}  →  {detail.get('arrival_icao') or '—'}",
        f"[bold]Departure:[/bold]       {detail.get('departure_airport_name') or detail.get('departure_icao') or '—'}",
        f"[bold]Arrival:[/bold]         {detail.get('arrival_airport_name') or detail.get('arrival_icao') or '—'}",
        f"[bold]Dep time (UTC):[/bold]  {(detail.get('departure_time') or '—')[:16]}",
        f"[bold]Arr time (UTC):[/bold]  {(detail.get('arrival_time') or '—')[:16]}",
        f"[bold]Duration:[/bold]        {fmt_duration(detail.get('duration_sec'))}",
        f"[bold]Distance:[/bold]        {detail.get('distance_nm') or '—'} nm",
        f"[bold]GPS Spoofing:[/bold]    {spoofing_str}",
    ]
    console.print(
        Panel("\n".join(lines), title=f"Flight Detail — {flight_id[:8]}", border_style="cyan")
    )


if __name__ == "__main__":
    main()

Output: A detail panel with registration, callsign, aircraft type, full route (airport names), departure/arrival times, duration, distance, and GPS spoofing flag.


3. KJFK arrivals in the last 24 hours

Question: How many flights arrived at JFK in the past 24 hours, and what were the first five?

Compute start and end in UTC, query the airport traffic endpoint with direction=arr. Note that the available window is subject to your plan's retention limit (90 days on Ultra path).

import datetime
import os

import requests
from rich.console import Console
from rich.table import Table

console = Console()

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"

AIRPORT = "KJFK"


def fetch_airport_traffic(
    icao: str,
    start: str,
    end: str,
    direction: str = "arr",
) -> dict | None:
    r = requests.get(
        f"{BASE}/ultra/history/airport/{icao}/traffic",
        headers=HEADERS,
        params={"start": start, "end": end, "direction": direction, "limit": 50},
        timeout=(10, 25),
    )
    if r.status_code in (401, 403):
        msg = r.json().get("message", "Plan not entitled.")
        console.print(
            f"[red]Access denied:[/red] {msg}\n"
            "[yellow]Upgrade to Pro or above for /ultra/history/... endpoints.[/yellow]"
        )
        return None
    r.raise_for_status()
    return r.json()


def main() -> None:
    now = datetime.datetime.now(datetime.timezone.utc)
    start = (now - datetime.timedelta(hours=24)).strftime("%Y-%m-%dT%H:%M:%SZ")
    end = now.strftime("%Y-%m-%dT%H:%M:%SZ")

    result = fetch_airport_traffic(AIRPORT, start=start, end=end, direction="arr")
    if result is None:
        return

    flights = result.get("flights", [])
    total = result.get("count", len(flights))

    console.print(
        f"[bold cyan]{AIRPORT}[/bold cyan] received "
        f"[bold]{total:,}[/bold] arrivals in the last 24 hours."
    )

    if not flights:
        console.print("[yellow]No individual flight records returned.[/yellow]")
        return

    table = Table(title=f"Sample Arrivals — {AIRPORT} (first 5)", border_style="cyan")
    table.add_column("Callsign")
    table.add_column("From")
    table.add_column("Arrival time (UTC)")

    for f in flights[:5]:
        table.add_row(
            f.get("callsign") or "—",
            f.get("departure_airport_icao") or "—",
            (f.get("landing_time") or "—")[:16],
        )

    console.print(table)
    console.print(
        "[dim]Window is subject to your plan's retention limit "
        "(Ultra path: up to 90 days).[/dim]"
    )


if __name__ == "__main__":
    main()

Output: Total arrival count for the 24-hour window, followed by a sample table of the first five arrivals: Callsign | From | Arrival time.