Skip to main content

Documentation Index

Fetch the complete documentation index at: https://teardowns.aero/docs/llms.txt

Use this file to discover all available pages before exploring further.

Most error responses follow this shape:
{
  "detail": {
    "error_code": "invalid_api_key",
    "message": "Invalid API key"
  }
}
Sometimes additional fields are included for context for example unknown_vocabulary errors include valid_values so you can correct the value without reading docs. Validation errors from Pydantic (unknown body keys, wrong types) follow FastAPI’s standard 422 shape. Branch on error_code, surface message to humans, log the rest.

Two exceptions to the envelope shape

Some endpoints return a plain string in detail rather than the { error_code, message } object. Today this applies to:
  1. 404 Not Found on the by-id endpoints (GET /teardowns/{id}, PATCH /teardowns/{id}, DELETE /teardowns/{id}, and the matching /sales-lease-exchange/{id} routes). The body is literally:
    { "detail": "Teardown not found" }
    
    or:
    { "detail": "Listing not found" }
    
    There is no error_code or message field branch on the HTTP status 404 instead.
  2. 400 from the transition endpoint when the state-machine rejects a move (e.g. trying to complete a teardown that is still in active_starting). The body is a plain string from the state-machine layer:
    { "detail": "Cannot perform 'complete' from status 'active_starting'" }
    
    Same pattern: no error_code, branch on the 400 status and the action you sent.
For everything else listed in the table below, the { error_code, message, ... } envelope is the rule.

Quick remediation matrix

Statuserror_codeWhyFix
401missing_or_malformed_authorizationAuthorization header missing, empty, or doesn’t start with Bearer tdao_live_Send the API key as Authorization: Bearer tdao_live_…. JWTs from the web app are not accepted here.
400invalid_organization_headerX-Organization-Id missing or not a UUIDSend the org’s UUID from the API Access page.
401invalid_api_keyKey not found, revoked, or rotatedMint a fresh key on the Settings page. Old value is dead.
401api_key_expiredKey passed its expires_atMint a new key, optionally without an expiry.
403organization_mismatchThe key belongs to a different org than the X-Organization-Id headerAlways send the org id that goes with the key. Every occurrence is treated as suspicious and alerted on.
403org_inactiveThe org’s account_status is inactiveContact Teardowns.aero support.
403org_churnedThe org’s account_status is churnedContact Teardowns.aero support.
403subscription_requiredSubscription is past_due / canceled / expired / rejected / pending payment / pending approvalResolve billing. The subscription_status field is included in the error body.
403api_access_disabledAPI access is turned off for this orgAsk Teardowns.aero support to re-enable. Existing keys resume working, no rotation needed.
403api_key_creator_revokedThe user who minted the key is no longer active in this orgHave another eligible member mint a replacement key.
403insufficient_capabilityThe minter doesn’t hold the capability the endpoint requiresHave an org admin grant the capability, or mint the key as someone who already has it.
404(plain-string body, no error_code)The resource doesn’t exist OR belongs to a different orgConfirm the id is yours. We deliberately do not distinguish never reveal foreign-org id existence. See the “Two exceptions” note above for the exact body shape.
422invalid_statusThe status field is not one of Starting / In process / CompletedUse one of the three exact values. Case-sensitive.
422vocab_requiredA required vocabulary field is missing for the chosen asset_typeSend aircraft_type for aircraft, engine_model for engine, etc.
400unknown_vocabularyThe vocabulary name doesn’t match any active rowThe error body includes valid_values pick one of those. Match is case-insensitive.
422invalid_audienceAn audience value is not in the allowed setAllowed: Airline / Lessor / OEM / MRO / Distributor / Others. Case-insensitive.
400file_too_largeUpload exceeds 50 MBSplit the file or compress.
400invalid_content_typeUpload MIME isn’t in the accepted listAccepted: PDF / XLSX / XLS / DOCX / DOC / CSV / JPEG / PNG / WEBP / GIF.
502storage_unavailableSupabase Storage upstream failedRetry with backoff. Each retry uploads a new file with a fresh UUID prefix no corruption risk.
413payload_too_largeJSON body exceeded the 10 MB capUse the document upload endpoint for files, not the JSON endpoint.
429too_many_requestsA rate-limit bucket overflowedCheck Retry-After header. Back off. See rate-limiting.
422various Pydantic shapesUnknown body key, wrong type, etc.The response body lists the bad field. Strict mode = no silent drops.

Detailed explanations

We return this same code whether the key doesn’t exist, was revoked, or matched a fingerprint but failed status checks. The single response prevents an attacker from probing the keyspace.From your side: if you minted the key recently and it suddenly stops working, the most likely cause is that the minter was deactivated (which auto-revokes their keys within an hour, but the 401 starts immediately). Less likely: the key was rotated by another org admin.
The API key already implies an org. Sending a different org id in X-Organization-Id is, by construction, never a legitimate user error either the wrong value got pasted into your ERP config, or something more interesting is happening. We alert on every occurrence.Fix: re-paste the Organization ID from the Settings → API Access page where you minted the key.
Two different switches:
  • api_access_disabled: API access was turned off for your org specifically. Has nothing to do with billing.
  • subscription_required: your subscription is past_due / canceled / expired / not yet active. Resolve billing.
Both can be true at once. If both checks would fail, the subscription one fires first.
The full list of valid values is in the response body under valid_values:
{
  "detail": {
    "error_code": "unknown_vocabulary",
    "field": "aircraft_type",
    "value": "A320-poo",
    "valid_values": ["737-700", "747-400", "A320-200", "A321-200", ...],
    "message": "Unknown aircraft_type: 'A320-poo'. See valid_values."
  }
}
Match is case-insensitive a320-200 and A320-200 both work. Whitespace at the edges is stripped.
The status field accepts exactly three values:
  • Starting
  • In process
  • Completed
Case-sensitive. starting, in progress, STARTING all return 422. We picked strict matching so the OpenAPI docs show the three values as a real enum IDE autocomplete and linters can see them.
Validation errors from the schema layer use FastAPI’s standard 422 shape. Example for an unknown body key:
{
  "detail": [
    {
      "type": "extra_forbidden",
      "loc": ["body", "registration"],
      "msg": "Extra inputs are not permitted",
      "input": "VT-ABC"
    }
  ]
}
loc tells you exactly which key was bad. Common offenders: registration (use tail_number), location_country (use country), estimated_teardown_date (use start_date), aircraft_type_id (use the name, not the UUID).
The response includes a Retry-After header in seconds. Wait at least that long before retrying. Exponential backoff on top is good practice the limit window resets gradually.If you’re hitting bucket A (the 600 req/min per-key limit) regularly, your ERP is probably retrying too aggressively. Mint a second key for batch jobs that need their own budget.
500 is an unexpected server-side bug. 502 storage_unavailable is a Supabase Storage upstream issue.For both: retry the request with backoff. If it persists for more than a minute, capture the request id (returned in the x-request-id response header) and email support@teardowns.aero with the id + a description.

What we don’t return

A few common shapes you might expect that the API deliberately doesn’t use:
  • Plain text errors. Every error has a structured JSON body. No bare strings. No HTML pages.
  • Different shapes for different error families. The shape is always { "detail": { "error_code", "message", ...optional fields } } (for business errors) or FastAPI’s default 422 shape (for schema errors).
  • success: true/false envelopes. Successes return the resource directly. Branch on HTTP status, not a wrapper field.