The Aero Reg API is a thin, versioned wrapper over the registry
adapter layer. Every endpoint reads the same adapters that power the member-facing
aircraft-detail, reports, and changes browsers, so payloads are consistent
across surfaces and across all four registries.
Look up individual aircraft by ICAO24 hex code or registration mark, query
full snapshot datasets with flexible column and filter controls, subscribe
to change feeds, and automate exports and webhooks — all against
daily-refreshed data from the FAA (United States), CCAR (Canada),
CASA (Australia), and FOCA (Switzerland).
API status
Stable · v1
Base URL
https://aeroreg.co.uk/api/v1
Registries
FAA, CCAR, CASA, FOCA
Format
JSON
Auth
X-API-Key / Bearer
Version
v1 — stable
Authentication
API keys
Every endpoint requires an API key. There is no anonymous
access and no dev-mode bypass on the API key path. Supply your
key using one of the two accepted header forms:
Both forms are equivalent; use whichever fits your HTTP client or framework
conventions. Keys are minted per member account, are
bcrypt-hashed at rest, and carry an explicit scope set
(see Scopes). Manage and rotate your keys from the
member dashboard.
Environments
Live and test keys
Every API key is either live or test,
distinguished by its prefix:
aeroreg_live_… — live key
aeroreg_test_… — test key
In v1 this is a label and usage-segregation distinction
only. Both key types hit real, production data. Live and test keys share
one rate-limit pool per member — requests made with a test key count
against the same hourly window as live requests. Sandbox semantics
(synthetic data, unlimited rate) are deferred to a future version.
Rate limits
Rate limits
Requests are counted in hourly windows, per member, by
subscription tier. Limits reset at the top of each UTC hour.
Per-tier request limits
Tier
Per hour
Approx. per day
Basic
42
~1,000
Pro
417
~10,000
Enterprise
4,167
~100,000
Rate-limit response headers
Every API response carries the following headers so your client can track
its current standing without polling a separate endpoint:
X-RateLimit-Limit — your tier's hourly ceiling
X-RateLimit-Remaining — requests remaining in the current window
X-RateLimit-Reset — ISO-8601 UTC timestamp when the window resets
When the window is exhausted the API returns 429 Too Many Requests
with a Retry-After header (value in seconds) indicating how long
to wait before retrying.
Tier gating
Registry access by tier
Access to each registry is gated by a subscription feature
({registry}_data_access). A request for a registry that
your tier cannot access returns 403 Forbidden.
Which registries each tier can access
Registry
Basic
Pro
Enterprise
faa
✗
✓
✓
ccar
✓
✓
✓
casa
✓
✓
✓
foca
✓
✓
✓
Watch-list and export endpoints additionally require their respective tier
features (watch_list_enabled, {registry}_data_access).
Webhook endpoints require the Enterprise-only webhooks_enabled feature.
Authorization
API key scopes
Each API key carries a set of scopes that enforce least-privilege access.
A request is permitted only when both conditions are met:
The key's scope set contains the endpoint's required scope, and
The member's subscription tier grants the underlying feature.
Effective permission = key.scopes ∩ tier.features.
Out-of-scope returns 403; out-of-tier returns 403.
A key can never exceed its member's tier.
Available scopes (11 total)
Scope
Grants
aircraft:read
Hex/registration lookups
query:read
Snapshot query /{registry}/aircraft
changes:read
/{registry}/changes/{type} change-feed browsing
columns:read
/{registry}/columns discovery (always allowed — never returns 403 on scope)
New keys are issued with the least-privilege read set:
aircraft:read, query:read, changes:read,
columns:read, usage:read. Members opt into write,
exports, and webhooks scopes explicitly from the key management dashboard.
Query grammar
Query grammar
The snapshot-query and changes endpoints share one URL grammar with the
member-facing reports and changes browsers (powered by the same
ColumnQueryParser). All parameters are optional.
Column selection
cols=id1,id2,… — comma-separated
column ids (machine-stable identifiers; see the
Column vocabulary freeze note in the
spec). Omitting cols returns the standard field set.
Discover valid column ids at runtime from
GET /{registry}/columns.
Filters
Filters are repeatable triples using array-bracket notation:
Example: repeatable filter triples in URL query string
Each triple specifies a column id (c), an operator
(o), and a value (v). The index
([N]) is zero-based and must be sequential. For the
in operator, v accepts a comma-separated list.
Available operators: equals, contains,
starts_with, in, gte, lte,
between. Which operators a given column accepts is
registry- and column-specific — discover it at runtime from
GET /{registry}/columns.
Sorting
sort=col&order=asc|desc — sort by a column id.
order defaults to asc when omitted.
Pagination
page=N&per_page=N — per_page defaults
to 50, maximum 500. Values outside the
allowed range are clamped silently; the effective value is echoed back in
the meta block of list responses.
Response format
Errors & response envelopes
All responses use a consistent JSON envelope — both success and
error payloads follow the same outer shape.
Field names inside data rows are the canonical adapter
column ids — the same ids accepted by cols,
sort, and filter parameters, and emitted by the JSON export
writer.
A parameter is missing, malformed, or out of range.
401 Unauthorized
No API key supplied, or the key is invalid or expired.
403 Forbidden
Key lacks the required scope, or the member's tier does not grant the requested registry or feature.
404 Not Found
The requested resource does not exist.
405 Method Not Allowed
The HTTP method is not supported for this endpoint.
409 Conflict
Export not yet ready (poll again) or a conflicting resource already exists.
410 Gone
Export artifact has expired and can no longer be downloaded.
422 Unprocessable Entity
The request body is structurally valid JSON but fails semantic validation.
429 Too Many Requests
Hourly rate limit exhausted. Check Retry-After (seconds) before retrying.
Lookup
Single-aircraft lookup by transponder hex or registration.
Lookup
Look up an aircraft by transponder hex (ICAO24)
GET/aircraft/hex/{hex}
Scope aircraft:read
Resolves a 6-hex-digit ICAO24 transponder code to the aircraft that is currently registered under it, querying the most likely registry first (by ICAO 24-bit allocation) and falling back to the others the caller's tier can access. Deregistered aircraft are not returned (404) — their history lives in the changes endpoint. Requires scope aircraft:read.
Parameters
hex
string·pathREQUIRED· e.g. A004B3
6-digit hexadecimal ICAO24 code (case-insensitive), e.g. A004B3.
include
string·queryOPTIONAL· values: lifecycle, owners, documents, import_export, airframe· e.g. lifecycle,owners
Comma-separated optional enrichment blocks to attach to the lookup response. Registry adapter blocks are gated by supported tabs; the opt-in airframe block reads the published canonical graph and is separately gated by the Phase 2 API publication flag and registry entitlements. An unavailable block is omitted and described in meta. An unknown token yields 400.
Responses
200
The currently-registered aircraft for the identifier.
Look up an aircraft by registration (tail number / mark)
GET/aircraft/registration/{registration}
Scope aircraft:read
Resolves a registration / tail number / mark to the currently-registered aircraft. The registration prefix routes the lookup to candidate registries (e.g. N…→FAA, C-…→CCAR, VH-…→CASA, HB-…→FOCA). Deregistered aircraft return 404. Requires scope aircraft:read.
Parameters
registration
string·pathREQUIRED· e.g. N100
Aircraft registration, e.g. N100, C-AAC, VH-22A (case-insensitive; prefix punctuation optional).
include
string·queryOPTIONAL· values: lifecycle, owners, documents, import_export, airframe· e.g. lifecycle,owners
Comma-separated optional enrichment blocks to attach to the lookup response. Registry adapter blocks are gated by supported tabs; the opt-in airframe block reads the published canonical graph and is separately gated by the Phase 2 API publication flag and registry entitlements. An unavailable block is omitted and described in meta. An unknown token yields 400.
Responses
200
The currently-registered aircraft for the identifier.
Paginated snapshot queries over a registry's current state.
Query
Query a registry's current-state snapshot
GET/{registry}/aircraft
Scope query:read
Paginated query over the registry's current registered fleet. Choose columns, apply filters, sort, and page using the shared query grammar. total in meta is the full match count (ignoring pagination). Requires scope query:read.
Parameters
registry
string·pathREQUIRED· values: faa, ccar, casa, foca· e.g. faa
Registry namespace.
cols
string·queryOPTIONAL· e.g. registration,manufacturer,model,year_manufactured
Comma-separated list of column ids to return, e.g. cols=registration,manufacturer,model. Unknown ids are rejected with 400. When omitted, a registry/context-specific default set is returned. Valid ids are frozen per registry/context — see the *Columns schemas.
f
object·queryOPTIONAL
Repeatable filter triples using bracket syntax: f[0][c]=manufacturer&f[0][o]=contains&f[0][v]=cessna. c is a column id, o an operator (equals, contains, starts_with, in, gte, lte, between), and v the value (comma-separated list for in). An unknown column, an operator the column does not accept, or an empty value each yields 400. Note: OpenAPI cannot fully model the nested f[N][c|o|v] grammar; treat the example as authoritative.
sort
string·queryOPTIONAL· e.g. registration
Column id to sort by. Must be a valid column for the registry/context. The pseudo-column change_summary is not sortable.
Change-feed browsing (additions, modifications, deletions, …) within a date window.
Changes
Browse a registry's change feed for a given type and date window
GET/{registry}/changes/{type}
Scope changes:read
Returns change records of one type within a date window. Each row carries a stable change_id. modifications rows expose a derived change_summary; request include=diffs to attach the full per-field old→new decomposition (one batch lookup per page). The virtual type rereg selects re-registrations (it resolves to the additions base type with a structural filter applied; meta.base_type reports the resolved base). Requires scope changes:read.
Parameters
registry
string·pathREQUIRED· values: faa, ccar, casa, foca· e.g. faa
Registry namespace.
type
string·pathREQUIRED· values: additions, modifications, deletions, import_export, rereg· e.g. additions
Change type. Common to all registries: additions, modifications, deletions. CCAR additionally supports import_export. The virtual type rereg (re-registrations) is available on all registries and resolves to an additions-based query.
Relative window shorthand, counting back from today (UTC). Ignored if since/until are supplied. Defaults to 7d.
since
string·queryOPTIONAL· e.g. 2026-06-05
Inclusive window start (UTC, YYYY-MM-DD). Presence of since or until switches to a custom window. Reversed ranges are auto-corrected.
until
string·queryOPTIONAL· e.g. 2026-06-12
Inclusive window end (UTC, YYYY-MM-DD).
include
string·queryOPTIONAL· values: diffs· e.g. diffs
Comma-separated optional blocks. Only diffs is supported, and only for type=modifications; requesting it for any other type is ignored and reported in meta.unsupported_includes.
cols
string·queryOPTIONAL· e.g. registration,manufacturer,model,year_manufactured
Comma-separated list of column ids to return, e.g. cols=registration,manufacturer,model. Unknown ids are rejected with 400. When omitted, a registry/context-specific default set is returned. Valid ids are frozen per registry/context — see the *Columns schemas.
f
object·queryOPTIONAL
Repeatable filter triples using bracket syntax: f[0][c]=manufacturer&f[0][o]=contains&f[0][v]=cessna. c is a column id, o an operator (equals, contains, starts_with, in, gte, lte, between), and v the value (comma-separated list for in). An unknown column, an operator the column does not accept, or an empty value each yields 400. Note: OpenAPI cannot fully model the nested f[N][c|o|v] grammar; treat the example as authoritative.
sort
string·queryOPTIONAL· e.g. registration
Column id to sort by. Must be a valid column for the registry/context. The pseudo-column change_summary is not sortable.
Column-vocabulary discovery for the query and changes endpoints.
Discovery
Discover the column vocabulary for a registry/context
GET/{registry}/columns
Scope columns:read
Returns the available columns — id, label, type, group, and the operators each accepts — for the snapshot query (type=snapshot, the default) or for a specific change type. This is the live mirror of the frozen *Columns enum schemas in this document; use it to build valid cols/sort/f[...] parameters at runtime. Requires scope columns:read. Discovery is always allowed; this endpoint never 403s on scope.
Parameters
registry
string·pathREQUIRED· values: faa, ccar, casa, foca· e.g. faa
Which column set to describe. snapshot (default) describes the query endpoint's columns; a change type (additions, modifications, deletions, CCAR import_export, or the virtual rereg) describes that change feed's columns. rereg resolves to its additions base and reports meta.base_type.
Responses
200
The column vocabulary for the requested registry/context.
401
Missing, invalid, or revoked API key.
403
The caller's subscription tier does not include the requested registry.
404
Unknown registry/endpoint, or no currently-registered aircraft for the identifier.
429
Hourly rate limit exceeded.
Code sample: GET /{registry}/columns in cURL, JavaScript, Python, and PHP
Per-member API usage aggregates and rate-limit window.
Usage
Get per-member API usage aggregates and rate-limit window
GET/usage
Scope usage:read
Returns usage totals and the current rate-limit window for the authenticated member. No tier gate — available to all subscription tiers. Requires scope usage:read.
Parameters
include
string·queryOPTIONAL· values: hourly
When hourly, attach a 24-element hourly_buckets sparkline.
Responses
200
Usage aggregates for the authenticated member.
401
Missing, invalid, or revoked API key.
403
The caller's subscription tier does not include the requested registry.
429
Hourly rate limit exceeded.
Code sample: GET /usage in cURL, JavaScript, Python, and PHP
Deletes the specified watch-list and all its aircraft entries and alerts. Requires tier feature watch_list_enabled; out-of-tier → 403. Requires scope watchlists:write.
Parameters
id
integer·pathREQUIRED· e.g. 42
Numeric resource id.
Responses
200
Watch-list deleted.
401
Missing, invalid, or revoked API key.
403
The caller's subscription tier does not include the requested registry.
404
Unknown registry/endpoint, or no currently-registered aircraft for the identifier.
429
Hourly rate limit exceeded.
Code sample: DELETE /watch-lists/{id} in cURL, JavaScript, Python, and PHP
Adds an aircraft to the specified watch-list by identifier. Serial-number lookups are not supported in v1 (→ 422). Requires tier feature watch_list_enabled; out-of-tier → 403. 403 is also returned when the list's aircraft cap has been reached. Requires scope watchlists:write.
Parameters
id
integer·pathREQUIRED· e.g. 42
Numeric resource id.
Request body
data_source
stringREQUIRED· values: faa, ccar, casa, foca
identifier_type
stringREQUIRED· values: hex, registration
Serial not supported in v1 — use hex or registration.
Marks all unread alerts for the specified watch-list as read. Requires tier feature watch_list_enabled; out-of-tier → 403. Requires scope watchlists:write.
Parameters
id
integer·pathREQUIRED· e.g. 42
Numeric resource id.
Responses
200
Alerts marked as read.
401
Missing, invalid, or revoked API key.
403
The caller's subscription tier does not include the requested registry.
404
Unknown registry/endpoint, or no currently-registered aircraft for the identifier.
429
Hourly rate limit exceeded.
Code sample: POST /watch-lists/{id}/alerts/mark-read in cURL, JavaScript, Python, and PHP
POST /watch-lists/{id}/alerts/mark-read
curl \
-X POST \
"http://127.0.0.1/aeroreg/api/v1/watch-lists/42/alerts/mark-read" \
-H "X-API-Key: aeroreg_live_xxxxxxxx"
Asynchronous data exports and saved-report definitions.
Exports
Enqueue an asynchronous data export
POST/exports
Scope exports:write
Tier feature: {registry}_data_access
Enqueues an asynchronous export of snapshot or change-feed data. The same parameters may alternatively be supplied as a query string (the snapshot/ changes URL grammar — cols, f[N][c|o|v], sort, order, win/ since/until, format, opt[…]); body values win on conflict. Sort/order are validated but do NOT round-trip to async output (registry- default ordering applies); saved reports do persist sort. Requires tier feature {registry}_data_access; out-of-tier → 403. Monthly quota exhaustion also returns 403. The virtual rereg change type cannot be exported in v1 (→ 422). Requires scope exports:write.
Change type; required when source=changes; e.g. additions, modifications, deletions, ccar import_export. The virtual rereg type is NOT exportable (→ 422).
cols
stringOPTIONAL
Comma-separated column ids; defaults to the context default set. May also be a JSON array.
Change type; required when source=changes; e.g. additions, modifications, deletions, ccar import_export. The virtual rereg type is NOT exportable (→ 422).
cols
stringOPTIONAL
Comma-separated column ids; defaults to the context default set. May also be a JSON array.
Performs a full replacement of the saved report definition. Requires tier feature {registry}_data_access; out-of-tier → 403. Requires scope exports:write.
Change type; required when source=changes; e.g. additions, modifications, deletions, ccar import_export. The virtual rereg type is NOT exportable (→ 422).
cols
stringOPTIONAL
Comma-separated column ids; defaults to the context default set. May also be a JSON array.
Returns the current status and metadata for an export run. Poll this endpoint until status=completed, then use GET /exports/{id}/download. Requires scope exports:read.
Parameters
id
integer·pathREQUIRED· e.g. 42
Numeric resource id.
Responses
200
Export run status and metadata.
401
Missing, invalid, or revoked API key.
403
The caller's subscription tier does not include the requested registry.
404
Unknown registry/endpoint, or no currently-registered aircraft for the identifier.
429
Hourly rate limit exceeded.
Code sample: GET /exports/{id} in cURL, JavaScript, Python, and PHP
Streams the completed export artifact as a file download. The run must have status=completed (→ 409 otherwise) and the artifact must not have expired (→ 410 otherwise). Rate-limit headers are omitted from this binary response path. Requires scope exports:read.
Parameters
id
integer·pathREQUIRED· e.g. 42
Numeric resource id.
Responses
200
The export artifact as a file stream.
401
Missing, invalid, or revoked API key.
403
The caller's subscription tier does not include the requested registry.
404
Unknown registry/endpoint, or no currently-registered aircraft for the identifier.
409
The export artifact is not ready yet (run not completed).
410
The export artifact has expired or been removed.
429
Hourly rate limit exceeded.
Code sample: GET /exports/{id}/download in cURL, JavaScript, Python, and PHP
Creates a new webhook subscription. The signing secret is returned in full exactly once in the response and is not retrievable later. Requires tier feature webhooks_enabled (Enterprise only); out-of-tier → 403. Requires scope webhooks:write.
Updates one or more fields of the specified webhook subscription. Requires tier feature webhooks_enabled; out-of-tier → 403. Requires scope webhooks:write.
Rotate the signing secret for a webhook subscription
POST/webhooks/{id}/rotate
Scope webhooks:write
Tier feature: webhooks_enabled
Rotates the signing secret for the specified webhook subscription. The new secret is returned in full exactly once and is not retrievable later. Requires tier feature webhooks_enabled; out-of-tier → 403. Requires scope webhooks:write.
Parameters
id
integer·pathREQUIRED· e.g. 42
Numeric resource id.
Responses
200
New signing secret (shown once — store it now).
401
Missing, invalid, or revoked API key.
403
The caller's subscription tier does not include the requested registry.
404
Unknown registry/endpoint, or no currently-registered aircraft for the identifier.
429
Hourly rate limit exceeded.
Code sample: POST /webhooks/{id}/rotate in cURL, JavaScript, Python, and PHP
POST /webhooks/{id}/rotate
curl \
-X POST \
"http://127.0.0.1/aeroreg/api/v1/webhooks/42/rotate" \
-H "X-API-Key: aeroreg_live_xxxxxxxx"
Synchronously fires a test delivery to the subscription's target URL. An SSRF guard still applies to the target. Returns the HTTP status received from the target, or an error message on connection/SSRF failure. Requires tier feature webhooks_enabled; out-of-tier → 403. Requires scope webhooks:write.
Parameters
id
integer·pathREQUIRED· e.g. 42
Numeric resource id.
Responses
200
Test delivery result.
401
Missing, invalid, or revoked API key.
403
The caller's subscription tier does not include the requested registry.
404
Unknown registry/endpoint, or no currently-registered aircraft for the identifier.
429
Hourly rate limit exceeded.
Code sample: POST /webhooks/{id}/test in cURL, JavaScript, Python, and PHP
POST /webhooks/{id}/test
curl \
-X POST \
"http://127.0.0.1/aeroreg/api/v1/webhooks/42/test" \
-H "X-API-Key: aeroreg_live_xxxxxxxx"