sondahub

APIs / Flights

Flights

Airports, airlines, two and a half thousand scheduled flights and their bookings — the live-board one.

Fifty real airports, six invented airlines, flights over a two-week window around the seed’s "today" (2026-09-01), and six thousand bookings with passengers and seats. The live stream runs a departures board: flights boarding, departing, delayed and landing. POST a booking and the hub picks a seat. 6,056 records in all.

Connect

Base URL
https://api.sondahub.com/v1/flights
OpenAPI 3
https://api.sondahub.com/v1/flights/openapi.json
GraphQL
https://api.sondahub.com/v1/flights/graphql
WebSocket
wss://api.sondahub.com/v1/flights/ws
SSE
https://api.sondahub.com/v1/flights/events
The data
airports.json, airlines.json, flights.json, bookings.json

In Sonda: Import → From a URL with the OpenAPI address and the whole API lands as a project, one request per operation with example bodies. No keys, no headers to add. More on each protocol.

Writes here are simulated: POST, PUT, PATCH and DELETE are validated, run through the real logic and answered as a real server would — then forgotten. The answer carries _note and X-Sondahub-Write: simulated. A GET afterwards will not find what you wrote.
The API describes itself
curl https://api.sondahub.com/v1/flights

Lists and filters

Every list answers { "data": [...], "meta": { "page", "limit", "total", "pages" } } with X-Total-Count and Link headers (next, prev, first, last). These options work on every collection and every nested route:

OptionMeaningExample
page, limitPaging, 1-based; limit 1–200, default 20. offset works too.?page=3&limit=50
sortComma list of fields, - for descending. Default id here.?sort=-lat,id
field=valueEquals. Booleans as true/false, null for missing.?id=1
_ne _gt _gte _lt _lteNot equal and comparisons, on numbers, dates and strings.?lat_gte=10&lat_lt=100
_likeContains, case-insensitive.?iata_like=an
_inAny of a comma list.?id_in=1,2,3
_nulltrue: missing; false: present.?city_null=true
a.b=valueInside a JSON field, dotted.?passenger.first_name=…
qSearch across the text fields.?q=alpine
fieldsOnly these fields back.?fields=id,iata
expandEmbed related records.?expand=…

A name that is not a field answers 400 and lists the fields. Writes answer 422 with one line per problem, 404 for a missing id, 405 with an Allow header for a verb a route does not take.

airports

Airports by IATA code. 50 records — the file.

FieldTypeNotes
idread-onlyintAssigned by the server. Seed records keep their ids across restarts; records you create continue after the seed.
created_atread-onlydatetimeWhen the record was created (ISO 8601, UTC).
updated_atread-onlydatetimeWhen the record last changed.
iatarequiredstring
namerequiredstring
citystring
countrystring
timezonestring
latfloat
lonfloat
terminalsint

Endpoints

GET/v1/flights/airportsA page, with every filter, sort, search, field and expand option below.
POST/v1/flights/airportsCreate one. Answers 201 with the record, its id and a Location header — simulated, nothing stored; 422 names each field that is wrong.
GET/v1/flights/airports/{id}One record, with an ETag; If-None-Match earns a 304.
PATCH/v1/flights/airports/{id}Change the fields you send.
PUT/v1/flights/airports/{id}Replace the record; required fields must all be there.
DELETE/v1/flights/airports/{id}Answers 200 with what would have been removed and the note (a real server’s 204 lives at /v1/utils/status/204).

Try it

List with a filter
curl "https://api.sondahub.com/v1/flights/airports?lat_gte=10&limit=3"
One record
curl https://api.sondahub.com/v1/flights/airports/1
Create (simulated)
curl -X POST https://api.sondahub.com/v1/flights/airports \
  -H "Content-Type: application/json" \
  -d '{"iata":"MIA","name":"A name","country":"US"}'
Change one field (simulated)
curl -X PATCH https://api.sondahub.com/v1/flights/airports/1 \
  -H "Content-Type: application/json" \
  -d '{"lat":42.5}'
Delete (simulated)
curl -X DELETE https://api.sondahub.com/v1/flights/airports/1

airlines

Carriers. 6 records — the file.

FieldTypeNotes
idread-onlyintAssigned by the server. Seed records keep their ids across restarts; records you create continue after the seed.
created_atread-onlydatetimeWhen the record was created (ISO 8601, UTC).
updated_atread-onlydatetimeWhen the record last changed.
coderequiredstring
namerequiredstring
alliancestring
fleet_sizeint

Relations: flights → the flights whose airline_id is this airline. Use ?expand=flights to embed them, or the routes below.

Endpoints

GET/v1/flights/airlinesA page, with every filter, sort, search, field and expand option below.
POST/v1/flights/airlinesCreate one. Answers 201 with the record, its id and a Location header — simulated, nothing stored; 422 names each field that is wrong.
GET/v1/flights/airlines/{id}One record, with an ETag; If-None-Match earns a 304.
PATCH/v1/flights/airlines/{id}Change the fields you send.
PUT/v1/flights/airlines/{id}Replace the record; required fields must all be there.
DELETE/v1/flights/airlines/{id}Answers 200 with what would have been removed and the note (a real server’s 204 lives at /v1/utils/status/204).
GET/v1/flights/airlines/{id}/flightsIts flights, as a page with all the list options.

Try it

List with a filter
curl "https://api.sondahub.com/v1/flights/airlines?fleet_size_gte=1&limit=3"
One record
curl https://api.sondahub.com/v1/flights/airlines/1
Its flights
curl "https://api.sondahub.com/v1/flights/airlines/1/flights?limit=5"
Create (simulated)
curl -X POST https://api.sondahub.com/v1/flights/airlines \
  -H "Content-Type: application/json" \
  -d '{"code":"SH","name":"A name"}'
Change one field (simulated)
curl -X PATCH https://api.sondahub.com/v1/flights/airlines/1 \
  -H "Content-Type: application/json" \
  -d '{"fleet_size":2}'
Delete (simulated)
curl -X DELETE https://api.sondahub.com/v1/flights/airlines/1

flights

A scheduled flight. Times are ISO with the airport’s UTC offset applied, so a departure reads like the board. Filter by route with ?origin=MIA&destination=EZE, by day with ?departure_date=2026-09-02. 2,500 records — the file.

FieldTypeNotes
idread-onlyintAssigned by the server. Seed records keep their ids across restarts; records you create continue after the seed.
created_atread-onlydatetimeWhen the record was created (ISO 8601, UTC).
updated_atread-onlydatetimeWhen the record last changed.
numberrequiredstring
airline_idrequiredint → airlines
originrequiredstringIATA code.
destinationrequiredstring
origin_airport_idint → airports
destination_airport_idint → airports
departure_datedateLocal date at the origin.
scheduled_departurerequireddatetime
scheduled_arrivaldatetime
estimated_departuredatetime
actual_departuredatetime
statusenumscheduled boarding departed in_air landed delayed cancelled diverted
delay_minutesintmin 0
gatestring
terminalstring
aircraftstring
duration_minutesint
distance_kmint
seats_totalint
seats_availableread-onlyint
base_farefloatUSD, economy.

Relations: airline → one airline through airline_id; origin_airport → one airport through origin_airport_id; destination_airport → one airport through destination_airport_id; bookings → the bookings whose flight_id is this flight. Use ?expand=airline,origin_airport,destination_airport,bookings to embed them, or the routes below.

Endpoints

GET/v1/flights/flightsA page, with every filter, sort, search, field and expand option below.
POST/v1/flights/flightsCreate one. Answers 201 with the record, its id and a Location header — simulated, nothing stored; 422 names each field that is wrong.
GET/v1/flights/flights/{id}One record, with an ETag; If-None-Match earns a 304.
PATCH/v1/flights/flights/{id}Change the fields you send.
PUT/v1/flights/flights/{id}Replace the record; required fields must all be there.
DELETE/v1/flights/flights/{id}Answers 200 with what would have been removed and the note (a real server’s 204 lives at /v1/utils/status/204).
GET/v1/flights/flights/{id}/airlineThe airline this record points at.
GET/v1/flights/flights/{id}/origin_airportThe airport this record points at.
GET/v1/flights/flights/{id}/destination_airportThe airport this record points at.
GET/v1/flights/flights/{id}/bookingsIts bookings, as a page with all the list options.

Try it

List with a filter
curl "https://api.sondahub.com/v1/flights/flights?status=boarding&delay_minutes_gte=1&expand=airline&limit=3"
One record
curl https://api.sondahub.com/v1/flights/flights/1?expand=airline
Its bookings
curl "https://api.sondahub.com/v1/flights/flights/1/bookings?limit=5"
Create (simulated)
curl -X POST https://api.sondahub.com/v1/flights/flights \
  -H "Content-Type: application/json" \
  -d '{"number":"SH 1042","airline_id":1,"origin":"MIA","destination":"EZE","scheduled_departure":"2026-09-30T12:00:00Z","status":"scheduled","gate":"D14","aircraft":"A321neo"}'
Change one field (simulated)
curl -X PATCH https://api.sondahub.com/v1/flights/flights/1 \
  -H "Content-Type: application/json" \
  -d '{"status":"boarding"}'
Delete (simulated)
curl -X DELETE https://api.sondahub.com/v1/flights/flights/1

bookings

A seat on a flight. POST {"flight_id","passenger":{...},"cabin"} and the hub assigns a seat and a record locator. Cancel with PATCH {"status":"cancelled"}. 3,500 records — the file.

POST needs passenger.first_name and last_name, refuses cancelled or departed flights and sold-out ones (409), picks a free seat in the cabin when you give none, prices the fare from the flight, and mints a six-character locator. Cancelling gives the seat back.
FieldTypeNotes
idread-onlyintAssigned by the server. Seed records keep their ids across restarts; records you create continue after the seed.
created_atread-onlydatetimeWhen the record was created (ISO 8601, UTC).
updated_atread-onlydatetimeWhen the record last changed.
locatorread-onlystringSix-character record locator, unique.
flight_idrequiredint → flights
passengerrequiredjson { first_name, last_name, email, document? }
seatstringAssigned by the hub on POST when omitted.
cabinenumeconomy premium business first
statusenumconfirmed checked_in boarded cancelled no_show
farefloatmin 0
currencystring
bagsintmin 0, max 5
frequent_flyerstring
special_requestsjson string[]
booked_atdatetime
checked_in_atdatetime

Relations: flight → one flight through flight_id. Use ?expand=flight to embed them, or the routes below.

Endpoints

GET/v1/flights/bookingsA page, with every filter, sort, search, field and expand option below.
POST/v1/flights/bookingsCreate one. Answers 201 with the record, its id and a Location header — simulated, nothing stored; 422 names each field that is wrong.
GET/v1/flights/bookings/{id}One record, with an ETag; If-None-Match earns a 304.
PATCH/v1/flights/bookings/{id}Change the fields you send.
PUT/v1/flights/bookings/{id}Replace the record; required fields must all be there.
DELETE/v1/flights/bookings/{id}Answers 200 with what would have been removed and the note (a real server’s 204 lives at /v1/utils/status/204).
GET/v1/flights/bookings/{id}/flightThe flight this record points at.

Try it

List with a filter
curl "https://api.sondahub.com/v1/flights/bookings?cabin=premium&fare_gte=10&expand=flight&limit=3"
One record
curl https://api.sondahub.com/v1/flights/bookings/1?expand=flight
Create (simulated)
curl -X POST https://api.sondahub.com/v1/flights/bookings \
  -H "Content-Type: application/json" \
  -d '{"flight_id":1,"passenger":{"first_name":"Ada","last_name":"Lovelace","email":"ada@example.com"},"seat":"14C","cabin":"economy","status":"confirmed"}'
Change one field (simulated)
curl -X PATCH https://api.sondahub.com/v1/flights/bookings/1 \
  -H "Content-Type: application/json" \
  -d '{"cabin":"premium"}'
Delete (simulated)
curl -X DELETE https://api.sondahub.com/v1/flights/bookings/1

WebSocket and SSE

The same stream two ways: the world's own activity, one tick a second, generated for your connection alone. Both push JSON text messages; SSE names each one with event: and numbers it with id:. ?topics=a,b narrows either.

TopicWhat arrivesHow often
boardThe departures board: a flight boarding, departing, delayed, landing or cancelled.3 s
bookingsA seat being booked or checked in on a seed flight.6 s
WebSocket
wss://api.sondahub.com/v1/flights/ws?topics=board

> {"type":"hello","api":"flights","topics":[…],"subscribed":[…]}
> {"type":"event","topic":"board","api":"flights","ts":"…","data":{…}}
< {"type":"subscribe","topics":["board"]}   # narrow to some topics
< {"type":"ping"}                            # → {"type":"pong"}
< anything else                             # → echoed back as {"type":"echo"}
Server-Sent Events
curl -N "https://api.sondahub.com/v1/flights/events?topics=board"

retry: 3000
id: 1
event: board
data: {"type":"event","topic":"board",…}

GraphQL

One endpoint, https://api.sondahub.com/v1/flights/graphql: POST {"query", "variables"} or GET ?query=. Introspection is on, so Sonda's GraphQL mode loads the schema; the SDL is a click away. Every collection is a paged query with the same filter, sort and q options as REST (operators as suffixes: price_lt), a by-id query, relation fields both ways, and create, update, replace and delete mutations — simulated like every write, with the note in extensions.

A query
curl https://api.sondahub.com/v1/flights/graphql -H "Content-Type: application/json" -d '{"query": "{ flights(limit: 3, sort: \"-id\", filter: { status: scheduled }) { total data { id number airline_id origin airline { code } bookings(limit: 2) { id } } } }"}'
{
  flights(limit: 3, sort: "-id", filter: { status: scheduled }) {
    total
    data {
      id number airline_id origin
      airline { code }
      bookings(limit: 2) { id }
    }
  }
}