Authentication

The API uses Bearer token authentication. Include your API key in the Authorization header:

Authorization: Bearer amt_your_api_key_here

There are three permission levels:

read

Search aircraft, museums, proximity lookups. All public endpoints work without a key too.

readwrite

Everything in read, plus create and update aircraft, museums, and exhibit links.

admin

Everything in readwrite, plus delete records.

Keys are limited by the owner's current role and museum/country assignments. Disabled accounts cannot use keys. Admin and aircraft_admin have full catalog access; managers can edit assigned museums and their exhibit links. Only admin sessions can manage other users. Session writes require CSRF tokens; adding a Bearer header does not bypass CSRF for session-only endpoints.

To get an API key, log in and visit the API Keys page.

Complete route index

Generated from the running application's routes. PUT and PATCH apply partial updates. Session endpoints require login; users may access their own details and keys, while managing other users requires an admin session.

MethodRouteAccess
POST/api/v1/aircraftreadwrite
GET/api/v1/aircraft-typesPublic
POST/api/v1/aircraft-typesreadwrite
DELETE/api/v1/aircraft-types/<int:type_id>admin
GET/api/v1/aircraft-types/<int:type_id>Public
PATCH, PUT/api/v1/aircraft-types/<int:type_id>readwrite
GET/api/v1/aircraft-types/resolvePublic
DELETE/api/v1/aircraft/<int:aircraft_id>admin
GET/api/v1/aircraft/<int:aircraft_id>Public
PATCH, PUT/api/v1/aircraft/<int:aircraft_id>readwrite
GET/api/v1/aircraft/<int:aircraft_id>/historyPublic
POST/api/v1/aircraft/<int:aircraft_id>/historyreadwrite
GET/api/v1/aircraft/<int:aircraft_id>/history/managereadwrite
POST/api/v1/aircraft/bulk_importadmin
GET/api/v1/aircraft/searchPublic
GET/api/v1/contributorsPublic
GET/api/v1/docsPublic
GET/api/v1/exhibitsPublic
POST/api/v1/exhibitsreadwrite
DELETE/api/v1/exhibits/<int:link_id>admin
PATCH, PUT/api/v1/exhibits/<int:link_id>readwrite
GET/api/v1/factsPublic
POST/api/v1/factsreadwrite
DELETE/api/v1/facts/<int:fact_id>admin
PATCH, PUT/api/v1/facts/<int:fact_id>readwrite
GET/api/v1/facts/randomPublic
DELETE/api/v1/history/<int:event_id>admin
PATCH, PUT/api/v1/history/<int:event_id>readwrite
GET/api/v1/keysSession
POST/api/v1/keysSession
DELETE/api/v1/keys/<int:key_id>Session
POST/api/v1/museumsreadwrite
DELETE/api/v1/museums/<int:museum_id>admin
GET/api/v1/museums/<int:museum_id>Public
PATCH, PUT/api/v1/museums/<int:museum_id>readwrite
POST/api/v1/museums/bulk_importadmin
GET/api/v1/museums/countriesPublic
GET/api/v1/museums/globePublic
GET/api/v1/museums/mapPublic
GET/api/v1/museums/nearbyPublic
GET/api/v1/museums/nearestPublic
GET/api/v1/museums/regionsPublic
GET/api/v1/museums/searchPublic
GET/api/v1/nearestPublic
GET/api/v1/statsPublic
GET/api/v1/templatesPublic
POST/api/v1/templatesreadwrite
DELETE/api/v1/templates/<int:template_id>admin
GET/api/v1/templates/<int:template_id>Public
PATCH, PUT/api/v1/templates/<int:template_id>readwrite
POST/api/v1/trips/planPublic
POST/api/v1/trips/routePublic
GET/api/v1/usersSession
POST/api/v1/usersSession
DELETE/api/v1/users/<int:user_id>Session
GET/api/v1/users/<int:user_id>Session
PATCH, PUT/api/v1/users/<int:user_id>Session
GET/api/v1/users/<int:user_id>/keysSession
POST/api/v1/users/<int:user_id>/keysSession

Query and import conventions

Aircraft and museum searches return results, total, page, and pages. per_page defaults to RESULTS_PER_PAGE (20) and is capped at 100; fetch subsequent pages to retrieve the full catalog. sort_by names a supported field and sort_dir is asc or desc.

Detail requests include all exhibits by default. Add visible_only=true to show only on_display exhibits, as the public UI does. Other valid statuses are in_storage and under_restoration. Statistics count only on_display exhibit links.

GET /api/v1/museums/globe returns coordinates and on-display aircraft counts. GET /api/v1/museums/nearby takes location, optional region, and limit (default 10, maximum 50); museums without coordinates are listed separately. GET /api/v1/museums/nearest takes lat and lon or location, optional radius in miles, and limit (default 10, maximum 50). GET /api/v1/contributors lists contributor rankings.

Aircraft and museum bulk_import endpoints require admin-level data access. POST a multipart file or a JSON object with format (csv, json, or auto), data (text), and dry_run (boolean). The limits are 5,000 rows, the configured request-size limit (1 MiB by default), and BULK_IMPORT_RATE_LIMIT (200 per hour by default). Validation includes duplicates already in the database; writes are atomic. Aircraft rows may supply museum_id or museum_name to create an exhibit link.

Templates require name, manufacturer, and model; optional type fields and aliases prefill new aircraft without changing existing airframes. API key creation accepts label and permissions. POST /api/v1/keys also accepts expires_in_days; raw keys are returned once. JSON flags such as is_active and dry_run must be booleans, not strings.

Histories, map, and trip planning

GET /api/v1/aircraft/{id}/history lists published milestones; /history/manage requires readwrite access and includes drafts. POST on /history creates a milestone; PUT/PATCH /api/v1/history/{event_id} edits one; DELETE requires admin data access. Entries require a title and source_name or source_url. Optional integer event_year, event_month, event_day preserve date precision; is_approximate and is_published are booleans. Histories belong to the individual aircraft ID and do not change current exhibit assignments.

GET /api/v1/museums/map accepts q, region, country and returns all matching museums in results and no_coordinates, with total and on-display aircraft_count. The museum map supports clusters and area search.

POST /api/v1/trips/plan accepts origin (location text, or latitude and longitude), targets (1–12 objects), radius_miles (1–5000, default 500), max_stops (1–8, default 5), and round_trip (boolean). Targets use kind=airframe plus aircraft_id, or kind=model plus manufacturer and model. The public endpoint requires a CSRF token and is limited to 30 requests per minute. It returns ordered stops, covered targets, unmatched reasons, and straight-line mileage. Suggestions use on_display exhibits only and do not calculate driving time or opening hours. Try the trip planner.

Endpoints

Aircraft

GET /api/v1/aircraft/search public

Search aircraft by tail number, model, variant, name, manufacturer, or alias (e.g. "B29", "Superfortress").

qSearch query (optional)
pagePage number (default 1)
per_pageResults per page (default 20, max 100)
GET /api/v1/aircraft/{id} public

Get aircraft details with all museum locations.

POST /api/v1/aircraft readwrite

Create a new aircraft. Send JSON body with manufacturer (required), model (required), and optional fields: variant, tail_number, model_name, aircraft_name, role_type, year_built, description, aliases (array of strings), aircraft_type (fixed_wing | rotary_wing | lighter_than_air | spacecraft | missile_rocket), wing_type (monoplane | biplane | triplane), military_civilian (military | civilian), museum_id + display_status to assign to a museum on creation.

PUT /api/v1/aircraft/{id} readwrite

Update an aircraft record. Send JSON body with any fields to update.

DELETE /api/v1/aircraft/{id} admin

Delete an aircraft and all its exhibit links.

Museums

GET /api/v1/museums/search public

Search museums by name, city, state/province, or country. Optionally filter by region or country.

qSearch query (optional)
regionFilter by region: North America, Europe, Asia, Asia-Pacific, Middle East, South America, Africa, Oceania
countryFilter by country name
stateFilter by state/province name
pagePage number
per_pageResults per page (max 100)
GET /api/v1/museums/{id} public

Get museum details with its full aircraft collection.

GET /api/v1/museums/nearest public

Museums near a point, closest first — no aircraft filter. Answers "what's near me?", where /api/v1/nearest answers "where can I see a B-17?". aircraft_count reflects only aircraft on display.

lat, lonDecimal degrees. Takes precedence when both are supplied — mobile passes these straight from the browser's geolocation API.
locationZip/postal code or city name, used when lat/lon are absent
radiusOptional cap in miles
limitMax results (default 10, max 50)
GET /api/v1/museums/regions public

List all regions with museum counts.

GET /api/v1/museums/countries public

List all countries with museum counts.

POST /api/v1/museums readwrite

Create a new museum. Required fields: name, city, country, region. Optional: state_province, postal_code, address, website, access_type, latitude, longitude. access_type is one of public (default), appointment or restricted; proximity endpoints hide restricted museums unless called with include_restricted=1.

PUT /api/v1/museums/{id} readwrite

Update a museum record.

DELETE /api/v1/museums/{id} admin

Delete a museum and all its exhibit links.

Exhibits (Aircraft-Museum Links)

GET /api/v1/exhibits public

List every aircraft-museum link as a flat array — one row per exhibit, each carrying the full aircraft and museum objects.

qFilter by aircraft designation, name, manufacturer, tail number, or museum name/city (optional)
sort_bySort column: id, aircraft, museum, status (optional)
sort_dirasc (default) or desc
POST /api/v1/exhibits readwrite

Link an aircraft to a museum. Required: aircraft_id, museum_id. Optional: display_status (on_display, in_storage, under_restoration), notes.

PUT /api/v1/exhibits/{id} readwrite

Update exhibit status or notes.

DELETE /api/v1/exhibits/{id} admin

Remove an exhibit link.

Aircraft Types (shared type information)

A type is one write-up per designation, inherited at render time by every airframe whose normalized model + variant matches. Editing one record changes every matching aircraft page at once. Nothing is stored on the aircraft row and there is no backfill: a newly imported airframe inherits on its next page load.

Matching. The key is model + variant uppercased with every non-alphanumeric character dropped, so ("F-4","C") and ("F-4C",null) both resolve to the same record. It is not scoped by manufacturer, which is deliberate: a Fuji-built UH-1H is still a UH-1H and inherits Bell's write-up. Resolution prefers an exact model+variant record, then falls back to a variant-less base record. Most entries should be base records — one UH-1 serves every UH-1 — and a variant record is worth adding only when that variant genuinely needs its own text. Below both tiers sits the alias tier described next, so a real record always wins over an alias of the same specificity.

manufacturer_scope restricts a type to airframes whose manufacturer matches, and a scoped type never applies to one it does not. Set it only where an unrelated aircraft shares the designation string (S-2 is both a Grumman Tracker and a Pitts biplane; 47 and 737 are bare numbers). Matching is normalized prefix in either direction, so Bell accepts Bell Helicopter. Scoping an ordinary type is a mistake — it strips the write-up from every licence-built airframe.

aliases are alternate designations that reach the same record. Aviation gives one aeroplane several names — a T-6 Texan is also an AT-6 and an SNJ, a Canadair F-104 is a CF-104, a Polish MiG-15 is a Lim-2 — and the catalog records whichever one is painted on the airframe. An alias is a second match key pointing at an existing type: no aircraft row changes and no text is duplicated. Send aliases as a list of strings, or of {"designation": "204", "manufacturer_scope": "Bell"} objects. The list you send replaces the current one, so drop an alias by PATCHing the list without it. A bare-number alias without a scope is refused — unscoped 204 would attach the UH-1 write-up to every airframe spelled 204. An alias may not shadow a real type or another type's alias, and ranks below a real record of the same specificity, so writing an actual Su-17 type later supersedes the alias pointing Su-17 at the Su-22 without anyone deleting it.

Uniqueness is (designation, manufacturer_scope). A second record for the same pair returns 409 with existing_id, so a repeated POST is safe to retry as a PUT.

GET /api/v1/aircraft-types public

List types, ordered by model then variant. Anonymous callers see published records only; any authenticated identity also sees drafts.

qFuzzy match on display name, model, type name or manufacturer. A designation typed without punctuation (mig21) also matches (optional)
GET /api/v1/aircraft-types/{id} public

One type, plus inherits_count — how many airframes currently show this write-up. Check it before editing a base record. An unpublished type returns 404 to anonymous callers.

GET /api/v1/aircraft-types/resolve public

Ask what a designation would inherit, without writing anything. Returns resolved (the type or null), matched_on (variant, base or null), matched_via_alias (the alias it came in by, or null), match_key and scoped.

modelDesignation, e.g. T-33 (required)
variante.g. A (optional)
manufacturerNeeded to resolve a scoped type; pass it whenever you have it (optional)
POST /api/v1/aircraft-types readwrite

Create a type. Required: model, display_name, description. Omit variant for a base record.

modelDesignation exactly as the catalog records it, e.g. T-33 (required)
variantOmit for a base type that serves every variant
display_nameHeading, e.g. Lockheed T-33 Shooting Star (required)
descriptionThe write-up. Separate paragraphs with a blank line (\n\n) (required)
manufacturer_scopeRestrict to one builder. Leave unset unless the designation string is genuinely ambiguous
aliasesAlternate designations reaching this same write-up, e.g. ["AT-6", "SNJ"]. Replaces the current list
manufacturerOriginal designer, shown as provenance only
model_name, also_built_by, origin_countryType name (Shooting Star), licence builders, ISO 3166-1 alpha-2 country of design
aircraft_typefixed_wing (default), rotary_wing, lighter_than_air, spacecraft, missile_rocket
role_type, wing_type, military_civilianSame vocabularies as an aircraft record
spec_basisWhich variant the figures below describe, e.g. T-33A. Set it whenever you publish any figure — a base type covers every variant but its numbers cannot
first_flight_year, introduced_year, retired_year, number_builtIntegers. Leave retired_year null if still in service anywhere
crew, enginesFree text, e.g. 2 and 1 x Allison J33-A-35 turbojet
length_m, wingspan_m, height_mMetres. For a helicopter put main rotor diameter in wingspan_m
max_speed_kmh, range_km, ceiling_mIntegers, metric
source_name, source_url, wikipedia_urlAttribution shown under the card
is_publishedBoolean, default true. False keeps the record editable in admin and off every public page

Returns 400 for a missing required field or an unparseable number, and 409 with existing_id if this designation and scope already exist.

PUT /api/v1/aircraft-types/{id} readwrite

Partial update — send only the fields you are changing, which is the normal way to revise a description. Changing model, variant or manufacturer_scope moves the whole inheriting set and rebuilds the slug.

DELETE /api/v1/aircraft-types/{id} admin

Delete a type. No aircraft record is touched; matching airframes simply stop showing the card. To hide a write-up reversibly, PUT is_published: false instead.

Aviation Facts

GET /api/v1/facts public

List facts. Hidden facts are excluded unless include_inactive=true.

aircraft_idOnly facts about this aircraft
include_inactiveInclude hidden facts
GET /api/v1/facts/random public

One random active fact. Optional aircraft_id narrows it to a single airframe. Returns 404 when no facts exist.

POST /api/v1/facts readwrite

Add a fact. Required: fact (max 5000 chars). Optional: source_url, aircraft_id to attach it to a specific airframe.

PUT /api/v1/facts/{id} readwrite

Update fact, source_url, aircraft_id or is_active. Set is_active=false to hide a fact without deleting it.

DELETE /api/v1/facts/{id} admin

Delete a fact permanently. Prefer is_active=false to hide one.

Proximity

GET /api/v1/nearest public

Find the nearest museum(s) displaying a specific aircraft, measured from a zip/postal code or city. Supports alias search (e.g. "B29", "Superfortress"). Museums without coordinates are returned separately.

aircraftSearch term for aircraft — matches model, name, tail, or alias (required)
locationZip/postal code or city name (required)
museumFilter by museum name (optional)
limitMax results (default 5, max 25)

Statistics

GET /api/v1/stats public

Get counts of aircraft, museums, exhibit links, and countries.