> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flyhub.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Virtual Airlines API Reference

> Connect an airline website, dashboard, or private tool to the read-only Virtual Airlines API.

# Virtual Airlines API Reference

This page is for airline owners, managers, and people building an airline website, dashboard, roster page, or private tool.

The Virtual Airlines API lets an approved integration read selected information about one airline. It is deliberately read-only: it can show an airline profile, pilots, flights, fleet, schedules, and aggregate statistics, but it cannot book flights, change ticket prices, move aircraft, manage people, edit routes, or spend FH\$.

The API is designed to be understandable and safe to integrate. Every API key belongs to exactly one airline, every key has explicit read-only scopes, list results are paginated, and responses contain only the identifiers needed by an integration.

The endpoint links below open interactive request examples and response-field references. Use a key with only the scopes your integration needs, and never paste credentials into a public page.

## Start here

1. The airline owner opens **Manage airline → Ownership → Integrations**.
2. Create an API key with a descriptive name, the smallest set of scopes the integration needs, and an optional expiry.
3. Copy the secret immediately. FlyHub displays it only once.
4. Store it on your website or service server. Never put it in browser JavaScript, a public repository, an embed, or a Discord message.
5. Call the public endpoint with the key in the `Authorization` header.

The public API base is:

```text theme={null}
https://flyhub.app/api/v1/va
```

The resource endpoint is:

```text theme={null}
GET https://flyhub.app/api/v1/va/{resource}
```

Example:

```bash theme={null}
curl --fail-with-body \
  -H "Authorization: Bearer ${FHVA_API_KEY}" \
  "https://flyhub.app/api/v1/va/airline"
```

The `${FHVA_API_KEY}` variable should exist only in the server environment of your integration. A browser-facing page should call your server, and your server should call FlyHub.

## Read-only by design

The public integration is read-only. Use FlyHub's own screens for bookings, finance, fleet, people, route editing, and other actions that change airline state. Do not build a third-party integration around private screens or undocumented requests.

## Authentication

Send the key as a bearer credential:

```http theme={null}
Authorization: Bearer fhva_live_<public-part>_<secret-part>
```

FlyHub API keys start with `fhva_live_`. Treat the complete value as a secret.

The secret is returned only in the successful response that creates the key. If it is lost, create a replacement key and revoke the old one.

The airline is derived from the key. There is no `airlineId` query parameter, and a caller cannot use a key issued to one airline to read another airline.

### Authentication outcomes

| Status | Meaning | What to do |
| - | - | - |
| `401` | The header is missing or the key is invalid, revoked, or expired. | Check the server secret, then create a replacement key if necessary. |
| `403` | The key is valid but does not include the scope required by this resource. | Add the scope to a new key, or use a key that already has it. |
| `404` | The resource name is not part of the public API. | Check the path and use one of the six resources listed below. |
| `429` | The request rate is temporarily limited. | Honor `Retry-After` and the reset headers; reduce polling and page size. |
| `503` | FlyHub cannot safely serve the request at the moment. | Retry with backoff. Do not disable your integration's security checks to work around it. |

The airline attached to a key must also be in an operating state accepted by the public API. Permanently closed or otherwise unavailable airlines cannot be read through an active integration key.

## Scopes

Give a key only the scopes it needs. Scopes cannot write to the airline.

| Scope | Resource | Includes |
| - | - | - |
| `airline:read` | `airline` | Name, codes, home base, status, business model, application mode, summary, and counts. |
| `pilots:read` | `pilots` | Active roster names, ranks, membership dates, activity, flights, hours, contribution points, and reliability. |
| `flights:read` | `flights` | Public logbook activity, routes, aircraft, times, duration, distance, score, and result status. |
| `fleet:read` | `fleet` | Active aircraft registrations, types, stations, condition, maintenance state, hours, cycles, and availability. |
| `schedules:read` | `schedules` | Published network schedules, routes, days, planned block time, eligible aircraft, and schedule status. |
| `statistics:read` | `statistics` | Aggregate airline, pilot, fleet, schedule, contribution, and reliability totals. |

The available scope names are shown in the Ownership integration screen. A key may contain one or more scopes, up to all six.

## Common request parameters

The list resources accept the same two query parameters:

| Parameter | Default | Limits | Meaning |
| - | -: | -: | - |
| `limit` | `25` | `1`–`100` | Number of records requested. |
| `offset` | `0` | `0`–`10000` | Number of records skipped before the page. |

Example:

```text theme={null}
GET /api/v1/va/flights?limit=50&offset=100
```

Invalid or missing numeric parameters fall back to their defaults instead of returning a validation error. A `limit` of `0` is treated as `1`. Values above the maximum are capped. The API uses limit-and-offset pagination, so an integration that needs a large list should page carefully and respect the offset ceiling.

The `limit` and `offset` parameters do not change the shape of `airline` or `statistics`, which are single objects.

## Response envelopes

### A single object

`airline` and `statistics` return:

```json theme={null}
{
  "data": {},
  "meta": {
    "apiVersion": "2026-07-21"
  }
}
```

### A paginated list

`pilots`, `flights`, `fleet`, and `schedules` return:

```json theme={null}
{
  "data": [],
  "pagination": {
    "offset": 0,
    "limit": 25,
    "total": 0
  },
  "meta": {
    "apiVersion": "2026-07-21"
  }
}
```

`total` is the total number of records available for that airline under the resource's filters, not the number returned on the current page.

Dates and timestamps are ISO 8601 values in UTC. A value can be `null` when FlyHub has no value to report. Treat unknown fields as forward-compatible: ignore fields you do not recognize rather than failing the whole response.

### Error envelope

Public API errors use this shape:

```json theme={null}
{
  "error": {
    "code": "rate_limited",
    "message": "Request rate temporarily limited"
  }
}
```

The supported public error codes are `unauthorized`, `forbidden`, `not_found`, `rate_limited`, and `service_unavailable`.

## Public API endpoints

| Method | Endpoint | Scope | Result |
| - | - | - | - |
| `GET` | `/api/v1/va/airline` | `airline:read` | One airline profile object. |
| `GET` | `/api/v1/va/pilots` | `pilots:read` | Active roster, paginated. |
| `GET` | `/api/v1/va/flights` | `flights:read` | Airline logbook activity, paginated. |
| `GET` | `/api/v1/va/fleet` | `fleet:read` | Non-retired aircraft, paginated. |
| `GET` | `/api/v1/va/schedules` | `schedules:read` | Active and suspended schedules, paginated. |
| `GET` | `/api/v1/va/statistics` | `statistics:read` | Aggregate statistics object. |

There are no public `POST`, `PATCH`, or `DELETE` endpoints under `/api/v1/va`. A `POST` to a public resource is not a way to book a flight or update an airline.

### Endpoint references

| Endpoint | Interactive reference |
| - | - |
| `GET /api/v1/va/airline` | [Airline profile](https://docs.flyhub.app/virtual-airlines/api/airline) |
| `GET /api/v1/va/pilots` | [Pilots](https://docs.flyhub.app/virtual-airlines/api/pilots) |
| `GET /api/v1/va/flights` | [Flights](https://docs.flyhub.app/virtual-airlines/api/flights) |
| `GET /api/v1/va/fleet` | [Fleet](https://docs.flyhub.app/virtual-airlines/api/fleet) |
| `GET /api/v1/va/schedules` | [Schedules](https://docs.flyhub.app/virtual-airlines/api/schedules) |
| `GET /api/v1/va/statistics` | [Statistics](https://docs.flyhub.app/virtual-airlines/api/statistics) |

## `GET /api/v1/va/airline`

Returns the identity and high-level status of the airline attached to the key. It is not paginated.

```bash theme={null}
curl \
  -H "Authorization: Bearer ${FHVA_API_KEY}" \
  "https://flyhub.app/api/v1/va/airline"
```

Example response:

```json theme={null}
{
  "data": {
    "name": "Example Air",
    "shortName": "Example",
    "slug": "example-air",
    "summary": "A scheduled international virtual airline.",
    "icaoCode": "EXA",
    "callsign": "EXAMPLE",
    "homeIcao": "SKBO",
    "homeCountryCode": "CO",
    "status": "active",
    "businessModel": null,
    "applicationMode": "open",
    "pilotCount": 18,
    "fleetCount": 7,
    "createdAt": "2026-07-21T00:00:00.000Z",
    "updatedAt": "2026-07-21T12:00:00.000Z"
  },
  "meta": {
    "apiVersion": "2026-07-21"
  }
}
```

`pilotCount` counts active airline memberships. `fleetCount` excludes aircraft whose operational status is `retired` and uses the same airline-group scope as the fleet endpoint.

## `GET /api/v1/va/pilots`

Returns the airline's active roster. Suspended, rejected, left, and removed memberships are not returned.

```bash theme={null}
curl \
  -H "Authorization: Bearer ${FHVA_API_KEY}" \
  "https://flyhub.app/api/v1/va/pilots?limit=25&offset=0"
```

Each item contains:

| Field | Meaning |
| - | - |
| `displayName` | The pilot name shown by FlyHub. |
| `rank` | Current rank in this airline. |
| `joinedAt` | Time the active membership began. |
| `lastActivityAt` | Most recent tracked airline activity, when available. |
| `completedFlights` | Completed-flight total in the airline pilot statistics. |
| `completedHours` | Completed hours, rounded to one decimal place. |
| `contributionPoints` | Contribution points attributed to the pilot in this airline. |
| `reliabilityScore` | Reliability value used by the airline statistics, defaulting to `100` when no value exists. |
| `lastFlightAt` | Most recent completed flight time, or `null`. |

The list is ordered by `joinedAt` from oldest to newest. User IDs are intentionally omitted. Use a display name plus the airline context for presentation, not as a guaranteed permanent identifier.

## `GET /api/v1/va/flights`

Returns the airline's public logbook activity, newest first. The result is a record of airline flight entries, not a command to start or book a flight. Always inspect `status`: a row can describe a successful, cancelled, rejected, or otherwise closed result.

```bash theme={null}
curl \
  -H "Authorization: Bearer ${FHVA_API_KEY}" \
  "https://flyhub.app/api/v1/va/flights?limit=25&offset=0"
```

A flagship airline's API key also includes flights flown for all non-closed subsidiaries in its own group. A subsidiary's API key returns only that subsidiary's flights; an independent airline's key returns only its own flights. Use `airlineIcao` to tell operators apart.

Each item contains:

| Field | Meaning |
| - | - |
| `airlineIcao` | ICAO code of the airline that operated the flight. |
| `status` | Current logbook entry status. Treat this as the result authority. |
| `flightNumber` | Airline flight number. |
| `originIcao` | Origin airport ICAO code. |
| `destinationIcao` | Destination airport ICAO code. |
| `aircraftRegistration` | Aircraft registration used for the entry. |
| `aircraftTypeIcao` | Aircraft type code. |
| `offBlockAt` | Off-block timestamp, when available. |
| `onBlockAt` | On-block timestamp, when available. |
| `blockMinutes` | Recorded block time in minutes. |
| `distanceNm` | Recorded distance in nautical miles. |
| `score` | Flight score, or `null` when no score was recorded. |
| `virtualPeriod` | Virtual operating period attached to the entry, when available. |

The public record does not expose private identifiers for bookings, results, pilots, or aircraft.

## `GET /api/v1/va/fleet`

Returns non-retired aircraft associated with the airline, ordered by registration. A flagship airline's API key also includes aircraft from all non-closed subsidiaries in its own group. A subsidiary's API key returns only that subsidiary's fleet; an independent airline's key returns only its own fleet. Pagination and its total apply to the combined fleet.

```bash theme={null}
curl \
  -H "Authorization: Bearer ${FHVA_API_KEY}" \
  "https://flyhub.app/api/v1/va/fleet?limit=100"
```

Each item contains:

| Field | Meaning |
| - | - |
| `registration` | Aircraft registration. |
| `displayName` | Airline name for the aircraft, when set. |
| `aircraftTypeIcao` | Aircraft type code. |
| `ownershipType` | Ownership category, such as a leased or owned asset. |
| `operationalStatus` | Current operational state. |
| `currentIcao` | Last recorded airport location, when available. |
| `conditionPercent` | Current aircraft condition percentage. |
| `maintenanceState` | Current maintenance state. |
| `airframeHours` | Airframe time, rounded to one decimal place. |
| `airframeCycles` | Airframe cycle count. |
| `availableFrom` | Time at which the aircraft can become available, when applicable. |
| `maintenanceDueAt` | Maintenance due timestamp, when applicable. |
| `acquiredAt` | Time the airline acquired the aircraft, when available. |
| `updatedAt` | Last update time for the public record. |

Retired aircraft are excluded. The public API does not provide a fleet-management action.

## `GET /api/v1/va/schedules`

Returns schedules whose template status is `active` or `suspended`, ordered by flight number. Archived or other non-public schedule states are not included.

```bash theme={null}
curl \
  -H "Authorization: Bearer ${FHVA_API_KEY}" \
  "https://flyhub.app/api/v1/va/schedules?limit=100&offset=0"
```

Group scope works like the flights endpoint: a flagship key includes the schedules of its non-closed subsidiaries, and `airlineIcao` names the operating airline.

Each item contains:

| Field | Meaning |
| - | - |
| `airlineIcao` | ICAO code of the airline that operates the schedule. |
| `flightNumber` | Published airline flight number. |
| `originIcao` | Route origin. |
| `destinationIcao` | Route destination. |
| `operationType` | Route operation type, such as passenger or cargo. |
| `operatingDaysMask` | Seven-day schedule mask. |
| `plannedBlockMinutes` | Planned block time in minutes. |
| `eligibleAircraftTypes` | Aircraft type codes accepted by the schedule. |
| `status` | `active` or `suspended`. |
| `version` | Schedule version number. |
| `validFrom` | Schedule validity start, when set. |
| `validUntil` | Schedule validity end, when set. |
| `updatedAt` | Last update time. |

### Reading `operatingDaysMask`

The mask uses Monday-first bits:

| Bit value | Day |
| -: | - |
| `1` | Monday |
| `2` | Tuesday |
| `4` | Wednesday |
| `8` | Thursday |
| `16` | Friday |
| `32` | Saturday |
| `64` | Sunday |

For example, `31` (`1 + 2 + 4 + 8 + 16`) means Monday through Friday. Operating-day checks in Virtual Airlines use UTC, so convert a timestamp to UTC before deciding whether a schedule operates.

## `GET /api/v1/va/statistics`

Returns one aggregate object. It is deliberately summary-only and has no pagination. A flagship airline's API key aggregates flights, hours, fleet, schedules, contribution and reliability across all non-closed subsidiaries in its own group, matching the flights, fleet and schedules endpoints. `activePilots` counts the shared group roster. A subsidiary or independent airline key returns only its own totals.

```bash theme={null}
curl \
  -H "Authorization: Bearer ${FHVA_API_KEY}" \
  "https://flyhub.app/api/v1/va/statistics"
```

Current fields:

| Field | Meaning |
| - | - |
| `completedFlights` | Count of airline logbook entries included by the current aggregate. |
| `completedHours` | Sum of airline pilot completed minutes expressed in hours and rounded to one decimal. |
| `activePilots` | Active membership count. |
| `fleetSize` | Non-retired aircraft count. |
| `activeSchedules` | Active schedule-template count. |
| `contributionPoints` | Sum of airline pilot contribution points. |
| `averageReliabilityScore` | Average pilot reliability rounded to two decimals, or `null` if no statistic exists. |

Statistics are aggregate values, not a replacement for the paginated resource records. For example, use `pilots` when you need the roster and `fleet` when you need individual aircraft.

## Rate limits and safe retries

The public API may temporarily limit requests. Do not hard-code a numeric allowance: use the response headers and the status code to decide when to retry.

| Header | Meaning |
| - | - |
| `Cache-Control: private, no-store` | The response is private and should not be stored as a shared cache entry. |
| `X-RateLimit-Limit` | Requests allowed for the current public API key/IP window. |
| `X-RateLimit-Remaining` | Requests left in that window. |
| `X-RateLimit-Reset` | ISO timestamp when the request window resets. |
| `Retry-After` | Seconds to wait on a `429` response. |

The exact allowance can change as the service evolves. Treat `X-RateLimit-*` values as authoritative for the current response and keep retry attempts bounded. A `503` means the service is temporarily unavailable; retry later with backoff.

### A polite polling pattern

For a dashboard, fetch only the resources it actually displays, use the smallest useful `limit`, and refresh on a human scale rather than every few seconds. On `429`, pause until `Retry-After`; on `503`, retry with exponential backoff. Do not run several identical requests in parallel.

## Example integration client

The following example keeps the key on a server and handles the common response shapes:

```ts theme={null}
type VaResponse<T> = {
  data: T;
  pagination?: { offset: number; limit: number; total: number };
  meta: { apiVersion: string };
};

export async function getVaResource<T>(resource: string, params: Record<string, string | number> = {}) {
  const url = new URL(`https://flyhub.app/api/v1/va/${resource}`);
  for (const [key, value] of Object.entries(params)) url.searchParams.set(key, String(value));

  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.FHVA_API_KEY}` },
    cache: 'no-store',
  });
  const body = await response.json().catch(() => null) as VaResponse<T> | { error?: { code?: string; message?: string } } | null;
  if (!response.ok) {
    const error = body && 'error' in body ? body.error : null;
    throw new Error(`${response.status}: ${error?.code ?? 'request_failed'}`);
  }
  return body as VaResponse<T>;
}
```

For a list, continue while `offset + data.length < pagination.total`. Stop on a repeated response or an error; never retry a failed page indefinitely.

## Managing access

Airline owners manage website connections from **Manage airline → Ownership → Integrations**. The screen lets an owner create a read-only key, choose its scopes and expiry, copy the secret once, and revoke it later.

Keep the secret on your integration server. If it is lost, create a replacement key, update and verify your integration, then revoke the old key. The public API does not include endpoints for managing keys; use the Ownership screen for those actions.

## What the public API does not do

The public API does not provide:

* flight booking, cancellation, dispatch, or completion;
* ticket-price changes or market/economy commands;
* pilot applications, role changes, suspensions, or removals;
* fleet leasing, transfers, positioning, configuration, or maintenance;
* route and schedule editing;
* FH\$ transfers, settlement changes, or billing controls;
* ownership changes or airline retirement;
* generic custom webhooks, event replay, delivery history, or a webhook signature endpoint.

Use FlyHub's own screens for those actions. The next page, [Virtual Airlines Webhooks](./webhooks), documents the supported Discord notification integration.

## Integration checklist

Before giving an integration to your pilots:

* keep the API secret server-side;
* request only the scopes the page needs;
* handle `401`, `403`, `404`, `429`, and `503` explicitly;
* honor `Retry-After` and the reset headers;
* paginate instead of requesting large repeated pages;
* treat timestamps as UTC;
* ignore fields added in a later API version;
* do not assume a display name, flight number, or registration is a permanent identifier;
* rotate credentials by create → deploy → verify → revoke;
* remove the old key from logs, source control, CI output, and screenshots.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.