Error Handling

PreflightAPI uses standard HTTP status codes and returns JSON error responses. Errors originate from two sources — the APIM gateway (auth, rate limits, quotas, tier-gating) and the backend API (validation, not found, server errors) — each with a distinct response format.

Gateway Errors (401, 429, 403 Quota)

These errors are generated by the Azure API Management gateway before the request reaches the backend. They use a simple statusCode + message format:

{
  "statusCode": 429,
  "message": "Rate limit is exceeded. Try again in 52 seconds."
}
StatusCauseExample Message
401Missing or invalid subscription key"Access denied due to invalid subscription key. Make sure to provide a valid key for an active subscription."
429Rate limit exceeded"Rate limit is exceeded. Try again in 52 seconds."
403Monthly quota exceeded"Out of call volume quota. Quota will be replenished in 06:23:15."

Tier-Gating Errors (403)

When your subscription plan does not include access to the requested endpoint, the APIM gateway returns a 403 Forbidden response with a different format — an error string field instead of statusCode:

{
  "error": "This endpoint is not available on the Free tier. Please upgrade to Starter or Professional."
}

See the endpoint access table for which endpoints are available on each plan.

You can distinguish the two types of 403 by checking the response body: quota exceeded has { statusCode, message }, while tier-gating has { error }.

Backend API Errors (400, 404, 409, 500, 503)

Errors generated by the API backend use a rich, structured format with a machine-readable code for programmatic handling and a traceId for support:

{
  "code": "METAR_NOT_FOUND",
  "message": "No current METAR available for station 'KXYZ'",
  "timestamp": "2026-01-15T18:56:00Z",
  "traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "path": "/api/v1/metars/KXYZ"
}
FieldTypeDescriptionAlways Present
codestringMachine-readable error code (e.g., METAR_NOT_FOUND)Yes
messagestringHuman-readable description of what went wrongYes
timestampstringISO 8601 UTC timestamp of when the error occurredYes
traceIdstring?Correlation ID — include this when contacting supportUsually
pathstring?The request path that generated the errorUsually
detailsstring?Additional context (development environments only)No
validationErrorsobject?Field-level errors (only for 400 validation failures)No

HTTP Status Codes

CodeNameSourceDescription
400Bad RequestBackendInvalid query parameters, missing required fields, or invalid values.
401UnauthorizedAPIM GatewayMissing or invalid API key. Check the Ocp-Apim-Subscription-Key header.
403ForbiddenAPIM GatewayEndpoint not available on your plan (tier-gating) or monthly quota exceeded.
404Not FoundBackendThe requested resource does not exist (e.g., unknown ICAO code, no METAR available).
409ConflictBackendThe request conflicts with the current state (e.g., duplicate resource).
429Too Many RequestsAPIM GatewayRate limit exceeded. Includes a Retry-After header. See the rate limits page for details.
500Internal Server ErrorBackendAn unexpected error occurred. Include the traceId when contacting support.
503Service UnavailableBackendAn external data source (NOAA, FAA) is temporarily unavailable. Retry after a short delay.

Error Codes Reference

The code field in backend error responses contains one of these machine-readable values. Use these to handle specific errors programmatically:

General

INTERNAL_ERRORAn unexpected server error occurred
VALIDATION_ERROROne or more request parameters failed validation
NOT_FOUNDGeneric resource not found
CONFLICTOperation conflicts with current state

Airports

AIRPORT_NOT_FOUNDNo airport exists with the given identifier
AIRPORT_DIAGRAM_NOT_FOUNDNo airport diagram available for this airport
RUNWAY_NOT_FOUNDNo runway found matching the query
COMMUNICATION_FREQUENCY_NOT_FOUNDNo communication frequency found for this airport

Weather

METAR_NOT_FOUNDNo current METAR available for the given station
TAF_NOT_FOUNDNo current TAF available for the given station
WEATHER_SERVICE_UNAVAILABLENOAA weather service is temporarily unavailable
WEATHER_DATA_MISSINGWeather data was expected but not available

NOTAMs & Airspace

NOTAM_SERVICE_UNAVAILABLEFAA NOTAM service is temporarily unavailable
AIRSPACE_NOT_FOUNDNo airspace found matching the query
OBSTACLE_NOT_FOUNDNo obstacle found matching the query

Documents

CHART_SUPPLEMENT_NOT_FOUNDNo chart supplement available for this airport

Performance & Navigation

PERFORMANCE_CALCULATION_ERRORError calculating performance values
INVALID_PERFORMANCE_DATAProvided performance data is invalid or out of range
NAVLOG_CALCULATION_ERRORError computing the nav log

External Services

EXTERNAL_SERVICE_UNAVAILABLEA downstream service is temporarily unavailable

Validation Errors

When a request fails validation (400 Bad Request), the response includes a validationErrors object with field-level details. Each key is the parameter name, and the value is an array of error messages:

{
  "code": "VALIDATION_ERROR",
  "message": "One or more validation errors occurred.",
  "validationErrors": {
    "latitude": [
      "Latitude must be between -90 and 90 degrees"
    ],
    "radiusNm": [
      "Radius must be between 0 and 100 nautical miles"
    ]
  },
  "timestamp": "2026-01-15T18:56:00Z",
  "traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "path": "/api/v1/notams/radius"
}

Handling Errors

Because gateway and backend errors have different shapes, your error handling should check the response format. Here's how to handle both:

async function fetchMetar(icaoCode: string): Promise<Metar | null> {
  const response = await fetch(
    `https://api.preflightapi.com/api/v1/metars/${icaoCode}`,
    {
      headers: {
        'Ocp-Apim-Subscription-Key': process.env.PREFLIGHT_API_KEY!,
      },
    },
  )

  if (!response.ok) {
    const body = await response.json()

    // Gateway errors have { statusCode, message }
    // Tier-gating errors have { error }
    // Backend errors have { code, message, traceId, ... }

    switch (response.status) {
      case 401:
        // Gateway: invalid key
        throw new Error(body.message)

      case 403:
        // Could be tier-gating ({ error }) or quota exceeded ({ statusCode, message })
        throw new Error(body.error || body.message)

      case 404:
        // Backend: resource not found
        if (body.code === 'METAR_NOT_FOUND') {
          return null // No METAR available for this station
        }
        throw new Error(body.message)

      case 429:
        // Gateway: rate limited — check Retry-After header
        throw new Error(body.message)

      case 503:
        // Backend: external service down — safe to retry
        throw new Error(body.message)

      default:
        // Backend error — log traceId for support
        console.error(`API error [${body.code}]: ${body.message}`)
        if (body.traceId) console.error(`Trace ID: ${body.traceId}`)
        throw new Error(body.message)
    }
  }

  return response.json() as Promise<Metar>
}

Retry Strategy

Not all errors are retryable. Here's a guide for which status codes to retry and which to handle immediately:

StatusSourceRetryable?Action
400BackendNoFix the request — check parameters and validationErrors
401APIMNoCheck your API key is correct and present
403 (tier)APIMNoUpgrade your plan to access this endpoint
403 (quota)APIMNoWait for monthly quota reset or upgrade plan
404BackendNoThe resource doesn't exist — check the identifier
409BackendNoResolve the conflict
429APIMYesWait for the Retry-After duration, then retry
500BackendMaybeRetry once — if it persists, contact support with the traceId
503BackendYesExternal data source is down — retry with backoff

For a ready-to-use retry implementation, see the exponential backoff example on the rate limits page.