Records for every kind of flying
Contrail is intended to describe any kind of flying. Scheduled airline service and general aviation are the most familiar examples, and they sit at roughly opposite ends of how much structured information a flight carries, but they are not the limits of the schema. Cargo, charter, business aviation, air ambulance, law enforcement, government, military and flight training operations all use the same record type.
The difference between them is only which fields happen to apply. A cargo flight has an operator and a flight number but no seat or cabin. A police helicopter may have nothing beyond a registration, an aircraft type, and a takeoff and landing time. Both are complete records, and so are the two below.
{
"$type": "com.airplaneian.contrail.temp.flight",
"createdAt": "2026-07-25T09:14:00-07:00",
"date": "2025-07-13",
"origin": { "iata": "SFO", "terminal": "INTL", "gate": "A2" },
"destination": { "iata": "LHR", "terminal": "3" },
"operator": "VIR",
"flightNumber": "20",
"marketingAirline": "DAL",
"marketingFlightNumber": "5964",
"registration": "GVBOW",
"aircraftType": "Boeing 787-9",
"actualGateDeparture": "2025-07-13T15:57:00-07:00",
"actualTakeoff": "2025-07-13T16:25:00-07:00",
"actualLanding": "2025-07-14T10:14:00+01:00",
"actualGateArrival": "2025-07-14T10:23:00+01:00",
"relationship": "passenger",
"seat": "46K",
"status": "normal",
"source": "flighty",
"sourceId": "bef37d51-2876-433b-bffd-95c81f8fe569"
}
{
"$type": "com.airplaneian.contrail.temp.flight",
"createdAt": "2026-07-25T09:14:00-07:00",
"date": "2026-07-19",
"origin": {
"name": "Twin Oaks Airpark",
"faaLid": "7S3",
"geo": { "latitude": "45.42361", "longitude": "-122.94028" }
},
"destination": {
"name": "Twin Oaks Airpark",
"faaLid": "7S3",
"geo": { "latitude": "45.42361", "longitude": "-122.94028" }
},
"registration": "N12345",
"icao24": "a1b2c3",
"icaoTypeDesignator": "PA18",
"aircraftType": "Piper PA-18 Super Cub",
"actualTakeoff": "2026-07-19T10:12:00-07:00",
"actualLanding": "2026-07-19T10:58:00-07:00",
"relationship": "pic",
"source": "manual"
}
Note that the origin and destination in the general aviation example are the same place. Local flights are ordinary and the schema treats them as such.
Identifying flights across repositories
Because a record describes the flight rather than the author, several people aboard the same aircraft may each write a record for it. None of these is canonical, and Contrail treats that duplication as expected. It does not attempt to define a global flight identifier, because no authority exists to issue one.
This does mean readers need some way to recognise two repositories describing the same flight, so the identifying fields carry more weight here than they would in a personal logbook. Write them consistently wherever they are known.
| Commercial | date + operator + flightNumber |
|---|---|
| General aviation | icao24 + actualTakeoff |
The operator field takes the ICAO designator of whoever actually flew the
aircraft, and is not restricted to airlines: UAL for an airline,
FDX for a cargo carrier, RCH for a military air mobility
flight. It is the ICAO designator rather than the IATA code (UA) because
IATA codes are reassigned between carriers over time, which makes a historical record
tagged with one ambiguous. Operators with no designator at all, which is most of general
aviation, go in operatorName instead. Registrations are written in uppercase
with no hyphens, so that G-VBOW and GVBOW do not describe two
different aircraft.
Airports and landing sites
Every place in a record uses the same definition, whether it is an origin, a destination or a diversion, and all of its fields are optional. That is what lets one shape describe both a major international airport and a grass strip.
Three identifier fields exist because no single system covers everything. ICAO location
indicators are globally unique and are not reassigned, so prefer icao where
a place has one. IATA codes are reassigned between airports over time and are absent for
the great majority of general aviation fields. Many United States airfields have an FAA
Location Identifier and no ICAO indicator at all, which is why faaLid is its
own field rather than something written into icao.
Never derive one identifier from another. A wrong code is worse than an absent one, and
readers reconciling records match on whatever the fields actually contain. Sites that no
identifier system covers, including private strips and off-airport landings, use
name and geo instead.
Terminal and gate live here too. They describe how a particular flight was handled at a place rather than a property of the place itself, which is why they belong to the record and not to any airport dataset.
Working with surveillance data
The aircraft and operator fields are shaped to take ADS-B data without losing anything. A surveillance feed supplies a Mode S address, a registration, an ICAO type designator, a human-readable type description and a callsign, and each of those has a field of its own.
The callsign is written verbatim into callsign and is never decomposed on
the way in. UAL38 and FDX5608 split cleanly into an operator
and a flight number, so an importer can fill those as well. A general aviation aircraft
transmits its registration, and a state aircraft may transmit a bare word such as
LEGACY. Anything that cannot be split with certainty stays in
callsign alone.
Surveillance datasets usually expose a single combined owner-or-operator field, and in
practice its contents are the registrant rather than the operator. Much of the fleet is
registered to trusts and single-purpose holding companies, so a Pilatus PC-12 comes back
registered to a trust company and a Cessna 172 to an LLC named after its own tail number.
That value belongs in registeredOwner. Military and state aircraft usually
have no entry at all.
Grouping flights into trips
A second record type, com.airplaneian.contrail.temp.trip, groups flights
that belonged to one journey. It is a record of its own rather than a field on the
flight, so that a flight record stays a standalone assertion. Deleting a trip orphans
nothing, and a flight written by someone with no interest in trips is unaffected.
The reference points one way only, from the trip to its flights. Nothing in the protocol keeps two directions consistent, so a flight carries no trip reference and there is no pair of fields that can disagree with each other.
Flights are referenced by plain AT-URI rather than by
com.atproto.repo.strongRef. A strong reference pins exact record content,
which is right for a like or a repost, but flight records are expected to be corrected in
place as better data arrives and every correction would break a pinned reference. The
tradeoff is that a reference may dangle if a flight is later deleted, and readers must
tolerate that rather than treat it as an error.
A trip references only flight records its own author wrote, and makes no claim about anyone else's repository. The importer below derives trips from booking references, which are read in the browser and never written into a record, because a booking reference can be used to retrieve a reservation.
What Contrail leaves out
Duration, distance, and great circle mileage are all derivable from the times and places already present in a record. Storing them would guarantee that records written by different tools eventually disagree with each other.
Airport names, coordinates, and elevations are reference data about the world rather than
assertions by the author, so they do not belong in a personal repository. The one
exception is place.geo, which exists for landing sites that no identifier
system covers.
The flown track is excluded as well, and this is the one exclusion that people ask about most. A single long-haul flight is ten to fifteen thousand ADS-B positions, and because the AT Protocol data model has no floating point type, every latitude, longitude and altitude has to be stored as a string. That is roughly a megabyte per flight, around a hundred times the size of the record describing it.
A record carries route for the route as filed, which is compact and is
genuinely something the author asserts. For the track actually flown, the record already
carries what you need to find it: icao24 together with a departure time is
exactly what a surveillance archive is queried with. Storing the track would duplicate a
measurement made by receivers into a repository of assertions made by a person.
Times and time zones
Contrail defines eight timestamps, following the standard out, off, on and in convention, each with a scheduled and an actual variant. All of them are optional. A general aviation record will often carry only a takeoff and a landing.
Every timestamp is written with an explicit UTC offset and no fractional seconds. The Lexicon specification notes two round tripping ambiguities for datetimes, one involving lost precision and one involving trailing fractional second zeros, and a fixed convention avoids both.