Skip to content

🇬🇧 uk-caa

CountryUnited Kingdom
Registration prefixG-
Data sourcehttps://ginfoapi.caa.co.uk (G-INFO REST API)
FormatJSON API (search + details endpoints; no bulk download)
Run frequencyWeekly (Monday, 06:10 UTC)
Depends on Mictronics for ICAO hexNo — the G-INFO details payload includes the aircraft's ICAO 24-bit address directly (AircraftDetails.ICAO24BitAircraftAddress.Hex).

How it works

G-INFO has no bulk-export endpoint, so this runner enumerates the entire register itself: it POSTs /api/aircraft/search once for every 2-letter suffix combination from AA to ZZ (676 calls total, via itertools.product), then, for each result with RegistrationStatus == "R", immediately calls GET /api/aircraft/details/{id} and writes the record — there is no intermediate accumulation. Search calls are not rate-limited; details calls sleep for request_interval_seconds between requests to be polite to the API. Any call that returns HTTP 403 is queued and retried once at a fixed 500ms interval after the main enumeration pass finishes. Lookup tables decode the API's free-text AircraftClass into canonical aircraft.type/aircraft.category values and full English country names into ISO 3166-1 alpha-2 codes, falling back to the raw source value for anything unmapped. Every written record explicitly sets military: false — this register is exclusively civil, and the explicit value ensures a stale military: true flag (from Mictronics or a prior record on a reused hex) is corrected on re-registration.

Whenever a record has an aircraft.type_designator, aircraft:type:{type_designator} is looked up in Redis (populated by the mictronics runner) and, if found, its manufacturer_model and wake_turbulence_category are each set directly on this record (independently — a type entry with only one of the two still sets that one) — unconditionally, regardless of whether Mictronics also has values for the same hex. This runner's own type_designator is sourced directly from the CAA and is authoritative; merge_aircraft.lua's "registry wins over mictronics" precedence rule guarantees these values take priority at read time either way. The lookup is not a hard dependency — a missing reference table entry, or the table not existing yet, leaves the record exactly as it would have been without this step.

Notable operational characteristic: because it makes 676 prefix search calls plus a details fetch per registered aircraft, a full run takes several hours. It is deliberately scheduled as the last runner of the day in config/ofelia/config.ini.example (10 6 * * 1, after ourairports at 10 5 * * 1) to avoid overlapping with other Monday jobs.

Columns

Fields below are grouped by the payload they come from: the POST /api/aircraft/search response (list of match summaries) and the GET /api/aircraft/details/{id} response (full nested record). Field names are otherwise unique except where noted as a duplicate.

Search response (POST /api/aircraft/search)

Source fieldImportedNotes
AircraftIDUsed to fetch the details payload; not stored
RegistrationStatusFilters which AircraftIDs get a details fetch ("R" only); not stored
MarkDuplicate of RegistrationDetails.Mark, which is read from the details payload instead
SerialNoDuplicate of AircraftDetails.SerialNumber, which is read from the details payload instead
MilitarySerialNoPresent in source; never read (no equivalent field exists in the details payload)
AircraftTypeDuplicate of AircraftDetails.Type, which is read from the details payload instead

Details response (GET /api/aircraft/details/{id})

Source fieldImportedNotes
RegistrationDetails.Markregistration (prefixed with G-)
RegistrationDetails.StatusFiltered to "Registered" only; not stored
RegistrationDetails.PreviousIDPresent in source; not read by this runner
RegistrationDetails.CurrentRegistrationDatePresent in source; not read by this runner
RegistrationDetails.DeRegistrationDatePresent in source; not read by this runner
RegistrationDetails.ReasonPresent in source; not read by this runner
RegistrationDetails.ToPresent in source; not read by this runner
AircraftDetails.Manufactureraircraft.manufacturer
AircraftDetails.Typeaircraft.model
AircraftDetails.SerialNumberaircraft.serial_number
AircraftDetails.ICAO24BitAircraftAddress.Hexicao_hex; records without a 6-character hex are discarded
AircraftDetails.ICAO24BitAircraftAddress.BinaryPresent in source; not read by this runner
AircraftDetails.ICAO24BitAircraftAddress.OctalPresent in source; not read by this runner
AircraftDetails.PopularNamePresent in source; not read by this runner
AircraftDetails.ICAOAircraftTypeDesignatoraircraft.type_designator; also used to look up aircraft:type:{type_designator} in Redis, setting aircraft.manufacturer_model and aircraft.wake_turbulence_category when found
AircraftDetails.AircraftClassaircraft.type / aircraft.category (decoded)
AircraftDetails.BuildCategoryPresent in source; not read by this runner
AircraftDetails.EasaCategoryPresent in source; not read by this runner
AircraftDetails.Engines[0].TotalNumberOfEnginesaircraft.powerplant.count
AircraftDetails.Engines[0].Nameaircraft.powerplant.model; only the first engine entry is used
AircraftDetails.Engines[0].NumberOfEnginesDistinct field from TotalNumberOfEngines (which is used); present in source, not read
AircraftDetails.Engines[0].IsPropellerPresent in source; not read by this runner
AircraftDetails.MtowPresent in source; not read by this runner
AircraftDetails.TotalAirframeHoursPresent in source; not read by this runner
AircraftDetails.YearBuildaircraft.manufactured_date
AircraftDetails.MaintenanceProgrammePresent in source; not read by this runner
AircraftDetails.ExpiryPresent in source; not read by this runner
AircraftDetails.OwnershipStatusPresent in source; not read by this runner
AircraftDetails.MaximumPassengersaircraft.seats (MaximumPassengers + 1)
AircraftDetails.MaximumTakeOffMassPresent in source; not read by this runner (numeric duplicate of Mtow)
AircraftDetails.CofaDetails.CofaCategoryPresent in source; not read by this runner
AircraftDetails.CofaDetails.CofaValidityExpiryLabelPresent in source; not read by this runner
AircraftDetails.CofaDetails.CofaValidityExpiryPresent in source; not read by this runner
AircraftDetails.CofaDetails.CofaValidityReferencePresent in source; not read by this runner
AircraftDetails.CofaDetails.CofaOperatingLimitationsPresent in source; not read by this runner
AircraftDetails.CofaDetails.ShowOperatingLimitationsPresent in source; not read by this runner
AircraftDetails.IsPotentialChangeOfOwnershipPresent in source; not read by this runner
AircraftDetails.EmergencyBallisticDeviceFittedPresent in source; not read by this runner
MortgageDetails[].EntryDateAndTimePresent in source; not read by this runner
MortgageDetails[].EntryTypePresent in source; not read by this runner
MortgageDetails[].ReferenceTypePresent in source; not read by this runner
RegisteredAircraftOwners[0].RegisteredOwnerregistrant.names[0]; only the first owner is used
RegisteredAircraftOwners[0].AddressIndicatorIDPresent in source; not read by this runner
RegisteredAircraftOwners[0].AddressIndicatorPresent in source; not read by this runner
RegisteredAircraftOwners[0].TradingAsPresent in source; not read by this runner
RegisteredAircraftOwners[0].TrusteeOfPresent in source; not read by this runner
RegisteredAircraftOwners[0].Address1 / Address2registrant.street
RegisteredAircraftOwners[0].Townregistrant.city
RegisteredAircraftOwners[0].Countyregistrant.administrative_area
RegisteredAircraftOwners[0].PostCoderegistrant.postal_code
RegisteredAircraftOwners[0].Countryregistrant.country (decoded to ISO 3166-1 alpha-2 where known)
AircraftOperatedByAocHolder.showSectionPresent in source; not read by this runner
AircraftOperatedByAocHolder.OperatorNamePresent in source; not read by this runner (operator enrichment for G-registered aircraft is not sourced from here)
AircraftOperatedByAocHolder.OperatorTrandingAsPresent in source (sic — typo in the live API); not read by this runner
InsuranceSummaryDetails.EvidenceVerifiedDatePresent in source; not read by this runner
InsuranceSummaryDetails.NoFlyDatePresent in source; not read by this runner
InsuranceSummaryDetails.AircraftMarkStatusIDPresent in source; not read by this runner
InsuranceSummaryDetails.AircraftIDPresent in source; not read by this runner (duplicate of the search result's AircraftID)
InsuranceSummaryDetails.MarkPresent in source; not read by this runner (duplicate of RegistrationDetails.Mark)
NoiseSummaryDetails.NoiseCertificateIssueDatePresent in source; not read by this runner
NoiseSummaryDetails.NoiseCertificateNumberPresent in source; not read by this runner
NoiseSummaryDetails.NoiseChapterPresent in source; not read by this runner
RegistrationMarkExemption.IssueDatePresent in source; not read by this runner
RegistrationMarkExemption.OriginPresent in source; not read by this runner
RegistrationMarkExemption.CodeCarriedPresent in source; not read by this runner
RegistrationMarkExemption.SerialCarriedPresent in source; not read by this runner
OpenRegistrationCases[].CaseNamePresent in source; not read by this runner
OpenRegistrationCases[].OpenDatePresent in source; not read by this runner
OpenRegistrationCases[].ReviewDatePresent in source; not read by this runner
OpenRegistrationCases[].PotentialChangeOfOwnershipPresent in source; not read by this runner
OpenApplicationsAndApprovalsCases[].CaseNamePresent in source; not read by this runner
OpenApplicationsAndApprovalsCases[].OpenDatePresent in source; not read by this runner
OpenApplicationsAndApprovalsCases[].ReviewDatePresent in source; not read by this runner
OpenApplicationsAndApprovalsCases[].PotentialChangeOfOwnershipPresent in source; not read by this runner
ExtractDatePresent in source; not read by this runner

See specs/data-dictionary.yaml (uk-caa entry) for full column semantics and cross-source schema notes.

Example Output

Read back the merged record for a given ICAO hex (combines this runner's data with Mictronics and any other sources that have written to the same key):

bash
docker run --rm --network host redis:latest redis-cli EVAL "$(cat ./shared/lua/merge_aircraft.lua)" 0 401A48 | python3 -m json.tool --sort-keys --no-ensure-ascii
json
{
    "aircraft": {
        "category": "Land",
        "manufactured_date": "1910-01-01T00:00:00Z",
        "manufacturer": "DEPERDUSSIN CIE",
        "model": "DEPERDUSSIN MONOPLANE",
        "powerplant": {
            "count": 1,
            "model": "ANZANI Y TYPE"
        },
        "seats": 1,
        "serial_number": "43",
        "type": "Airplane",
        "type_designator": "ULAC"
    },
    "icao_hex": "401A48",
    "registrant": {
        "city": "BIGGLESWADE",
        "country": "GB",
        "names": [
            "RICHARD SHUTTLEWORTH TRUSTEES"
        ],
        "postal_code": "SG18 9ER",
        "street": [
            "OLD WARDEN AERODROME",
            "OLD WARDEN"
        ]
    },
    "registration": "G-AANH",
    "source": "uk-caa"
}
bash
docker run --rm --network host redis:latest redis-cli EVAL "$(cat ./shared/lua/merge_aircraft.lua)" 0 407F19 | python3 -m json.tool --sort-keys --no-ensure-ascii
json
{
    "aircraft": {
        "category": "Land",
        "manufactured_date": "2023-01-01T00:00:00Z",
        "manufacturer": "AIRBUS SAS",
        "manufacturer_model": "AIRBUS A-350-1000",
        "model": "AIRBUS A350-1041",
        "powerplant": {
            "count": 2,
            "model": "ROLLS-ROYCE Trent XWB-97"
        },
        "seats": 336,
        "serial_number": "605",
        "type": "Airplane",
        "type_designator": "A35K",
        "wake_turbulence_category": "Heavy"
    },
    "icao_hex": "407F19",
    "military": false,
    "registrant": {
        "city": "CRAWLEY",
        "country": "GB",
        "names": [
            "VIRGIN ATLANTIC AIRWAYS LTD"
        ],
        "postal_code": "RH10 9DF",
        "street": [
            "THE VHQ",
            "FLEMING WAY"
        ]
    },
    "registration": "G-VBOB",
    "source": "uk-caa"
}

Configuration

Reads settings.json (mounted at /app/settings.json):

ParameterRequiredDefaultNotes
redis.hostRedis connection host
redis.port6379
request_interval_seconds0.1Sleep between G-INFO details calls (seconds); search calls are not rate-limited
mqtt.hostOmit the whole mqtt block to skip completion-stats publishing entirely
mqtt.port1883
mqtt.usernameOptional MQTT auth; omit for an anonymous broker
mqtt.password
redis_ttl_days14TTL applied to each aircraft:registry:{icao_hex} key written by this runner

MQTT

Published once, at the end of a run, to SkyFollower/runner/uk-caa/statistic/{name} (all retained):

Topic suffixValueFormat
records_importede.g. 271Integer as string
last_run_ate.g. 2026-07-07T14:32:01.123456+00:00ISO 8601 UTC
last_run_statussuccess or failureString

Home Assistant autodiscovery configs are also published (retained) to homeassistant/sensor/SkyFollower_runner_uk_caa_{name}/config for each of the three stats above.