REST reference

The BasiFly API.

One base URL: /api. Versioned under /v1. Discovery is the live source of truth — when this page and discovery disagree, discovery wins. Read it first, branch on what it returns.

Endpoint catalog

MethodPathTierPrice (USDC)Auth
POST /v1/airport-discovery/nearby free — none
POST /v1/airport-discovery/places free — none
POST /v1/airport-discovery/airports free — none
GET /v1/dossiers/export api_key — scoped Bearer key; see endpoint contract
GET /v1/reference/airports/snapshots/:id free — none
POST /v1/map/query free — none
GET /v1/airports/map free — none
GET /v1/reference/aircraft-types free — none
GET /v1/reference/aircraft-types/:slug free — none
GET /v1/dossiers api_key — scoped Bearer key; see endpoint contract
GET /v1/dossiers/:id api_key — scoped Bearer key; see endpoint contract
GET /v1/aviation/live free — none
GET /v1/aviation/track/:hex free — none
GET /v1/aviation/summary free — none
GET /v1/me api_key — scoped Bearer key; see endpoint contract
GET /v1/airports free — none
GET /v1/airports/:icao free — none
GET /v1/calculator free — none
POST /v1/estimates free — none
GET /v1/health free — none
GET /v1/discovery free — none
GET /v1/opportunities paid 0.05 x402 when live
POST /v1/quote free — none
POST /v1/intents paid 0.02 x402 when live
GET /v1/intents/:id owner — owner token
POST /v1/intents/:id/commit owner — owner token
POST /v1/operator/availability api_key — scoped Bearer key; see endpoint contract
POST /v1/operator/quote api_key — scoped Bearer key; see endpoint contract
POST /v1/operator/confirm api_key — scoped Bearer key; see endpoint contract

Price columns describe behavior when payment status is live. While pending, paid tiers are free under rate limits.

Free endpoints

Calculator and airport reference

GET /v1/airports?q=… searches 2–80 characters and returns up to 12 supported airports. GET /v1/airports/:icao resolves a selected airport code or an unambiguous ICAO, IATA, local or source identifier. Each result has a canonical code; submit that value as origin/destination. icao is nullable. Airports without ICAO codes use stable OA- reference keys, with source/local/GPS aliases retained for search. Coverage spans the US, Canada, Mexico, Caribbean and Bermuda. Runway status is reference information, not operational approval. GET /v1/calculator returns category guidance and the current connection-consent text/version.

For location-first planning, use POST /v1/airport-discovery/places with a JSON query (2–80 characters), select a town, then call POST /v1/airport-discovery/nearby with numeric latitude, longitude, optional radius_miles (30, 100 or 250) and sort (distance or smaller). The response returns up to 30 canonical airports, has_more, source freshness, airport class, runway metadata and distance_miles measured in straight-line statute miles. Access and suitability remain unverified. Towns come from a local GeoNames snapshot; street addresses and driving directions are not supported.

POST /v1/airport-discovery/airports accepts the same query body for known-airport lookup. These free searches accept at most 2 KiB, retain the usual rate limits and return Cache-Control: private, no-store. Keep precise locations and typed places in request bodies and ephemeral client state, never public URLs or analytics. Searching does not select an airport, create a trip or contact an operator. Ask before using device location and explicitly choose airports for each end of the itinerary.

POST /v1/estimates is free and rate limited. Send origin, destination, departure_date, trip_type, pax, email contact, explicit connection_consent and consent_version; round trips also need return_date. Optional fields are cabin_class, notes, departure_time, return_time, marketing_consent and marketing_consent_version=marketing-v1. Times are airport-local; ambiguous DST times are rejected. Without times, dates represent flexible local-day windows. Use a random Idempotency-Key (32–128 characters) and independent secret X-Request-Token (32–128 characters), and preserve both across unchanged retries.

The 201 receipt includes intent_id, owner_token, normalized itinerary, distance, indicative airborne time/category, price_status=modeled, point/range estimates, per-passenger amounts, model version and separate BasiFly fee ranges when enabled. Keep the owner token private and use it in an Authorization: Bearer header for status reads. Never put it in a URL. A changed payload under the same retry key returns 409; invalid credentials cannot retrieve an earlier receipt.

Use the traveler calculator for this flow. Estimates use published-guide priors with unvalidated allowances and broad planning ranges; they are not binding operator quotes or a ±10% accuracy guarantee. Email requires separate unchecked-by-default marketing consent and domain activation. Actual owner-authorized operator-firm quotes remain accessible.

GET /v1/health

Liveness. No auth.

json
{
  "ok": true,
  "service": "basifly-api",
  "time": "2026-06-10T18:00:00Z"
}
200 OK

GET /v1/discovery

The capability and pricing manifest. The payment.status field is pending today and flips to live when settlement is enabled. The endpoints array is the catalog above; the mcp block names the available tools.

json
{
  "service": "basifly-api",
  "version": "0.2.0",
  "min_skill_version": "0.2.0",
  "endpoints": [
    {
      "method": "POST",
      "path": "/v1/airport-discovery/nearby",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Up to 30 canonical airports within 30/100/250 statute miles; body-only coordinates, straight-line distance, sourced caveats"
    },
    {
      "method": "POST",
      "path": "/v1/airport-discovery/places",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Body-only town/place search from a local GeoNames snapshot; no street addresses"
    },
    {
      "method": "POST",
      "path": "/v1/airport-discovery/airports",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Body-only canonical airport/alias search, maximum 12 results"
    },
    {
      "method": "GET",
      "path": "/v1/dossiers/export",
      "tier": "api_key",
      "price_usdc": null,
      "purpose": "Private paginated dossier export; read/export scopes and active intelligence entitlement; at most 25 per page"
    },
    {
      "method": "GET",
      "path": "/v1/reference/airports/snapshots/:id",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Source, retrieval date and transform for an airport snapshot_id; restricted manifest fields excluded"
    },
    {
      "method": "POST",
      "path": "/v1/map/query",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Jev map display filters from allowlisted command words; no raw query text or aircraft/provider lookup"
    },
    {
      "method": "GET",
      "path": "/v1/airports/map",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Cacheable map index of published airports with four-letter ICAO codes; public codes, names, coordinates and runway status"
    },
    {
      "method": "GET",
      "path": "/v1/reference/aircraft-types",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Published aircraft families: q (2–80 chars), after slug, limit 1–100 (default 50); immutable publication version and sourced specs; drafts excluded"
    },
    {
      "method": "GET",
      "path": "/v1/reference/aircraft-types/:slug",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Published model specifications and approved photo credits"
    },
    {
      "method": "GET",
      "path": "/v1/dossiers",
      "tier": "api_key",
      "price_usdc": null,
      "purpose": "Private aircraft index; dossiers:read scope and active intelligence entitlement required; INK not enabled"
    },
    {
      "method": "GET",
      "path": "/v1/dossiers/:id",
      "tier": "api_key",
      "price_usdc": null,
      "purpose": "Private aircraft dossier by stable airframe UUID; dossiers:read scope and active entitlement required"
    },
    {
      "method": "GET",
      "path": "/v1/aviation/live",
      "tier": "free",
      "price_usdc": null,
      "purpose": "ADSB.lol type-filtered North American positions; optional hex query uses allowance-limited ADS-B Exchange lookup"
    },
    {
      "method": "GET",
      "path": "/v1/aviation/track/:hex",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Last hour of collected ADSB.lol positions for a tracked aircraft; no provider calls"
    },
    {
      "method": "GET",
      "path": "/v1/aviation/summary",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Latest regional observation counts, aircraft-type mix, sample timestamps and collection freshness; aggregate data, not charter availability"
    },
    {
      "method": "GET",
      "path": "/v1/me",
      "tier": "api_key",
      "price_usdc": null,
      "purpose": "Describe your key, permissions, expiry, usage, rate limits and current inventory"
    },
    {
      "method": "GET",
      "path": "/v1/airports",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Supported airport search"
    },
    {
      "method": "GET",
      "path": "/v1/airports/:icao",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Canonical airport reference"
    },
    {
      "method": "GET",
      "path": "/v1/calculator",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Versioned category/date/consent guidance"
    },
    {
      "method": "POST",
      "path": "/v1/estimates",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Consented, idempotent traveler request with versioned modeled estimates when enabled"
    },
    {
      "method": "GET",
      "path": "/v1/health",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Liveness"
    },
    {
      "method": "GET",
      "path": "/v1/discovery",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Capability + pricing manifest"
    },
    {
      "method": "GET",
      "path": "/v1/opportunities",
      "tier": "paid",
      "price_usdc": "0.05",
      "purpose": "Search surfaced open legs / group seeds"
    },
    {
      "method": "POST",
      "path": "/v1/quote",
      "tier": "free",
      "price_usdc": null,
      "purpose": "Indicative estimate-v2 with dates, category comparison and separate coordination fee; 503 when disabled"
    },
    {
      "method": "POST",
      "path": "/v1/intents",
      "tier": "paid",
      "price_usdc": "0.02",
      "purpose": "Submit trip intent (enters east wing → received)"
    },
    {
      "method": "GET",
      "path": "/v1/intents/:id",
      "tier": "owner",
      "price_usdc": null,
      "purpose": "Intent/booking status (owner token only)"
    },
    {
      "method": "POST",
      "path": "/v1/intents/:id/commit",
      "tier": "owner",
      "price_usdc": null,
      "purpose": "Human-tap commit: quoted → confirming"
    },
    {
      "method": "POST",
      "path": "/v1/operator/availability",
      "tier": "api_key",
      "price_usdc": null,
      "purpose": "Operator posts time-boxed availability"
    },
    {
      "method": "POST",
      "path": "/v1/operator/quote",
      "tier": "api_key",
      "price_usdc": null,
      "purpose": "Operator firm quote response"
    },
    {
      "method": "POST",
      "path": "/v1/operator/confirm",
      "tier": "api_key",
      "price_usdc": null,
      "purpose": "Operator confirmation — the only path to booked"
    }
  ],
  "references": {
    "contract_version": "reference-v1",
    "documentation": "/docs/references/",
    "aircraft_identity": "Published family slug/type UUID; ICAO codes are not registry model codes or unique airframes. Unpublished aliases excluded.",
    "airport_identity": "Canonical code is ICAO where present, otherwise OA-<OurAirports ID>; aliases resolve only when unambiguous, except exact canonical matches take precedence.",
    "freshness": "Publication/review/retrieval dates describe evidence, not operational availability. Airport snapshot metadata resolves by snapshot_id.",
    "operator_directory": "hidden",
    "tail_dossiers": "scoped_key_and_current_entitlement"
  },
  "payment": {
    "status": "pending",
    "protocol_version": 2,
    "network": "eip155:8453",
    "asset": "USDC",
    "header": "PAYMENT-SIGNATURE",
    "ink": {
      "status": "not_enabled",
      "reason": "wallet_resource_authorization_required"
    }
  },
  "mcp": {
    "path": "/mcp",
    "tools": [
      "search_aircraft_types",
      "get_aircraft_type",
      "get_airport_snapshot",
      "search_aircraft_dossiers",
      "get_aircraft_dossier",
      "export_aircraft_dossiers",
      "whoami",
      "search_airports",
      "get_airport",
      "search_opportunities",
      "estimate_quote",
      "submit_trip_intent",
      "get_booking_status",
      "list_capabilities"
    ]
  }
}
200 OK — abbreviated

GET /v1/aviation/live

GET /v1/airports/map returns a cacheable schema_version: 1 airport index for the live map. Only published airports with four-letter ICAO codes are included. Each row contains its canonical code, ICAO/IATA codes, name, municipality, country, coordinates and runway status. The map starts live view automatically while visible and displays this airport index as searchable rows and map markers.

Public aircraft positions for 94 selected business-jet and charter-turboprop types across North America and nearby airspace (7–84°N, 172–50°W). No API key is required. The overview uses ADSB.lol and a durable shared 15-second cache. Add ?hex=abcdef for a targeted ADS-B Exchange lookup. All paid lookups share a maximum of one attempt per minute and allowance caps: 288 attempts per rolling 24 hours and 9,000 per rolling 31 days, with a 1,000-request provider buffer. Failed attempts count. ADSB.lol requests do not consume the paid allowance.

The response includes schema_version, source, cohort with type descriptors, source generated_at, retrieved_at, as_of, refresh_seconds, status, and aircraft. Aircraft contain source id, registration, callsign, ICAO aircraft_type, lat/lon, altitude in feet, groundspeed in knots, track in degrees, ground flag and position timestamp/age. Missing values are null.

Status is current, delayed, refresh_pending, allowance_reserved or provider_unavailable. When possible an older response is returned with its original timestamps and status; callers must check each position's age. The map hides positions older than 120 seconds or with unknown age. Without a saved response, exhausted allowance returns HTTP 429 and other unavailable states return 503. Responses are not browser cached.

Queries select known types globally; the public response shows the North American bounding box. Unknown types are absent from the type-filtered overview but retained in historical research imports. Type is a research filter, not proof of charter activity or availability. Original raw snapshots remain private to the research collection.

bash
curl -s https://basifly.angel.ink/api/v1/aviation/live
Shared ADSB.lol overview. Optional ?hex=abcdef uses the paid source.

Additional telemetry includes aircraft_description, manufacture_year, vertical_rate_fpm, geometric altitude, indicated/true airspeed, Mach, headings, squawk, emergency, category, position source, signal strength, message counts, selected altitude/heading, altimeter setting and wind. Missing source fields remain null. The overview refresh interval is 15 seconds; paid lookups remain limited to 60 seconds. In-flight or failed source attempts back off for at least 60 seconds.

GET /v1/aviation/track/:hex

Read recent collected positions for a selected aircraft without making provider calls. Returns aircraft_id, source, as_of, window_start and timestamp-ordered points, using the same public aircraft fields as the live endpoint. Reads at most 250 ADSB.lol snapshots from the last hour. Duplicate position timestamps, stale reports, out-of-region and non-cohort aircraft are excluded. Responses are shared for 15 seconds.

Track history is bounded to BasiFly's on-demand live collection; it is not a full flight, interpolated trajectory or access to WPI historical archives. The flight explorer breaks trails at gaps, offers sample-by-sample replay and exports the selected view or track. No departure, destination or charter status is inferred.

GET /v1/aviation/summary

Public, read-only regional observation summaries. No key or research password is required. The response contains schema_version, as_of, source and interpretation notes, and an areas array.

Each area includes its name, survey center, radius, collection interval, enabled flag, generated_at (source time), retrieved_at, observation count, airborne/on-ground counts and up to 32 aircraft-type counts. unknown_type_count counts missing or invalid types; other_type_count counts types beyond the top 32. Counts refer to one area's latest snapshot, not flights or seats. Areas overlap and must not be summed as unique aircraft.

status is current, delayed, paused or awaiting_data. Current requires both source and retrieval timestamps within two collection intervals, without an invalid future timestamp. A fresh response timestamp does not make an old snapshot current. Empty snapshots remain valid. Missing snapshots have null source/retrieval times.

bash
curl -s https://basifly.angel.ink/api/v1/aviation/summary
Legacy regional aggregates; no provider request is made by this API call.

The former three regional surveys are paused; this endpoint preserves their last snapshots and reports paused status. Server reads are coalesced and cached for 60 seconds; public responses can be cached for 15 seconds. A failed read returns 503 aviation_summary_unavailable without substituting sample data. Standard API rate limits apply. Individual aircraft identities, positions and raw research snapshots are not included. Activity is not evidence of a charter flight or confirmed availability.

Paid endpoints

Free while payment status is pending. When live, available paid operations answer a missing payment with the 402 challenge shown at the end of this page.

GET /v1/opportunities — 0.05 USDC

Search surfaced open legs and group seeds. Query params: from, to, window, limit (max 200). An opportunity's confidence is inferred or operator_confirmed — a property of the lead, not a price.

bash
curl -s "/api/v1/opportunities?from=KTEB&to=KPBI&limit=20"
json
{
  "opportunities": [
    {
      "id": "op_01J...",
      "tail": "N123BF",
      "operator": "Apex Jet (Part 135)",
      "cabin_class": "light",
      "kind": "likely_return_leg",
      "confidence": "inferred",
      "est_total_usd": 9800,
      "date": "2026-06-20",
      "narrative": "Likely empty return leg; estimate until operator confirms."
    }
  ]
}
200 OK

POST /v1/quote — versioned planning estimate

This free research endpoint requires origin, destination, pax, cabin_class and departure_date, with optional departure_time, trip_type, return_date and return_time. It returns schema_version=estimate-v2 with the same modeled output as the calculator and creates no booking quote or email enrollment. Disabled models return 503 estimate_unavailable. Distance hints cannot override airport-derived distance.

bash
curl -s -X POST /api/v1/quote \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "KTEB",
    "destination": "KPBI",
    "pax": 3,
    "cabin_class": "midsize",
    "departure_date": "2027-01-20",
    "departure_time": "12:00"
  }'
json
// Success: schema_version="estimate-v2", price_status="modeled";
// point_estimate_usd, range_usd, per_passenger_usd, basifly_fee_range,
// category_estimates, assumptions, accuracy_note and model_version.
// When the audited model is disabled:
{"error":"estimate_unavailable","detail":{"reason":"disabled"}}
503 — no payment collected

POST /v1/intents — 0.02 USDC

Submit a trip intent. Required: origin, destination, pax, cabin_class, and contact ({ channel, ref }, both strings). Optional: depart_window ({ earliest, latest }, ISO strings), return, flexibility ({ date_days, airport_radius_nm }), price_ceiling_usd, group_ok, agent_partner_id.

bash
curl -s -X POST /api/v1/intents \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "KTEB",
    "destination": "KPBI",
    "depart_window": { "earliest": "2026-07-01T13:00:00Z", "latest": "2026-07-01T19:00:00Z" },
    "return": null,
    "pax": 3,
    "cabin_class": "midsize",
    "flexibility": { "date_days": 1, "airport_radius_nm": 50 },
    "price_ceiling_usd": null,
    "group_ok": false,
    "contact": { "channel": "agent", "ref": "[email protected]" },
    "agent_partner_id": null
  }'

The intent enters the state machine at received. The response carries an owner_token (prefix own_) — the only credential needed to read status or commit afterward. Keep it; it is safe in env or session, but never paste it into a public channel.

json
HTTP/1.1 201 Created

{
  "intent_id": "in_01J...",
  "owner_token": "own_8f3c...",
  "status": "received",
  "status_url": "/v1/intents/in_01J..."
}
201 Created

Owner-token endpoints

Owner-token reads pass the token as Authorization: Bearer own_..., or as ?token=... for the read-only status link. A bad or missing token returns 404 (never 403) so a wrong token can never confirm a trip exists.

GET /v1/intents/:id

Intent and booking status. Returns the status field and any quotes.

bash
curl -s /api/v1/intents/in_01J... \
  -H "Authorization: Bearer own_8f3c..."
json
{
  "intent_id": "in_01J...",
  "status": "quoted",
  "origin": "KTEB",
  "destination": "KPBI",
  "pax": 3,
  "cabin_class": "midsize",
  "created_at": "2026-06-10T18:05:00Z",
  "updated_at": "2026-06-10T18:06:30Z",
  "quotes": [ { "id": "qt_01J...", "confidence": "estimate", "valid_until": "2026-06-12T18:00:00Z" } ]
}
200 OK

POST /v1/intents/:id/commit

The human-tap commit. Advances quoted → confirming through the guarded transition. Body: { "quote_id": "qt_..." }. The owner token here is header-only — query strings leak into proxy logs, so a credential that authorizes a write never rides there. A quote that does not belong to the intent returns 404; a rejected transition returns 409.

bash
curl -s -X POST /api/v1/intents/in_01J.../commit \
  -H "Authorization: Bearer own_8f3c..." \
  -H "Content-Type: application/json" \
  -d '{ "quote_id": "qt_01J..." }'
json
{
  "intent_id": "in_01J...",
  "status": "confirming",
  "quote_id": "qt_01J..."
}
200 OK

Operator endpoints

Operator-key-only: a Bearer op_live_ key scoped to the operator's tails. A traveler-side agent never calls these — they are documented so the booking chain is legible. Full detail on the operators page.

POST /v1/operator/availability

Post time-boxed availability. Required: operator_id, tail, window_start, window_end, valid_until. Optional: origin_icao, dest_icao, seats, price_band.

bash
curl -s -X POST /api/v1/operator/availability \
  -H "Authorization: Bearer op_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "operator_id": 42,
    "tail": "N123BF",
    "origin_icao": "KTEB",
    "dest_icao": "KPBI",
    "window_start": "2026-07-01T12:00:00Z",
    "window_end": "2026-07-01T20:00:00Z",
    "seats": 6,
    "price_band": "midsize-east",
    "valid_until": "2026-06-30T00:00:00Z"
  }'

POST /v1/operator/quote

The firm response. Required: intent_id, price_usd, validity. Optional: conditions, tail, operator_id. This is what promotes a quote's confidence to operator_firm; the BasiFly fee stays structurally separate.

bash
curl -s -X POST /api/v1/operator/quote \
  -H "Authorization: Bearer op_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "intent_id": "in_01J...",
    "price_usd": 10568.5,
    "conditions": "subject to slot availability",
    "validity": "2026-06-15T00:00:00Z",
    "tail": "N123BF",
    "operator_id": 42
  }'

POST /v1/operator/confirm

The only path to booked. Body: { "quote_id": "qt_..." }. Advances confirming → booked; a bad source state returns 409 with a reason.

bash
curl -s -X POST /api/v1/operator/confirm \
  -H "Authorization: Bearer op_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "quote_id": "qt_01J..." }'

Errors

402 — payment challenge (when live)

While payment status is pending this never appears. When live, a paid call with no PAYMENT-SIGNATURE header answers with the challenge below. Pay per the accepts block, then retry with the payment proof header.

http
HTTP/1.1 402 Payment Required

{
  "x402Version": 2,
  "resource": { "url": "https://basifly.angel.ink/api/v1/opportunities", "mimeType": "application/json", "description": "BasiFly API access" },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x...",
      "amount": "50000",
      "maxTimeoutSeconds": 300,
      "extra": { "name": "USD Coin", "version": "2" }
    }
  ]
}

409 — guarded transition rejected

State transitions are guarded in core. A commit without a recorded commit token, or a confirm from the wrong state, returns 409 with the rejection reason in detail.

http
HTTP/1.1 409 Conflict

{ "error": "transition_rejected", "detail": "missing commit token" }

400 / 401 / 404 / 429

  • 400 validation_error — body fails the schema; detail holds the field errors.
  • 401 operator_key_required / invalid_operator_key — operator endpoint without a valid op_live_ key.
  • 404 not_found — unknown id, or an owner/quote token that does not match.
  • 429 rate_limited — bucket empty; a Retry-After: 60 header is returned.

Rate limits

Keyed by IP and key. Single window of 60 seconds per tier.

TierLimit
anonymous / x40260 / min
agent key (ag_live_)300 / min
operator key (op_live_)600 / min
health + discovery120 / min

Do not loop paid calls. One search, one quote, then ask.

Know and manage your API key

GET /v1/me (MCP whoami) is free and requires your Bearer API key. It returns the key id, kind, label, prefix, scopes, creation/expiry timestamps, expires_in_seconds, account_id, last_used_at, calls_7d, superseded_by, rate_limit (limit, remaining, reset_at), payment and inventory. Operator keys also return operator_id. Legacy keys may have null label, expiry or account fields. Hashes and secrets are never returned.

Inventory contains status (surfaced, none_surfaced or ingest_paused), as_of and note. It accompanies opportunity searches too. It describes active surfaced leads. With no leads, registry-only mode or a worker heartbeat over 15 minutes old reports ingest_paused. No heartbeat remains unknown. Aircraft availability is not guaranteed.

Scope errors return insufficient_key_scope with required, have and manage_url. Invalid keys return invalid_api_key with reason expired or invalid. Revoked, suspended and unknown credentials all use invalid. Missing credentials on /v1/me return api_key_required. MCP carries these same objects in an isError tool result.

Below 14 days remaining, keyed responses include X-Key-Expires-In in seconds. Rate-limit reset_at is when the token bucket would be full without further requests. Counts update about once a minute and cover today plus six previous UTC dates; abrupt restarts or unconfirmed database writes can lose the final buffered counts. They are not billing records.

Use Developer account to rotate a key. Rotation preserves its name and scopes, grants the remaining lifetime or 30 days (whichever is longer), and lets the old key work until its original expiry or 24 hours (whichever comes first). A live successor prevents another rotation of the same predecessor. The 20-active-key cap includes the grace-period key. Only the signed-in owner can create, revoke or rotate keys; an API key alone cannot manage the account. The replacement secret is shown once.