# OpenSky Live Flight Tracking Scraper (`scrapyx/opensky-flights-scraper`) Actor

Live ADS-B aircraft positions worldwide plus airport arrival and departure history, from the OpenSky Network. Turns OpenSky's bare positional arrays into named fields, strips padded callsigns and keeps barometric and GNSS altitude apart.

- **URL**: https://apify.com/scrapyx/opensky-flights-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** Travel, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.75 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OpenSky Live Flight Tracking Scraper

Live ADS-B aircraft positions worldwide, plus airport arrival and departure
history, from the community-run **OpenSky Network**. Around 8,600 aircraft are
airborne at any moment.

Free, no API key, no login, no WAF.

### What this actor is actually for

OpenSky returns each aircraft as a **bare 17-element array**:

```json
["4bb143","THY4    ","Turkey",1787703164,1787703164,5.2191,52.5453,
 10668,false,255.01,122.73,0,null,10980.42,"1166",false,0]
```

Nothing in the payload says what any position means. Every consumer has to
carry the index table, and — worse — an index-based reader keeps *working* if
OpenSky ever inserts a column, silently returning altitude where velocity used
to be. This actor turns those into named fields, refuses a row whose layout
does not match rather than guessing, and fixes three things upstream gets
awkwardly right.

### Input

| Field | Type | Default | Meaning |
|---|---|---|---|
| `mode` | enum | `liveStates` | `liveStates` (airborne now) or `flights` (airport history). |
| `boundingBoxes` | string\[] | — | `lamin,lomin,lamax,lomax`, e.g. `51,3,53,6`. Empty = whole world. |
| `icao24Codes` | string\[] | — | Restrict to specific transponder addresses. |
| `airports` | string\[] | — | `flights` mode. ICAO codes, e.g. `EHAM`. |
| `direction` | enum | `both` | `flights` mode. |
| `hoursBack` | integer | `6` | `flights` mode, max 24 — see below. |
| `maxResultsPerQuery` | integer | `500` | `0` = unlimited. |

```json
{ "mode": "liveStates", "boundingBoxes": ["51,3,53,6", "40,-75,43,-70"] }
```

### Four things this actor will not let you get wrong

#### 1. Every callsign is whitespace-padded

`"THY4    "`, not `"THY4"` — measured on **8,436 of 8,450** non-empty callsigns
worldwide. Any join, group-by or lookup on the raw value fails silently and
completely. Stripped into `callsign`, with the wire value kept in
`callsignRaw`.

#### 2. There are two altitudes, both in metres, and they differ on every row

`baroAltitude` (barometric) and `geoAltitude` (GNSS) differed on **every single
row** measured — commonly by 300–450 m. On one live sample the gap was 457 m:
picking the wrong field is a **1,500 ft** error.

Both are published, in metres and feet, under names that say which is which,
plus `altitudeDeltaM`. Neither is presented as "the" altitude.

#### 3. Nulls are invisible in a small sample

A 12-aircraft bounding box showed **zero** nulls in every field. The identical
request over the whole world showed **97 aircraft with no position fix**, 895
with no barometric altitude, and 168 with a blank callsign.

A null latitude read as `0` puts an aircraft in the Gulf of Guinea, so
`hasPosition` is a field rather than something you infer, and the summary
counts `aircraftWithoutPosition` and `aircraftWithoutAltitude` per run.

#### 4. A `404 []` means "no flights" *or* "no such airport" — identically

```
/flights/arrival?airport=EHAM   (quiet window)  -> 404 []
/flights/arrival?airport=ZZZZ   (no such code)  -> 404 []
```

Byte-identical. Upstream cannot tell you which it was, so the ICAO code shape
is validated **here** (4 letters), and when a well-formed code still returns
nothing the error is `no_results_or_unknown_airport` — which says plainly that
the two cannot be separated, instead of picking one and sounding confident.

#### Also: the anonymous history wall

A window 1 day back is served; **2 days back answers `403 "You cannot access
historical flights"`**. That 403 is reported as its own error and never folded
into "no results" — those are the two answers a user must not see confused.
`hoursBack` is capped at 24 with the measurement in the message.

### Output

Envelope on every row: `_input`, `_source`, `_scrapedAt`, `recordType`.

- **`AIRCRAFT_STATE`** — named fields, both altitudes in m and ft, speed in
  m/s, knots and km/h, vertical rate in m/s and ft/min, `positionSourceName`,
  ISO timestamps, `hasPosition`
- **`FLIGHT`** — departure/arrival airports, first/last seen, duration
- **`SEARCH_SUMMARY`** — counts, `creditsRemaining`, null counts,
  `paddedCallsignsCleaned`, `malformedRowsSkipped`
- **`ERROR`** — `_error` + `_errorDetail`

### Credits

Anonymous users get **400 credits a day, shared per IP**; a bounding box costs
1–4 depending on area. OpenSky publishes `x-rate-limit-remaining` — unusual
enough that this actor surfaces it as `creditsRemaining` on every summary. It
is reported, not used to predict cost: the counter is shared and was observed
to refill mid-run. A free account raises the budget.

# Actor input Schema

## `mode` (type: `string`):

'liveStates' is a snapshot of every aircraft airborne right now. 'flights' is arrival/departure history for an airport — anonymous access reaches back about a day.

## `boundingBoxes` (type: `array`):

liveStates only. Each box is 'lamin,lomin,lamax,lomax', e.g. '51,3,53,6' for the Netherlands. Leave empty for the whole world (~8,600 aircraft). Each box runs separately and gets its own summary row.

## `icao24Codes` (type: `array`):

liveStates only. Restrict to specific aircraft by hex transponder address, e.g. 4bb143.

## `airports` (type: `array`):

flights mode only. Four-letter ICAO codes, e.g. EHAM, KJFK, EGLL. Validated locally — OpenSky answers an unknown code with the SAME '404 \[]' it uses for a real airport with no flights, so a typo would otherwise look like 'nothing flew'.

## `direction` (type: `string`):

flights mode only.

## `hoursBack` (type: `integer`):

flights mode only. Anonymous OpenSky access reaches back about a day — measured, a window 2 days back answers 403. Capped at 24 here.

## `maxResultsPerQuery` (type: `integer`):

0 = unlimited. A whole-world liveStates snapshot is around 8,600 aircraft.

## `maxConcurrency` (type: `integer`):

Requests in flight at once. OpenSky's credit budget is per-IP, so a high value spends it faster rather than going faster.

## `minRequestInterval` (type: `number`):

Politeness pacing. Anonymous users get 400 credits a day; a bounding box costs 1–4 depending on its area.

## `proxyConfiguration` (type: `object`):

Recommended for cloud runs. No WAF was observed, but OpenSky's anonymous budget is 400 credits per DAY per IP — and on a shared cloud IP that budget is often already spent by other users, so a run can fail with rate\_limited through no fault of the query. Each proxy exit IP has its own budget.

## Actor input object example

```json
{
  "mode": "liveStates",
  "boundingBoxes": [
    "24,-125,49,-66",
    "35,-10,60,30"
  ],
  "icao24Codes": [
    "4bb143",
    "3c6444"
  ],
  "airports": [
    "EHAM",
    "EGLL"
  ],
  "direction": "both",
  "hoursBack": 6,
  "maxResultsPerQuery": 500,
  "maxConcurrency": 2,
  "minRequestInterval": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "boundingBoxes": [
        "51,3,53,6"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/opensky-flights-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "boundingBoxes": ["51,3,53,6"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/opensky-flights-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "boundingBoxes": [
    "51,3,53,6"
  ]
}' |
apify call scrapyx/opensky-flights-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/opensky-flights-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/D0DEiDE6eW7DnEe9h/builds/KvmpNQXVlzNUhLpif/openapi.json
