# Error Handling



When a request fails, the response body is JSON — but the **shape depends on which layer rejected the call**. Your client should branch on HTTP status first, then parse the body using the rules below. This page is the single source of truth for platform-wide error handling across all API versions (`/v2`, `/v3`, `/v3.1`) and all endpoints.

Auth failures differ between the two channels — <ApiProviderToggle />

| Layer                  | Top-level field              | Typical statuses                  |
| ---------------------- | ---------------------------- | --------------------------------- |
| **Auth / quota layer** | `message` or `code` (string) | `401`, `403`, `429`               |
| **SkyLink API**        | `detail` (string or array)   | `404`, `422`, `500`, `502`, `504` |

Always read `Content-Type: application/json`. Do not assume one error format for every status code.

Endpoint reference pages document **only** failures whose meaning or `detail` text is specific to that resource (for example [METAR](https://en.wikipedia.org/wiki/METAR) station missing vs [TAF](https://en.wikipedia.org/wiki/Terminal_aerodrome_forecast) not issued). They link here for auth, validation, rate limits, and server errors.

***

## Auth and quota errors [#auth-and-quota-errors]

These occur before or instead of reaching the data layer. The body uses &#x2A;*`message`*&#x2A; or &#x2A;*`code`**, not `detail`.

<ForProvider only="direct">
  **401 — missing or invalid API key**

  ```json
  {
    "code": "missing_api_key",
    "message": "Provide your key in the x-api-key header."
  }
  ```

  The key is read from the `x-api-key` header only — passing it as a query parameter is not accepted and returns the same `401`.

  **403 — endpoint not included in your plan**

  ```json
  {
    "code": "plan_forbidden",
    "message": "This endpoint is not included in your plan."
  }
  ```

  **429 — abuse ceiling reached**

  ```json
  {
    "message": "Too many requests"
  }
  ```

  See [Rate Limits](/docs/rate-limits#quota-headers) — on the direct API this is the per-day anti-abuse ceiling, not your monthly allowance.
</ForProvider>

<ForProvider only="rapidapi">
  **401 — invalid or missing API key**

  ```json
  {
    "message": "Invalid API key. Go to https://docs.rapidapi.com/docs/keys for more info."
  }
  ```

  **403 — not subscribed**

  ```json
  {
    "message": "You are not subscribed to this API."
  }
  ```

  **429 — rate limit**

  ```json
  {
    "message": "Too many requests"
  }
  ```
</ForProvider>

<DocDetails title="JSON Schema — gateway errors (401, 403, 429)">
  ```json
  {
    "type": "object",
    "required": ["message"],
    "properties": {
      "message": { "type": "string" }
    },
    "additionalProperties": true
  }
  ```
</DocDetails>

Fix the key or subscription — **do not retry** `401` or `403`. Back off and retry `429`; see [Retry rules](#retry-rules) and [Rate Limits](/docs/rate-limits) for quota headers.

See [Getting Started](/docs/getting-started) for header setup.

***

## Validation errors (422) [#validation-errors-422]

When the request reaches SkyLink but fails input validation, `detail` is an **array** of objects:

```json
{
  "detail": [
    {
      "type": "string_too_short",
      "loc": ["path", "icao"],
      "msg": "String should have at least 4 characters",
      "input": "KF",
      "ctx": { "min_length": 4 }
    }
  ]
}
```

| Field            | Description                                                             |
| ---------------- | ----------------------------------------------------------------------- |
| `detail[].type`  | Validator id, e.g. `string_too_short`                                   |
| `detail[].loc`   | Path to the bad field, e.g. `["path", "icao"]` or `["query", "parsed"]` |
| `detail[].msg`   | Human-readable explanation                                              |
| `detail[].input` | Value that was rejected                                                 |
| `detail[].ctx`   | Optional constraints                                                    |

<DocDetails title="JSON Schema — 422 (detail array)">
  ```json
  {
    "type": "object",
    "required": ["detail"],
    "properties": {
      "detail": {
        "type": "array",
        "items": {
          "type": "object",
          "required": ["type", "loc", "msg", "input"],
          "properties": {
            "type": { "type": "string" },
            "loc": { "type": "array", "items": { "type": "string" } },
            "msg": { "type": "string" },
            "input": { "type": "string" },
            "ctx": { "type": "object" }
          },
          "additionalProperties": true
        }
      }
    },
    "additionalProperties": false
  }
  ```
</DocDetails>

**Do not retry** — fix the request parameters and send again.

***

## Server and upstream errors (5xx) [#server-and-upstream-errors-5xx]

When SkyLink or an upstream data service fails:

```json
{
  "detail": "Internal Server Error"
}
```

<DocDetails title="JSON Schema — 5xx (detail string)">
  Same shape as business `detail` strings (`404` uses this form too):

  ```json
  {
    "type": "object",
    "required": ["detail"],
    "properties": {
      "detail": { "type": "string" }
    },
    "additionalProperties": false
  }
  ```
</DocDetails>

| Status | Meaning                           |
| ------ | --------------------------------- |
| `500`  | Internal server error             |
| `502`  | Upstream data service unavailable |
| `504`  | Upstream timed out                |

**Retry** with exponential backoff (max 3 attempts). See [Retry rules](#retry-rules).

***

## Not found (404) [#not-found-404]

`404` responses use SkyLink's string `detail` form, but **the message and meaning are endpoint-specific** — a [METAR](https://en.wikipedia.org/wiki/METAR) with no station, a [TAF](https://en.wikipedia.org/wiki/Terminal_aerodrome_forecast) with no forecast, an unknown airport ICAO, and a missing historical flight each carry different semantics. Document `404` on each **reference page** (or section integration guide when the pattern spans several endpoints). Do not duplicate every variant here; link from your handler to the page that matches the endpoint you called.

| Section               | Where `404` is documented                                                                                                                                                                                              |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Weather — METAR / TAF | [METAR](/docs/v31/weather-metar#error-responses) · [TAF](/docs/v31/weather-taf#error-responses) · [When weather data is missing](/docs/v31/weather-integration-errors#when-weather-data-is-missing)                    |
| Weather — en-route    | [Winds Aloft](/docs/v31/winds-aloft#error-responses) · [PIREPs](/docs/v31/pireps#error-responses) · [AIRMET/SIGMET](/docs/v31/airsigmet#error-responses)                                                               |
| Airport data          | [Airports](/docs/v31/airports#error-responses) · [Airport search](/docs/v31/airport-search#error-responses)                                                                                                            |
| Reference data        | [Countries](/docs/v31/countries#error-responses) · [Regions](/docs/v31/regions#error-responses)                                                                                                                        |
| Live ADS-B            | [ADS-B tracking](/docs/v31/adsb#error-responses)                                                                                                                                                                       |
| Historical ADS-B      | [Historical ADS-B hub](/docs/v31/historical-adsb#error-responses) · [Flight detail](/docs/v31/historical-adsb-flight-detail#error-responses) · [Position history](/docs/v31/historical-adsb-positions#error-responses) |
| Charts                | [Aerodrome charts](/docs/v31/charts#error-responses)                                                                                                                                                                   |
| Flight operations     | [Flight status](/docs/v31/flight-status#error-responses)                                                                                                                                                               |
| Webhooks              | [Subscriptions](/docs/v31/webhooks-subscriptions#error-responses)                                                                                                                                                      |

**Important:** Some endpoints return an empty result in HTTP `200` instead of `404` (for example [aircraft lookup](/docs/v31/aircraft-lookup) when no tail number matches). Check the reference page before mapping all "not found" cases to `404`.

Plan-gate `401`/`403` on Historical [ADS-B](https://www.faa.gov/air_traffic/technology/adsb) (subscription too low for the path prefix) are documented on the [Historical ADS-B hub](/docs/v31/historical-adsb#error-responses), not here.

***

## Retry rules [#retry-rules]

| Status              | Retry? | Action                                                                      |
| ------------------- | ------ | --------------------------------------------------------------------------- |
| `401`, `403`        | No     | Fix [API key / subscription](/docs/getting-started#key-security)            |
| `404`               | No     | Endpoint-specific fallback — see reference page                             |
| `422`               | No     | Fix input                                                                   |
| `429`               | Yes    | Exponential backoff; check [quota headers](/docs/rate-limits#quota-headers) |
| `500`, `502`, `504` | Yes    | Exponential backoff (max 3 attempts)                                        |

```python
import time
import requests

RETRYABLE = {429, 500, 502, 504}


def fetch_with_retry(url: str, headers: dict, params: dict | None = None, max_retries: int = 3) -> requests.Response:
    delay = 1.0
    for attempt in range(max_retries + 1):
        r = requests.get(url, headers=headers, params=params, timeout=(10, 15))
        if r.status_code not in RETRYABLE or attempt == max_retries:
            return r
        time.sleep(delay)
        delay *= 2
    return r
```

For plan quotas, overage pricing, and Historical [ADS-B](https://www.faa.gov/air_traffic/technology/adsb) path tiers, see [Rate Limits](/docs/rate-limits).
