API
The public API is the primary surface for downstream consumers. It serves the verified dataset and exposes the rules engine. It is operated with the care of paid infrastructure: real SLAs, a status page, public incident reports, and a published roadmap.
Base URL: https://api.flighthelp.net
API design philosophy
REST first, GraphQL alongside. The REST API covers the common cases simply. The GraphQL endpoint covers the cases where consumers need to fetch precisely-shaped data without over-fetching. Both backends read from the same Postgres source-of-truth.
Versioned in the URL. /v1/ is the current major version. /v2/ will live alongside it during transitions, with at least 18 months of overlap before /v1/ is sunset. Breaking changes get a new major version. Backwards-compatible additions are deployed under the existing version.
Provenance in every response. Every response includes a _meta block with last_verified_at, verifier_count, confidence_score, and sources. AI assistants and other downstream consumers can show users where the data came from. Missing or stale data is visible, not hidden.
Pagination is cursor-based. Offset pagination is unstable for live data. Every collection endpoint returns next_cursor and previous_cursor tokens.
Errors are structured and useful. Following RFC 7807 (Problem Details for HTTP APIs), every error response includes type, title, status, detail, and instance. Error types are documented and stable.
Authentication and rate limits
Three free tiers, plus one paid tier. All tiers use Bearer token authentication except anonymous.
| Tier | Rate limit | Requirements |
|---|---|---|
| Anonymous | 100 requests/hour per IP | None |
| Registered | 5,000 requests/hour | Sign up with email, get a key |
| Verified non-profit | 100,000 requests/hour | Apply with project description |
| Commercial | Negotiated, SLA-backed | Paid subscription |
Rate limits are tracked per API key. Burst capacity equals 20% of the hourly limit. Limits reset on a rolling 60-minute window, not a fixed clock hour.
When a rate limit is approached, the API returns X-RateLimit-Remaining and X-RateLimit-Reset headers. When exceeded, the API returns 429 Too Many Requests with a Retry-After header.
Abusive consumers get throttled, not banned, unless they are actively scraping in violation of rate limits. The principle: the data is open, the API serves the data, and gating it punitively contradicts the mission.
REST endpoints
Airlines
GET /v1/airlines
?country=DE
&alliance=star
&status=active
&cursor=...
&limit=50
Response 200:
{
"data": [Airline, ...],
"_meta": {
"total_estimated": 421,
"next_cursor": "...",
"previous_cursor": null,
"as_of": "2026-05-22T14:32:11Z"
}
}
GET /v1/airlines/{iata}
-> full Airline record
GET /v1/airlines/{iata}/baggage-rules
?fare_class=...
?bag_type=...
-> array of BaggageRule
GET /v1/airlines/{iata}/contacts
?purpose=baggage
?country=DE
?channel=phone
-> array of ContactMethod, sorted by success_rate descending
GET /v1/airlines/{iata}/fees
?fee_type=change
-> array of Fee
GET /v1/airlines/{iata}/fare-classes
-> array of FareClass
Airports
GET /v1/airports
?country=DE
?size_class=large
?cursor=...
-> paginated list
GET /v1/airports/{iata}
-> full Airport record
GET /v1/airports/{iata}/lounges
?access=priority_pass
-> array of Lounge
GET /v1/airports/{iata}/transport
-> array of GroundTransport
GET /v1/airports/{iata}/lost-and-found
-> array of ContactMethod
Scenarios and regulations
GET /v1/scenarios
?jurisdiction=eu-261-2004
?disruption_type=cancellation
-> paginated list
GET /v1/scenarios/{slug}
-> full Scenario record including templates and escalation path
GET /v1/regulations
?jurisdiction=DE
-> paginated list
GET /v1/regulations/{slug}
-> full Regulation record
GET /v1/regulations/{slug}/case-law
-> array of court rulings cited by the rules engine
Tools
POST /v1/compensation/evaluate
body: Scenario (see RULES-ENGINE.md)
-> Determination
This endpoint is stateless and not rate-limited differently from
other endpoints. The engine is invoked server-side, eliminating
the need for consumers to install the library.
POST /v1/bag-fit/check
body: {
dimensions_cm: { length, width, height },
weight_kg: number,
fare_class_filters?: string[]
}
-> {
airlines_accepting: [{ airline_id, fare_class_id, ... }],
airlines_rejecting: [{ airline_id, reason }],
airlines_with_fee: [{ airline_id, fee, ... }]
}
POST /v1/distance/calculate
body: { origin: IATA, destination: IATA }
-> { km: number, miles: number, eu_261_band: "short"|"medium"|"long" }
Search
GET /v1/search
?q=lufthansa+baggage
?type=airline,scenario
?lang=en
-> {
results: [
{ type, id, title, snippet, url, score },
...
]
}
Backed by Meilisearch; sub-50ms median latency at scale.
Activity
GET /v1/changes
?since=2026-05-01T00:00:00Z
?entity_type=BaggageRule
?airline_id=lufthansa
-> paginated stream of Revisions
GET /v1/contributors/{id}
-> public Contributor profile (only if public_profile=true)
GET /v1/leaderboards/{scope}
scope: global | regional | airline | airport
-> array of Contributors with reputation scores
GET /v1/sources/{id}
-> full Source record, including snapshot URL
GET /v1/sources/{id}/snapshot
-> redirects to archived snapshot
Webhooks
Registered consumers can subscribe to entity-change webhooks.
POST /v1/webhooks
body: {
url: "https://your-app.com/webhook",
events: ["airline.updated", "baggage_rule.created", ...],
filters: {
airline_ids: ["lufthansa", "ryanair"],
entity_types: ["BaggageRule"]
},
secret: "shared_secret_for_hmac"
}
-> { webhook_id, status: "active" }
GET /v1/webhooks
-> list of caller's webhooks
DELETE /v1/webhooks/{id}
-> 204
Webhook deliveries include an HMAC-SHA256 signature in the X-Flighthelp-Signature header for verification. Failed deliveries are retried with exponential backoff up to 24 hours, then the webhook is marked unhealthy and the consumer is notified.
Bulk
GET /v1/bulk/snapshot/latest
-> JSON manifest pointing to bulk dump files in R2
GET /v1/bulk/snapshot/{YYYY-MM-DD}
-> JSON manifest for a specific date's snapshot
Direct download links are CDN-served from Cloudflare R2 with
no rate limit; suitable for nightly ETL processes.
GraphQL endpoint
POST https://api.flighthelp.net/graphql
Full schema with introspection enabled. The schema mirrors the REST API but allows precise field selection and combined queries.
Example:
query {
airline(iata: "LH") {
common_name
contacts(purpose: BAGGAGE, country: "DE") {
channel
value
success_rate
typical_wait_minutes
}
baggage_rules(fare_class: "economy") {
bag_type
max_dimensions_cm { length width height }
max_weight_kg
enforcement_strictness
last_verified_at
}
}
scenario(slug: "eu-cancellation-less-than-14-days") {
title
your_rights
template_messages(language: "de") {
subject
body
}
}
}
GraphQL has its own rate limits based on query complexity (computed by depth × breadth). A 5,000 REST request/hour key gets approximately 50,000 GraphQL points/hour.
Response shape
Every successful response wraps data in a consistent envelope:
{
"data": { ... } | [ ... ],
"_meta": {
"as_of": "2026-05-22T14:32:11Z",
"data_version": "2026-05-22T03:00:00Z",
"engine_version": "1.4.2",
"schema_version": "1.2.0",
"request_id": "req_abc123",
"rate_limit": {
"remaining": 4892,
"reset_at": "2026-05-22T15:32:11Z"
},
"next_cursor": "...",
"previous_cursor": "..."
}
}
Every fact-carrying object within data carries its own provenance:
{
"airline_id": "ryanair",
"fare_class_id": "ryanair:basic",
"bag_type": "carry_on",
"max_dimensions_cm": { "length": 40, "width": 20, "height": 25 },
"max_weight_kg": 10,
"enforcement_strictness": "brutal",
"_provenance": {
"last_verified_at": "2026-05-19T10:14:00Z",
"verifier_count": 47,
"confidence_score": 0.94,
"sources": [
{ "id": "src_xyz", "type": "airline_official", "url": "..." },
{ "id": "src_abc", "type": "user_report", "url": "..." }
],
"revisions_url": "https://api.flighthelp.net/v1/entities/{id}/revisions"
}
}
The revisions_url lets any consumer pull the full edit history of a specific datapoint.
Error responses
Following RFC 7807:
{
"type": "https://flighthelp.net/errors/airline-not-found",
"title": "Airline not found",
"status": 404,
"detail": "No airline matches IATA code 'XX'.",
"instance": "/v1/airlines/XX",
"request_id": "req_abc123"
}
Common error types are documented at https://flighthelp.net/errors/. Stable URLs.
Error categories:
400— Bad request (malformed input, validation failure)401— Authentication required or invalid API key403— Authenticated but forbidden (e.g. paid tier required)404— Resource not found409— Conflict (e.g. webhook URL already registered)422— Unprocessable (e.g. scenario can't be evaluated due to missing fields)429— Rate limited500— Internal error (always logged and investigated)503— Temporarily unavailable (during maintenance windows, announced)
Caching
All GET responses include Cache-Control and ETag headers. Cloudflare caches aggressively at the edge; the origin only sees requests for uncached or stale content.
Typical cache lifetimes:
- Static resources (regulations, schemas): 24 hours
- Slowly-changing entities (airlines, airports): 1 hour
- Frequently-changing entities (contact wait times, recent edits): 5 minutes
- Tools (compensation evaluator, bag-fit): 1 hour for same input
When an entity is edited, the cache is invalidated edge-wide within 30 seconds via Cloudflare's purge API.
Consumers can pass Cache-Control: no-cache to bypass edge caching; the response will be served from origin.
SLAs
| Tier | Uptime | Latency (p50) | Latency (p99) | Support |
|---|---|---|---|---|
| Anonymous | best effort | best effort | best effort | community forum |
| Registered | 99.5% | <100ms | <500ms | email, 5 day reply |
| Non-profit | 99.5% | <100ms | <500ms | email, 2 day reply |
| Commercial | 99.9% | <50ms | <250ms | priority, 4hr reply |
The status page at status.flighthelp.net publishes real-time and historical uptime, regional latency, and incident reports. Major incidents get public post-mortems within 72 hours.
Versioning and deprecation
Breaking changes get a new major version. New major versions are announced at least 12 months before they ship. Old major versions are supported for at least 18 months after the new version ships.
Within a major version, only backwards-compatible changes are deployed:
- New endpoints
- New optional query parameters
- New fields in responses
- New enum values (consumers should treat unknown enums gracefully)
Deprecations are announced via:
- The
DeprecationHTTP header (RFC 8594) - The changelog at
https://flighthelp.net/changelog - An email to all registered consumers
- The status page
SDKs
Official SDKs are maintained in parallel:
@flighthelp/api-client— TypeScript/JavaScript, on npmflighthelp-api— Python, on PyPIflighthelp-php/api-client— PHP, on Composerflighthelp.dev/api-client— Go, on pkg.go.devflighthelp-api-client— Rust, on crates.ionet.flighthelp:api-client— Java, on Maven Centralflighthelp-api-client-swift— Swift, on SPM
Each SDK:
- Generates types from the schemas (auto-updated on schema release)
- Handles pagination, rate limits, and retries
- Exposes the same surface across languages
Community SDKs in other languages are welcome and listed in the docs once they meet basic quality standards.
What the API does not do
No write endpoints for arbitrary contributors via the public API. Edits go through contribute.flighthelp.net or its dedicated backend, which enforces moderation. The public API is read-only for the dataset.
No analytics or aggregation endpoints. No "top 10 worst airlines for baggage fees" computed-on-the-fly endpoint. Consumers can compute these from the bulk dump or by paginating the data. This keeps the API focused on serving facts, not opinions.
No proxying of airline APIs. flighthelp does not relay reservations queries, flight status, or other airline operational data. Consumers needing that should use Duffel, Amadeus, or the carrier's own API.
No personalization. The API serves the same data to every consumer (except for the per-key rate limit and the contributor profile endpoints). No A/B testing, no consumer-specific result shaping.