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.
| # | Question | Data used |
|---|---|---|
| 1 | What were the last 5 flights operated by tail number N636JB? | /ultra/history/flights |
| 2 | Full metadata for the most recent departure from EGLL | /ultra/history/flights → /ultra/history/flight/{flight_id} |
| 3 | How many arrivals did KJFK receive in the last 24 hours? | /ultra/history/airport/{icao}/traffic |
Requirements
x-api-key on every request (direct subscription). Learn more →/ultra/history/... requires Pro, Ultra, or Mega — 401/403 means your plan is below the threshold.
Install
The scripts below use requests for API calls and rich for terminal output. Install once:
pip install requests rich1. 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.