GuidesOAG Flight Explorer

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

typescript
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

bash
curl "/api/schedules?origin=EDI&destination=LHR&dateFrom=2026-06-01"

Response

json
{
  "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.

bash
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

bash
curl "/api/schedules?origin=EDI&destination=HER&dateFrom=2026-06-01&mode=connections"

Response

json
{
  "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

bash
curl "/api/schedules/availability?origin=EDI&destination=LHR&month=2026-06"

Response

json
{
  "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:

scheduleddeparteden-routelandedcancelled

Request

bash
curl "/api/flight-info?flight=BA1470"

Response

json
{
  "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

bash
curl "/api/otp?origin=EDI&destination=LHR"

Response

json
{
  "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 }
  ]
}