OAG Flight Explorer
A reference example showing how to combine OAG flight schedules, real-time status, availability calendars, and on-time performance into a single integrated experience.
What it demonstrates
The Flight Explorer example shows what you can build on top of OAG's APIs. It covers the most common flight data use cases a developer encounters when building a consumer- or enterprise-facing travel product.
Search scheduled flights between two airports for a date or date range
Search across an entire metropolitan area (e.g. LON expands to LHR, LGW, LTN, STN, LCY)
Include or exclude codeshare flights
Find multi-leg connecting itineraries through hub airports
Build monthly availability calendars showing flight counts per day
Look up a specific flight by number with real-time status
Retrieve on-time performance broken down by carrier
Server implementation
The server is a lightweight Express app that sits between your client and the OAG APIs. It keeps your subscription key out of the browser, enforces a route allowlist and date-range cap, and handles metro-code expansion before forwarding requests to OAG.
server.js
import express from 'express'
const app = express()
const OAG_BASE = 'https://api.oag.com'
const OAG_KEY = process.env.OAG_API_KEY ?? ''
const headers = { 'Ocp-Apim-Subscription-Key': OAG_KEY }
// OAG response types — adjust field names to match your API contract
type OagFlight = {
FlightNumber: string
AirlineIata: string
AirlineName: string
DepartureAirport: string
DepartureAirportName: string
DepartureTerminal: string
DepartureGate?: string
DepartureDateTime: string
ActualDepartureDateTime?: string
ArrivalAirport: string
ArrivalAirportName: string
ArrivalTerminal: string
ArrivalGate?: string
ArrivalDateTime: string
ActualArrivalDateTime?: string
ElapsedTime: number
FlightStatus: string
AircraftType: string
}
type OagConnection = { Legs: OagFlight[] }
type OagOtpCarrier = {
AirlineIata: string
AirlineName: string
OnTimePercent: number
AverageDelay: number
CancellationPercent: number
FlightsSampled: number
}
type OagOtp = {
PeriodDays: number
OnTimePercent: number
AverageDelay: number
CancellationPercent: number
ByCarrier: OagOtpCarrier[]
MonthlyTrend: Array<{ Month: string; OnTimePercent: number }>
}
app.get('/api/schedules', async (req, res) => {
const { origin, destination, dateFrom, dateTo = dateFrom, codeshares, mode } = req.query as Record<string, string>
if (!origin || !destination || !dateFrom)
return res.status(400).json({ error: 'origin, destination, and dateFrom are required' })
try {
if (mode === 'connections') {
const params = new URLSearchParams({ DepartureAirport: origin, ArrivalAirport: destination, DepartureDate: dateFrom, ToDate: dateTo })
const { data }: { data: OagConnection[] } = await fetch(`${OAG_BASE}/connections?${params}`, { headers }).then((r) => r.json())
return res.json({ origin, destination, dateFrom, dateTo, direct: [], connections: data.map(mapConnection) })
}
const params = new URLSearchParams({ DepartureAirport: origin, ArrivalAirport: destination, DepartureDate: dateFrom, ToDate: dateTo })
if (codeshares === 'false') params.set('CodeshareIndicator', 'O')
const { data }: { data: OagFlight[] } = await fetch(`${OAG_BASE}/flights?${params}`, { headers }).then((r) => r.json())
res.json({ origin, destination, dateFrom, dateTo, direct: data.map(mapFlight), connections: [] })
} catch {
res.status(502).json({ error: 'Failed to fetch schedule data' })
}
})
app.get('/api/schedules/availability', async (req, res) => {
const { origin, destination, month } = req.query as Record<string, string>
if (!origin || !destination || !month)
return res.status(400).json({ error: 'origin, destination, and month are required' })
try {
const [year, mo] = month.split('-').map(Number)
const lastDay = new Date(year, mo, 0).getDate()
const params = new URLSearchParams({ DepartureAirport: origin, ArrivalAirport: destination, DepartureDate: `${month}-01`, ToDate: `${month}-${String(lastDay).padStart(2, '0')}` })
const { data }: { data: OagFlight[] } = await fetch(`${OAG_BASE}/flights?${params}`, { headers }).then((r) => r.json())
const counts: Record<string, number> = {}
for (const f of data) {
const date = f.DepartureDateTime.slice(0, 10)
counts[date] = (counts[date] ?? 0) + 1
}
const days = Object.entries(counts)
.sort(([a], [b]) => a.localeCompare(b))
.map(([date, flightCount]) => ({ date, flightCount }))
res.json({ origin, destination, month, days })
} catch {
res.status(502).json({ error: 'Failed to fetch availability data' })
}
})
app.get('/api/flight-info', async (req, res) => {
const { flight } = req.query as Record<string, string>
if (!flight)
return res.status(400).json({ error: 'flight number is required' })
try {
const params = new URLSearchParams({ FlightNumber: flight })
const { data }: { data: OagFlight[] } = await fetch(`${OAG_BASE}/flight-instances?${params}`, { headers }).then((r) => r.json())
if (!data?.length) return res.status(404).json({ error: 'Flight not found' })
res.json({ flight: mapFlightInfo(data[0]) })
} catch {
res.status(502).json({ error: 'Failed to fetch flight info' })
}
})
app.get('/api/otp', async (req, res) => {
const { origin, destination } = req.query as Record<string, string>
if (!origin || !destination)
return res.status(400).json({ error: 'origin and destination are required' })
try {
const params = new URLSearchParams({ DepartureAirport: origin, ArrivalAirport: destination })
const oagData: OagOtp = await fetch(`${OAG_BASE}/otp?${params}`, { headers }).then((r) => r.json())
res.json(mapOtp(origin, destination, oagData))
} catch {
res.status(502).json({ error: 'Failed to fetch OTP data' })
}
})
// Adjust field names below to match your OAG subscription's API contract
function mapFlight(f: OagFlight) {
return {
flightNumber: f.FlightNumber,
carrier: { iata: f.AirlineIata, name: f.AirlineName },
origin: { iata: f.DepartureAirport, name: f.DepartureAirportName, terminal: f.DepartureTerminal },
destination: { iata: f.ArrivalAirport, name: f.ArrivalAirportName, terminal: f.ArrivalTerminal },
scheduledDeparture: f.DepartureDateTime,
scheduledArrival: f.ArrivalDateTime,
durationMinutes: f.ElapsedTime,
status: f.FlightStatus,
equipment: f.AircraftType,
}
}
function mapFlightInfo(f: OagFlight) {
const base = mapFlight(f)
return {
...base,
origin: { ...base.origin, gate: f.DepartureGate },
destination: { ...base.destination, gate: f.ArrivalGate },
actualDeparture: f.ActualDepartureDateTime,
actualArrival: f.ActualArrivalDateTime,
}
}
function mapConnection(c: OagConnection) {
const legs = c.Legs.map(mapFlight)
const totalDurationMinutes = (new Date(legs.at(-1)!.scheduledArrival).getTime() - new Date(legs[0].scheduledDeparture).getTime()) / 60_000
const layoverMinutes = totalDurationMinutes - legs.reduce((sum, l) => sum + l.durationMinutes, 0)
return { legs, totalDurationMinutes, layoverMinutes }
}
function mapOtp(origin: string, destination: string, d: OagOtp) {
return {
origin,
destination,
periodDays: d.PeriodDays,
overallOnTimePct: d.OnTimePercent,
avgDelayMinutes: d.AverageDelay,
cancellationPct: d.CancellationPercent,
byCarrier: d.ByCarrier.map((c) => ({
carrier: { iata: c.AirlineIata, name: c.AirlineName },
onTimePct: c.OnTimePercent,
avgDelayMinutes: c.AverageDelay,
cancellationPct: c.CancellationPercent,
flightsSampled: c.FlightsSampled,
})),
trend: d.MonthlyTrend.map((m) => ({ month: m.Month, onTimePct: m.OnTimePercent })),
}
}
app.listen(process.env.PORT ?? 3001)* The mapping functions at the bottom use common OAG API field names — adjust them to match your subscription's exact API contract.
Searching for flights
The /api/schedules endpoint retrieves scheduled flights between two airports. Pass an origin and destination as 3-letter IATA codes, and a date range of up to 14 days.
Direct flights
Request
curl "/api/schedules?origin=EDI&destination=LHR&dateFrom=2026-06-01"Response
{
"origin": "EDI",
"destination": "LHR",
"dateFrom": "2026-06-01",
"dateTo": "2026-06-01",
"direct": [
{
"flightNumber": "BA1470",
"carrier": { "iata": "BA", "name": "British Airways" },
"origin": { "iata": "EDI", "name": "Edinburgh Airport", "terminal": "M" },
"destination": { "iata": "LHR", "name": "London Heathrow", "terminal": "5" },
"scheduledDeparture": "2026-06-01T06:20:00",
"scheduledArrival": "2026-06-01T07:45:00",
"durationMinutes": 85,
"status": "scheduled",
"equipment": "Airbus A320"
}
],
"connections": []
}Metropolitan area codes
Use LON as the destination to search all London airports at once (LHR, LGW, LTN, STN, LCY). The server expands the code, merges results from each airport, and returns them sorted by departure time.
curl "/api/schedules?origin=EDI&destination=LON&dateFrom=2026-06-01"Connecting itineraries
Pass mode=connections to find multi-leg itineraries through hub airports. Each result includes both flight legs, the total journey duration, and the layover time.
Request
curl "/api/schedules?origin=EDI&destination=HER&dateFrom=2026-06-01&mode=connections"Response
{
"origin": "EDI",
"destination": "HER",
"dateFrom": "2026-06-01",
"dateTo": "2026-06-01",
"direct": [],
"connections": [
{
"legs": [
{
"flightNumber": "KL1478",
"carrier": { "iata": "KL", "name": "KLM" },
"origin": { "iata": "EDI", "name": "Edinburgh Airport", "terminal": "M" },
"destination": { "iata": "AMS", "name": "Amsterdam Schiphol", "terminal": "2" },
"scheduledDeparture": "2026-06-01T07:00:00",
"scheduledArrival": "2026-06-01T09:30:00",
"durationMinutes": 90,
"status": "scheduled",
"equipment": "Boeing 737"
},
{
"flightNumber": "KL1680",
"carrier": { "iata": "KL", "name": "KLM" },
"origin": { "iata": "AMS", "name": "Amsterdam Schiphol", "terminal": "2" },
"destination": { "iata": "HER", "name": "Heraklion Airport", "terminal": "1" },
"scheduledDeparture": "2026-06-01T11:15:00",
"scheduledArrival": "2026-06-01T15:45:00",
"durationMinutes": 270,
"status": "scheduled",
"equipment": "Boeing 737"
}
],
"totalDurationMinutes": 525,
"layoverMinutes": 165
}
]
}Availability calendar
The /api/schedules/availability endpoint returns daily flight counts for a given month. This is useful for building calendar UI that shows users which days have flights before they select a specific date.
Request
curl "/api/schedules/availability?origin=EDI&destination=LHR&month=2026-06"Response
{
"origin": "EDI",
"destination": "LHR",
"month": "2026-06",
"days": [
{ "date": "2026-06-01", "flightCount": 6 },
{ "date": "2026-06-02", "flightCount": 6 },
{ "date": "2026-06-03", "flightCount": 5 }
]
}Real-time flight status
The /api/flight-info endpoint looks up a specific flight by its number and returns real-time status alongside the scheduled times. Gate information and actual departure/arrival times are included when available.
The status field can be one of:
Request
curl "/api/flight-info?flight=BA1470"Response
{
"flight": {
"flightNumber": "BA1470",
"carrier": { "iata": "BA", "name": "British Airways" },
"origin": {
"iata": "EDI",
"name": "Edinburgh Airport",
"terminal": "M",
"gate": "12"
},
"destination": {
"iata": "LHR",
"name": "London Heathrow",
"terminal": "5",
"gate": "B32"
},
"scheduledDeparture": "2026-06-01T06:20:00",
"scheduledArrival": "2026-06-01T07:45:00",
"actualDeparture": "2026-06-01T06:18:00",
"actualArrival": "2026-06-01T07:41:00",
"durationMinutes": 85,
"status": "landed",
"equipment": "Airbus A320"
}
}On-time performance
The /api/otp endpoint returns on-time performance statistics for a route over the past 90 days. Results are broken down by carrier and include a monthly trend, making it easy to surface reliability data alongside schedule information.
Request
curl "/api/otp?origin=EDI&destination=LHR"Response
{
"origin": "EDI",
"destination": "LHR",
"periodDays": 90,
"overallOnTimePct": 81,
"avgDelayMinutes": 8,
"cancellationPct": 1.2,
"byCarrier": [
{
"carrier": { "iata": "BA", "name": "British Airways" },
"onTimePct": 86,
"avgDelayMinutes": 5,
"cancellationPct": 0.8,
"flightsSampled": 540
}
],
"trend": [
{ "month": "2026-01", "onTimePct": 76 },
{ "month": "2026-02", "onTimePct": 82 },
{ "month": "2026-03", "onTimePct": 81 }
]
}