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
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.
https://data.skylinkapi.com/v3/weather/winds-aloft| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
bbox | string | Yes | - | Bounding box as lat1,lon1,lat2,lon2 — southwest corner first, northeast second. Example: 40,-80,42,-73 (NYC area) |
forecast | integer | No | 12 | Forecast period in hours — 6, 12, or 24 |
level | string | No | low | Altitude 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
}| Field | Type | Required | Description |
|---|---|---|---|
bbox | string | Yes | Echo of the queried bounding box |
forecast_hour | integer | Yes | Forecast period applied (6, 12, or 24) |
level | string | Yes | Altitude band applied (low or high) |
valid_time | string | null | Yes (key always present) | Forecast valid time (UTC), e.g. 131800Z. null when not provided |
stations | array | Yes | FB winds stations inside the bbox. [] when none match — usually paired with 404 |
total | integer | Yes | Count of stations in stations |
Altitude bands
level | Typical altitudes returned (ft MSL) |
|---|---|
low | 3,000 / 6,000 / 9,000 / 12,000 / 18,000 / 24,000 / 30,000 / 34,000 / 39,000 |
high | 6,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.
| Situation | What you get | Not this |
|---|---|---|
| Low-altitude level with no temp | temperature_c: null | 0, key missing |
| Wind group not decoded | wind_direction: null, wind_speed_kt: null | key missing or 0 |
| Light-and-variable winds | light_and_variable: true, wind_direction may be null | null for light_and_variable |
| Station with no decoded levels | winds: [] | null, key missing |
| No raw FB line for station | raw_text: null | key missing |
| Upstream omits valid time | valid_time: null | key missing |
| No stations inside bbox | stations: [] + HTTP 404 | null |
Station and wind level schemasExpand section
Station
JSON path: stations[]. Each element represents one FB winds station inside the bbox.
| Field | Type | Required | Null? | Description |
|---|---|---|---|---|
station | string | Yes | Never | 3-letter FB winds station identifier (often matches a nearby airport, e.g. JFK) |
latitude | number | Yes | Never | Station latitude (decimal degrees) |
longitude | number | Yes | Never | Station longitude (decimal degrees) |
winds | array | Yes | Never | Wind/temperature levels for this station. [] when no levels decoded |
raw_text | string | null | Yes (key always present) | Yes | Full 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.
| Field | Type | Required | Null? | Description |
|---|---|---|---|---|
altitude_ft | integer | Yes | Never | Altitude in feet MSL for this level |
wind_direction | integer | null | Yes (key always present) | Yes | Degrees true (0–360). null when not decoded |
wind_speed_kt | integer | null | Yes (key always present) | Yes | Speed in knots. null when not decoded |
temperature_c | integer | null | Yes (key always present) | Yes | Temperature in °C. Often null at the lowest levels (3,000–6,000 ft) |
light_and_variable | boolean | Yes | Never | true when winds are light and variable (< 5 kt). Default false |
raw | string | Yes | Never | Encoded 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 | NoneIntegration
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.
Your SkyLink licence key, for keys bought direct from skylinkapi.com.
In: header
Query Parameters
Bounding box as 'lat1,lon1,lat2,lon2' (SW corner → NE corner)
Forecast period in hours (6, 12, or 24)
126 <= value <= 24Altitude range: 'low' (3K-39K ft) or 'high' (6K-45K ft)
"low"^(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
}{
"detail": [
{
"loc": [
"string"
],
"msg": "string",
"type": "string",
"input": null,
"ctx": {}
}
]
}Related: Weather hub · METAR · TAF · Cruise wind use case