Flight trackers are one of those apps that look much harder to build than they are. You open one, see thousands of tiny planes crawling across the planet, click one and find out it’s a Japan Airlines 787 heading to Bangkok, and it feels like there must be radar stations and a team of engineers behind it. There are, but they’re behind the data provider. For your app, a flight tracker comes down to fetching a list of flights with positions, putting them on a map, and keeping them moving. That makes it a great project for learning an API, with the bonus that the result is fun to look at.
I recently built one called AeroStack, which shows live flights on a 3D globe, using the Aviationstack API. In this article I’ll go through the API first: what it returns, what a real response looks like, and the quirks I ran into in the data. After that I’ll show how to vibe code a tracker of your own with an AI assistant, including the prompts and the parts worth checking yourself.
Table of Contents
What is the Aviationstack API?
Aviationstack is a REST API for flight data. Its main endpoint, /v1/flights, returns flights with their status, departure and arrival airports, scheduled and estimated times, delays, airline, flight number and aircraft. For flights that are being tracked, it also includes a live block with the plane’s current latitude, longitude, altitude, heading and speed. There are separate endpoints for reference data like airports, airlines and aircraft types.
According to Aviationstack’s pricing page, the free plan includes real-time flights and HTTPS, with 100 requests a month, for non-commercial use. Historical flights data, routes, schedules and future flights start on the Basic plan, which also raises the limit to 10,000 requests a month.
A live map is the obvious thing to build, but the same data powers a lot of smaller features. A travel app can show whether a booked flight is delayed, an airport pickup service can track when a flight actually lands, and a dashboard can count departures from a city. Once you’ve integrated the flights endpoint for one of these, the others are mostly filtering.
How do you get live flight positions from Aviationstack?
You call the flights endpoint with your access key and filter it to active flights, which are the ones currently underway. After signing up and copying the key from your dashboard, the request looks like this:
curl "https://api.aviationstack.com/v1/flights?access_key=YOUR_KEY&flight_status=active&limit=100"
The response has a pagination object and a data array. When I ran this, the pagination said there were 89,742 active flights in total, returned 100 at a time. Here’s one of them, a DHL Air cargo flight from Auckland to Melbourne, exactly as it came back:
{
"flight_date": "2026-10-27",
"flight_status": "active",
"departure": {
"airport": "Auckland International",
"timezone": "Pacific/Auckland",
"iata": "AKL",
"icao": "NZAA",
"delay": 0,
"scheduled": "2026-10-27T03:20:00+00:00",
"estimated": "2026-10-27T03:20:00+00:00",
"actual": "2026-10-27T03:03:00+00:00"
},
"arrival": {
"airport": "Melbourne - Tullamarine Airport",
"timezone": "Australia/Melbourne",
"iata": "MEL",
"icao": "YMML",
"scheduled": "2026-10-27T05:02:00+00:00",
"estimated": "2026-10-27T04:46:00+00:00"
},
"airline": { "name": "DHL Air", "iata": "HJ", "icao": "TMN" },
"flight": { "number": "7", "iata": "HJ7", "icao": "TMN7", "codeshared": null },
"aircraft": { "registration": "VH-EXZ", "iata": "B763", "icao": "B763", "icao24": "7C1795" },
"live": {
"updated": "2026-10-26T15:39:15+00:00",
"latitude": -38.6042,
"longitude": 161.485,
"altitude": 12192,
"direction": 269,
"speed_horizontal": 757.468,
"speed_vertical": 0,
"is_ground": false
}
}
I’ve removed a few empty fields like terminal and gate to keep it short. The live block is what you’ll draw on the map. The latitude and longitude put this plane over the Tasman Sea, and direction is its heading in degrees, so 269 means it’s flying almost due west, toward Melbourne. The numbers are metric: altitude is in metres (12,192 m is exactly 40,000 feet) and speed_horizontal is in kilometres per hour, which fits a Boeing 767 at cruise. If your audience thinks in feet and knots, convert before you display them.
Besides the flight status, you can filter by airline (airline_iata), flight number (flight_iata), departure airport (dep_iata) or arrival airport (arr_iata). Those filters are what you’ll use for search, like “all active flights into Lagos” with arr_iata=LOS.
Why don’t all active flights have a live position?
Not every active flight comes with a live block, and this was the first surprise when I looked at real data. Of the 100 active flights in my response, only 35 had one. The rest had a schedule, airports and an airline, but no position, so there was nothing to put on a map. If your code assumes live is always present, it will crash on the first flight without one. If it plots flights at 0, 0 when the coordinates are missing, you’ll get a crowd of planes floating off the coast of West Africa.
So the first job after fetching is to filter. Keep the flights where live.latitude and live.longitude are both present, and expect to fetch a lot more flights than you’ll show. Also check is_ground. “Active” includes planes that are taxiing or waiting at the gate. In my sample, 13 of the 35 flights with positions were on the ground, including the first flight in the response, a Japan Airlines 787 sitting at Tokyo Haneda with a speed of zero. You can show those differently, or hide them, depending on what your map is for.
The second surprise was codeshares. Airlines sell seats on each other’s flights under their own flight numbers, so one physical plane can appear in the data several times. In my sample, 84 of the 100 entries had a codeshared object, like a Singapore Airlines number (SQ2929) pointing at a Swiss flight (LX177). Draw all of those and you’ll stack several identical planes on top of each other. The fix is to keep only the operating flight, where flight.codeshared is null, or to de-duplicate by the aircraft’s registration.
What time zone are Aviationstack’s times in?
The departure and arrival times are in the local time of each airport, even though they end in +00:00. Look at the DHL flight again. Its actual departure is “2026-10-27T03:03:00+00:00”, but the live position was updated at 15:39 UTC on October 26, and the plane was already almost halfway to Melbourne. If 03:03 on the 27th were really UTC, the flight wouldn’t have taken off yet. It’s 03:03 in Auckland, which is 14:03 UTC on the 26th, and the arrival times are in Melbourne time. The flight_date follows the same rule, so it can be a day ahead of UTC.
Each side of the flight includes a timezone field, like Pacific/Auckland, and that’s what makes the times usable. Parse the timestamp without its offset and treat it as local time in that zone. In JavaScript, a date library with time zone support (Luxon, or date-fns with date-fns-tz) does this in a line or two. If you skip this, delays, progress bars and “lands in 40 minutes” labels will all be wrong by the size of the time difference, which can be more than half a day. The live.updatedtimestamp, on the other hand, is real UTC. In my test, every position had been updated between 15:39:07 and 15:39:35 UTC, and I fetched them at 15:55 UTC, so the positions were about 16 minutes old when they reached me.
How do you stay inside the request quota?
You fetch flights on your server on a fixed schedule and share that one result with every visitor, rather than letting each browser call the API. This matters more for a flight tracker than for most apps, because a live map wants fresh data all the time. On the free plan, 100 requests a month works out to about three a day, which is enough to build and test against but not to run a live map for the public. Even on paid plans, if every visitor’s browser polled the API every minute, ten visitors would use more requests than one shared server fetch every few minutes.
The pattern that works is a server route that calls Aviationstack, caches the result for a couple of minutes, and returns the cached copy to everyone who asks within that window. On Next.js, fetch with next: { revalidate: 120 } gives you this almost for free, and a Cache-Control header with s-maxage lets your CDN serve it without touching your server at all. Search queries are different, because each new search is a new request upstream, so give them their own, tighter rate limit per visitor. It also keeps the access key on your server, since the key travels in the query string and would otherwise be visible in the browser.
Refreshing every two minutes doesn’t mean the planes have to freeze for two minutes. You can move each plane forward along its direction at its speed_horizontal, a technique called dead reckoning. Count from live.updated, not from when you fetched the data: as we saw above, positions can be a quarter of an hour old when they arrive, and a plane at 757 km/h covers about 200 kilometres in that time. Between refreshes the same calculation keeps the planes moving, about 25 kilometres every two minutes at that speed, so on a world map the motion looks smooth and the correction at the next update is barely visible.
Finally, check errors in the response body. With an invalid key, Aviationstack returns a 401 with a body like {“error”: {“code”: “invalid_access_key”, “message”: “…”}}. The code is what you want to act on. Codes like usage_limit_reached tell you the monthly quota is gone, and your app should keep showing the last good data with a notice rather than wiping every plane off the map.
How to vibe code the flight tracker
With the API’s quirks understood, an AI coding assistant can build most of the app. I’d use a tool that edits a real project, like Claude Code or Cursor, and go one step at a time. Put the details from the sections above into your prompts. Left alone, an AI will fetch from the browser, plot every flight including the ones without positions, and treat local times as UTC, because nothing in the response tells it otherwise.
Start with the server route, since everything else depends on clean data:
Create a Next.js App Router project with a GET route /api/flights that calls
https://api.aviationstack.com/v1/flights with access_key from
AVIATION_STACK_API_KEY and flight_status=active. Cache the upstream response for
120 seconds with fetch's next.revalidate and return Cache-Control: public,
s-maxage=120, stale-while-revalidate=120. Keep only flights where
live.latitude and live.longitude are present, drop codeshare duplicates (keep
entries where flight.codeshared is null), and return them with a count of how
many were fetched versus kept. Errors come back as { error: { code, message } }
in the body: map usage_limit_reached to a 429 and never cache an empty result.
Pass through optional filters: airline_iata, flight_iata, dep_iata, arr_iata.
When it’s done, call the route twice in a row and confirm the second response comes from the cache, then check the browser’s network tab to make sure the access key never appears. The fetched-versus-kept count is worth keeping in the UI too, because it explains to users why they’re seeing a few thousand planes rather than every flight in the world.
Next comes the globe. Three.js through React Three Fiber is the most approachable route, and AI tools write it well because there’s so much example code:
Add a full-screen 3D Earth using @react-three/fiber and @react-three/drei: a
textured sphere with OrbitControls, a soft atmosphere glow and a starfield.
Convert each flight's latitude and longitude to a point slightly above the
sphere's surface and draw it as a small plane icon rotated to its direction
(degrees clockwise from north). Use instanced meshes so 3,000+ planes render
at 60 fps. Clicking a plane selects it.
The instruction about instanced meshes is the one that matters for performance. Without it, an AI will usually create one mesh per plane, which is fine for fifty planes and painful for three thousand. If you’d rather start in 2D, Leaflet with OpenStreetMap tiles is simpler and runs on any phone, and you can add the globe later.
The third prompt makes the planes move and fixes the times:
Between data refreshes, move every airborne plane forward using dead
reckoning: advance its position along live.direction at live.speed_horizontal
(km/h) based on time since live.updated, and ease to the new position when
fresh data arrives. Planes with is_ground true don't move. Departure and
arrival times are LOCAL airport times despite the +00:00 suffix: parse them
with departure.timezone and arrival.timezone using Luxon before showing
delays, progress or "lands in" text. Show altitude in feet and speed in knots,
converting from metres and km/h.
Check the time handling by picking a real flight from the response and comparing the arrival time your app shows with the airline’s own website or an airport arrivals board. If they’re out by a whole number of hours, the time zone conversion is wrong.
The last prompt adds the details panel and search, which is what turns a pretty globe into something people use:
When a plane is selected, show a side panel with the airline, flight number,
aircraft type and registration, departure and arrival airports with local
times, delay in minutes, altitude, speed and heading, and a great-circle arc
from departure to arrival on the globe. Add a search bar that queries
/api/flights by flight number, airline or airport code, and filter chips for
"airborne only" and "hide ground traffic".
There’s one catch with the arc: the flights endpoint gives you airport codes but not their coordinates. You can look them up with Aviationstack’s airports endpoint, but that spends requests, so it’s simpler to bundle an open airport dataset such as OurAirports and look up coordinates by IATA or ICAO code on your server.
Things to sort out before you launch
Plan your quota around your traffic, not around a single user. Work out how many server fetches you make per hour, including search, and multiply by a month, then pick the plan that covers it with room to spare. Keep showing the last good data when a fetch fails, so a temporary error doesn’t empty the map.
Performance on phones is the next thing to test. A globe with thousands of planes is a heavy page, so check it on a mid-range Android phone and offer a lighter 2D view, or fewer planes, when the frame rate drops. Keep the textures small, since a high-resolution Earth image can be bigger than the rest of the app put together.
Be honest about the data on the page, too. Positions come from the last update and are estimated in between, not tracked in real time to the second, and not every flight in the air has a position. A small “position from 16 minutes ago” label based on live.updated and a line in your FAQ explaining missing flights will save you a lot of confused feedback. Finally, check the licence: Aviationstack’s free plan is for non-commercial use, so move to a paid plan before you put the tracker in front of customers.
Wrapping up
A live flight tracker is a single API endpoint with a lot of care around it. You filter to flights that actually have a position, remove codeshare duplicates, read the times in each airport’s own time zone, convert metric units, cache one server fetch for every visitor, and animate the planes in between updates. Aviationstack does the hard part of collecting positions and schedules from around the world, and an AI assistant can build the globe and the interface in an afternoon or two, as long as you give it those details up front.
Explore APILayer Products
Discover APIs for geolocation, finance, aviation, weather, web scraping, AI, and more. Browse APILayer's product catalog to find the right APIs for your application and integrate the data and functionality your project needs.
Explore APILayer ProductsFind the right tools to start building.
STAY CONNECTED
Aviationstack API
Read our blogs on Aviationstack API, covering features, integration tips, and use cases for accessing real-time and historical aviation data.
Related categories