Documentation

How to write a Contrail record.

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, 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 are relevant (or known). 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 considered complete records in Contrail.

Commercial
{
  "$type": "com.airplaneian.contrail.temp.flight",
  "createdAt": "2026-07-25T09:14:00-07:00",
  "date": "2025-07-13",
  "origin": { "icao": "KSFO", "iata": "SFO", "terminal": "INTL", "gate": "A2" },
  "destination": { "icao": "EGLL", "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"
}
General aviation
{
  "$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"
}

Identifying flights across repositories

Several records may describe the same flight segment, written by different people in their own repositories. There is no canonical record for a flight, and Contrail treats that duplication as expected. Contrail defines no global flight identifier, since no authority exists to issue one.

Readers therefore need a 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 but is not restricted to airlines: UAL for an airline, FDX for a cargo carrier, RCH for a military air mobility flight. Use the ICAO designator rather than the IATA code (UA), since IATA codes are reassigned between carriers over time. 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 rarely 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.

A place may carry any combination of the three. Sites that no identifier system covers, including private strips and off-airport landings, are described by 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.

Working with track data

Contrail is designed to accept records imported from flight tracking platforms as well as records created by hand. The same record type serves both, and they differ only in which fields are populated. The fields below are the ones data from a tracking platform should be able to supply.

Track data Contrail
Mode S addressicao24
Registrationregistration
ICAO type designatoricaoTypeDesignator
Type descriptionaircraftType
Callsigncallsign
Owner or operatorregisteredOwner

callsign holds the transmitted identification as broadcast, whatever form it takes. operator and flightNumber are separate fields with their own definitions, and a record may carry any combination of the three.

registeredOwner takes the combined owner-or-operator field these datasets expose. Its contents typically reflect the registrant rather than the operator, since much of the fleet is registered to trusts and single-purpose holding companies. Military and state aircraft usually have no entry at all.

Grouping flights into trips

A second record type groups flights that belonged to one journey. It is separate from the flight record, so a flight stands alone and deleting a trip affects nothing else.

The reference points one way only, from the trip to its flights. A flight carries no trip reference.

Flights are referenced by plain AT-URI rather than by com.atproto.repo.strongRef, so a reference stays valid when a flight record is corrected in place. A reference may dangle if a flight is later deleted.

A trip references only flight records its own author wrote, and makes no claim about anyone else's repository. The Flighty importer demo derives trips from booking references, which are read in the browser and never written into a record.

What Contrail leaves out

Duration, distance, and great circle mileage are all derivable from the times and places already present in a record. Values computed separately by different tools drift apart.

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 to document locations with no standard identifier.

A record carries route for the flight plan route. 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 flight tracking archive is queried with.

Fields with open value sets

relationship, cabin and status are plain strings with a documented list of known values rather than closed enumerations. The lists are not exhaustive, and a record carrying something outside them is valid. Anything reading these fields should handle an unfamiliar value rather than reject the record.

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. Fractional seconds round-trip inconsistently between implementations.