New rate limits now apply to all accounts.

Winds Aloft

Winds aloft forecasts describe expected wind direction, speed, and temperature at standard altitude levels for US flight planning. FB Winds (Winds and Temperatures Aloft) are issued on a fixed schedule and cover stations across the continental US; this endpoint returns every station whose coordinates fall inside your bounding box. Use it alongside METAR and TAF when you need cruise-altitude wind components for fuel and time planning — not surface conditions at a single airport. Coverage is US only: queries over non-US areas return a 404 with no stations.

Request

Requirements

Auth
x-api-key on every request (direct subscription). Learn more →

Send a GET with a geographic bounding box. Optional query flags select the forecast horizon and altitude band.

GEThttps://data.skylinkapi.com/v3/weather/winds-aloft
ParameterTypeRequiredDefaultDescription
bboxstringYes-Bounding box as lat1,lon1,lat2,lon2 — southwest corner first, northeast second. Example: 40,-80,42,-73 (NYC area)
forecastintegerNo12Forecast period in hours — 6, 12, or 24
levelstringNolowAltitude band: low (3,000–39,000 ft MSL) or high (6,000–45,000 ft MSL)

Bounding box tips. Keep the box tight around your route — smaller areas return fewer stations and faster responses. Coordinates are decimal degrees (WGS-84). Longitude west of the prime meridian is negative (e.g. -73.78 for JFK).

Response

{
  "bbox": "40.0,-80.0,42.0,-73.0",
  "forecast_hour": 12,
  "level": "low",
  "valid_time": "131800Z",
  "stations": [
    {
      "station": "JFK",
      "latitude": 40.6413,
      "longitude": -73.7781,
      "winds": [
        {
          "altitude_ft": 3000,
          "wind_direction": 310,
          "wind_speed_kt": 7,
          "temperature_c": null,
          "light_and_variable": false,
          "raw": "3107"
        },
        {
          "altitude_ft": 18000,
          "wind_direction": 270,
          "wind_speed_kt": 35,
          "temperature_c": -9,
          "light_and_variable": false,
          "raw": "2735-09"
        }
      ],
      "raw_text": "JFK 3107 3215+13 2926+08 2827+06 2735-09 2645-23 255637 256344 255852"
    }
  ],
  "total": 1
}
FieldTypeRequiredDescription
bboxstringYesEcho of the queried bounding box
forecast_hourintegerYesForecast period applied (6, 12, or 24)
levelstringYesAltitude band applied (low or high)
valid_timestring | nullYes (key always present)Forecast valid time (UTC), e.g. 131800Z. null when not provided
stationsarrayYesFB winds stations inside the bbox. [] when none match — usually paired with 404
totalintegerYesCount of stations in stations

Altitude bands

levelTypical altitudes returned (ft MSL)
low3,000 / 6,000 / 9,000 / 12,000 / 18,000 / 24,000 / 30,000 / 34,000 / 39,000
high6,000 / 9,000 / 12,000 / 18,000 / 24,000 / 30,000 / 34,000 / 39,000 / 45,000

Exact levels per station depend on the FB product — not every station returns every altitude.

Object schemas and nullability

Every key in a station object is always present. Every key in a wind level object is always present once the object exists. Use null, 0, or [] per the patterns below — never check for key absence.

SituationWhat you getNot this
Low-altitude level with no temptemperature_c: null0, key missing
Wind group not decodedwind_direction: null, wind_speed_kt: nullkey missing or 0
Light-and-variable windslight_and_variable: true, wind_direction may be nullnull for light_and_variable
Station with no decoded levelswinds: []null, key missing
No raw FB line for stationraw_text: nullkey missing
Upstream omits valid timevalid_time: nullkey missing
No stations inside bboxstations: [] + HTTP 404null
Station and wind level schemasExpand section

Station

JSON path: stations[]. Each element represents one FB winds station inside the bbox.

FieldTypeRequiredNull?Description
stationstringYesNever3-letter FB winds station identifier (often matches a nearby airport, e.g. JFK)
latitudenumberYesNeverStation latitude (decimal degrees)
longitudenumberYesNeverStation longitude (decimal degrees)
windsarrayYesNeverWind/temperature levels for this station. [] when no levels decoded
raw_textstring | nullYes (key always present)YesFull raw FB winds line for this station. null when not provided

Wind level

JSON path: stations[].winds[]. Each element represents one altitude level for the parent station.

FieldTypeRequiredNull?Description
altitude_ftintegerYesNeverAltitude in feet MSL for this level
wind_directioninteger | nullYes (key always present)YesDegrees true (0–360). null when not decoded
wind_speed_ktinteger | nullYes (key always present)YesSpeed in knots. null when not decoded
temperature_cinteger | nullYes (key always present)YesTemperature in °C. Often null at the lowest levels (3,000–6,000 ft)
light_and_variablebooleanYesNevertrue when winds are light and variable (< 5 kt). Default false
rawstringYesNeverEncoded wind token for this level, e.g. 2735-09 (270°/35 kt, −9 °C)

Light and variable. When light_and_variable is true, winds are below 5 knots with no specific heading — wind_direction and wind_speed_kt may be null or zero.

Client types

"""Winds aloft response types - matches winds-aloft field tables."""from dataclasses import dataclassfrom typing import Literal@dataclassclass WindLevel:    altitude_ft: int    wind_direction: int | None    wind_speed_kt: int | None    temperature_c: int | None    light_and_variable: bool    raw: str@dataclassclass WindsAloftStation:    station: str    latitude: float    longitude: float    winds: list[WindLevel]    raw_text: str | None@dataclassclass WindsAloftResponse:    bbox: str    forecast_hour: int    level: str    valid_time: str | None    stations: list[WindsAloftStation]    total: int@dataclassclass CruiseWindSummary:    station: str    altitude_ft: int    direction_deg: int | None    speed_kt: int | None    temperature_c: int | None

Integration

Fetch winds for a route bounding box and pick the level closest to your planned cruise altitude.

The example queries a box around the New York area, caches the response for one hour (FB products update on a fixed schedule), and returns the wind level nearest a target altitude. On a 404, the helper returns null and the caller shows an empty state instead of an error.

import osimport timefrom typing import TypedDictimport requests# CruiseWindSummary - helper return type. Full wire types are in the Client types section.class WindLevel(TypedDict):    altitude_ft: int    wind_direction: int | None    wind_speed_kt: int | None    temperature_c: int | None    light_and_variable: bool    raw: strclass Station(TypedDict):    station: str    latitude: float    longitude: float    winds: list[WindLevel]    raw_text: str | Noneclass WindsAloftResponse(TypedDict):    bbox: str    forecast_hour: int    level: str    valid_time: str | None    stations: list[Station]    total: intclass CruiseWindSummary(TypedDict):    station: str    altitude_ft: int    direction_deg: int | None    speed_kt: int | None    temperature_c: int | NoneHEADERS = {    "x-api-key": os.getenv("SKYLINK_API_KEY", "YOUR_API_KEY")}BASE = "https://data.skylinkapi.com/v3"CACHE: dict[str, tuple[WindsAloftResponse, float]] = {}WINDS_TTL = 3600  # 1 h - FB products update on a fixed scheduledef fetch_winds_aloft(bbox: str, forecast: int = 12, level: str = "low") -> WindsAloftResponse | None:    cache_key = f"winds:{bbox}:{forecast}:{level}"    cached = CACHE.get(cache_key)    if cached and time.time() < cached[1]:        return cached[0]    try:        r = requests.get(            f"{BASE}/weather/winds-aloft",            headers=HEADERS,            params={"bbox": bbox, "forecast": forecast, "level": level},            timeout=(10, 20),        )    except requests.Timeout as exc:        raise RuntimeError(f"SkyLink timeout fetching winds aloft for {bbox}") from exc    if r.status_code == 404:        return None    r.raise_for_status()    data: WindsAloftResponse = r.json()    CACHE[cache_key] = (data, time.time() + WINDS_TTL)    return datadef nearest_cruise_wind(    bbox: str,    target_altitude_ft: int = 18_000,    forecast: int = 12,) -> CruiseWindSummary | None:    """Pick the wind level closest to target altitude from the first station in the bbox."""    payload = fetch_winds_aloft(bbox, forecast=forecast, level="low")    if not payload or not payload["stations"]:        return None    station = payload["stations"][0]    levels = station["winds"]    if not levels:        return None    best = min(levels, key=lambda w: abs(w["altitude_ft"] - target_altitude_ft))    return {        "station": station["station"],        "altitude_ft": best["altitude_ft"],        "direction_deg": best["wind_direction"],        "speed_kt": best["wind_speed_kt"],        "temperature_c": best["temperature_c"],    }if __name__ == "__main__":    import json    from dataclasses import asdict    bbox = "-74.5,40.3,-73.5,41.0"    cruise = nearest_cruise_wind(bbox, target_altitude_ft=18_000)    print(json.dumps(asdict(cruise), indent=2) if cruise else "No winds data")

Implementation notes

US coverage only. FB Winds are a US product. A bbox over Europe or ocean with no US stations returns 404 — not an auth or quota error. Build your bbox from US route waypoints.

Empty vs 404. When no station centroids fall inside the bbox, the API returns 404 with a detail message naming the forecast and level. Widen the bbox slightly or shift it along the route.

Choosing level. Use low for typical airline cruise below FL390. Use high when you need FL390–FL450 data. If the service is temporarily unavailable for a level, you may receive 503 — retry with backoff.

Temperature nulls at low altitudes. The 3,000 ft and sometimes 6,000 ft levels commonly omit temperature (temperature_c: null). Use wind direction and speed for planning; treat missing temperature as "not reported at this level."

Error responses

Endpoint-specific failures only. For auth, validation, rate limits, and server errors shared by every endpoint, see Error Handling.

404 — no stations in bbox

{
  "detail": "No FB winds stations found in the given bounding box for forecast=12h, level=low"
}

Common causes: bbox outside US coverage, bbox too small between stations, or non-US coordinates. Widen the box or verify the route is over the US.

400 — invalid bbox format

{
  "detail": "Invalid bounding box format. Expected 'lat1,lon1,lat2,lon2'"
}

503 — upstream unavailable

{
  "detail": "Winds aloft service temporarily unavailable"
}

Retry after a short backoff. If persistent, try the other level or a different forecast hour.

GET
/weather/winds-aloft
x-api-key<token>

Your SkyLink licence key, for keys bought direct from skylinkapi.com.

In: header

Query Parameters

bbox*string

Bounding box as 'lat1,lon1,lat2,lon2' (SW corner → NE corner)

forecast?integer

Forecast period in hours (6, 12, or 24)

Default12
Range6 <= value <= 24
level?string

Altitude range: 'low' (3K-39K ft) or 'high' (6K-45K ft)

Default"low"
Match^(low|high)$

Response Body

application/json

application/json

curl -X GET "https://data.skylinkapi.com/v3/weather/winds-aloft?bbox=string"
{
  "bbox": "40,-80,42,-73",
  "forecast_hour": 12,
  "level": "low",
  "valid_time": "130000Z",
  "stations": [
    {
      "station": "JFK",
      "latitude": 40.6413,
      "longitude": -73.7781,
      "winds": [
        {
          "altitude_ft": 3000,
          "wind_direction": 270,
          "wind_speed_kt": 9,
          "light_and_variable": false,
          "raw": "2709"
        }
      ],
      "raw_text": "JFK      2709 3012+08 ..."
    }
  ],
  "total": 1
}
Empty
Empty
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string",
      "input": null,
      "ctx": {}
    }
  ]
}

Related: Weather hub · METAR · TAF · Cruise wind use case