TAF
TAFs (Terminal Aerodrome Forecasts) are structured forecasts for a specific airport and its immediate vicinity, typically covering roughly five nautical miles around the aerodrome. Standard TAFs are issued four times daily at 00Z, 06Z, 12Z, and 18Z and remain valid for 24 or 30 hours depending on the station. Each TAF describes expected wind, visibility, cloud layers, weather phenomena, and flight categories across timed forecast groups such as FROM, TEMPO, and BECMG. Pilots, dispatchers, and flight planners use these forecasts to assess expected conditions at departure, arrival, and alternate airports when building operational plans. Many small airports publish METAR only — a TAF 404 is common and does not mean the airport is unknown.
Request
Requirements
x-api-key on every request (direct subscription). Learn more →Send a GET with the airport ICAO in the path. Optional query flags control decoding.
https://data.skylinkapi.com/v3/weather/taf/{icao}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
icao | string | Yes | - | 4-letter ICAO code — lookup, e.g. KJFK |
parsed | boolean | No | false | Add decoded forecast periods under a parsed key |
Response
Raw (default)
{
"raw": "TAF KJFK 072334Z 0800/0906 35014G20KT P6SM FEW050 SCT100 FM080600 03011KT P6SM SCT025 ...",
"icao": "KJFK",
"airport_name": "John F. Kennedy International Airport",
"timestamp": "2026-06-08T23:45:12Z"
}| Field | Type | Required | Description |
|---|---|---|---|
raw | string | Yes | Full TAF string as issued |
icao | string | Yes | ICAO code |
airport_name | string | Yes | Airport name |
timestamp | string (ISO 8601) | Yes | When the API fetched this data — not the forecast issue time |
Parsed (parsed=true)
{
"raw": "TAF KJFK 061933Z 0620/0724 ...",
"icao": "KJFK",
"airport_name": "John F. Kennedy International Airport",
"timestamp": "2026-06-06T23:09:48Z",
"parsed": {
"start_time": "2026-06-06T22:00:00+02:00",
"end_time": "2026-06-08T02:00:00+02:00",
"forecast": [
{
"type": "FROM",
"start_time": "2026-06-06T22:00:00+02:00",
"end_time": "2026-06-07T01:00:00+02:00",
"wind": { "direction": 200, "speed": 14, "gust": null, "variable": [] },
"visibility": { "value": null, "repr": "P6" },
"clouds": [
{ "type": "SCT", "base": 70, "repr": "SCT070" },
{ "type": "BKN", "base": 250, "repr": "BKN250" }
],
"wx_codes": [],
"flight_rules": "VFR",
"probability": null,
"turbulence": [],
"icing": []
},
{
"type": "TEMPO",
"start_time": "2026-06-07T01:00:00+02:00",
"end_time": "2026-06-07T05:00:00+02:00",
"wind": { "direction": 290, "speed": 25, "gust": 45, "variable": [] },
"visibility": { "value": 3, "repr": "3" },
"clouds": [
{ "type": "SCT", "base": 25, "repr": "SCT025" },
{ "type": "BKN", "base": 50, "repr": "BKN050CB" }
],
"wx_codes": [
{ "repr": "TSRA", "value": "Thunderstorm Rain" },
{ "repr": "BR", "value": "Mist" }
],
"flight_rules": "MVFR",
"probability": null,
"turbulence": [],
"icing": []
}
]
}
}| Field | Type | Required | Description |
|---|---|---|---|
parsed | object | Yes (with parsed=true) | Absent when parsed=false — not null |
parsed.start_time | string (ISO 8601) | Yes | Start of the TAF valid period |
parsed.end_time | string (ISO 8601) | Yes | End of the TAF valid period |
parsed.forecast | array | Yes | Forecast periods in chronological order |
parsed.forecast[].type | string | Yes (per item) | FROM, TEMPO, or BECMG |
parsed.forecast[].start_time | string (ISO 8601) | Yes (per item) | When this group becomes active |
parsed.forecast[].end_time | string (ISO 8601) | Yes (per item) | When this group ends |
parsed.forecast[].wind | object | Yes (per item) | Same structure as METAR parsed.wind |
parsed.forecast[].visibility | object | Yes (per item) | Same structure as METAR parsed.visibility |
parsed.forecast[].clouds | array | Yes (per item) | Same structure as METAR parsed.clouds — cloud cover codes |
parsed.forecast[].wx_codes | array | Yes (per item) | Expected weather phenomena — { repr, value }[] |
parsed.forecast[].flight_rules | string | Yes (per item) | Expected flight category for this group |
parsed.forecast[].probability | object | null | Yes (key always present) | Populated on PROB30/PROB40 groups |
parsed.forecast[].turbulence | array | Yes (per item) | Turbulence intensity when issued — [] when none |
parsed.forecast[].icing | array | Yes (per item) | Icing intensity when issued — [] when none |
Object schemas and nullability
Each object in parsed.forecast[] includes the same keys on every period — same rules as METAR nullability for wind, visibility, clouds, and wx_codes.
| Field | Type | Required | Null / empty | Notes |
|---|---|---|---|---|
parsed.forecast[].wind | object | Yes | Always present | Same shape as METAR parsed.wind |
parsed.forecast[].visibility | object | Yes | Always present | value: null common for P6SM |
parsed.forecast[].clouds | array | Yes | [] possible | Same element shape as METAR |
parsed.forecast[].wx_codes | array | Yes | [] when none | Same { repr, value } item shape |
parsed.forecast[].probability | object | null | Yes | null on most groups | Object on PROB30/PROB40 |
parsed.forecast[].turbulence | array | Yes | [] when not issued | Never null |
parsed.forecast[].icing | array | Yes | [] when not issued | Never null |
Probability on PROB groups
"probability": {
"repr": "30",
"value": 30,
"spoken": "three zero"
}Turbulence, icing, and forecast period typesExpand section
Forecast period types
| TAF token | type in JSON | What it means |
|---|---|---|
Initial / FM | FROM | Baseline conditions — each FM group replaces everything before it |
TEMPO | TEMPO | Temporary fluctuations within the baseline |
BECMG | BECMG | Gradual change toward values in this group by end_time |
PROB30 / PROB40 | FROM or TEMPO + probability | Probabilistic sub-period |
Client types
"""TAF response and departure-summary types - matches weather-taf field tables."""from __future__ import annotationsfrom dataclasses import dataclassfrom typing import LiteralFlightRules = Literal["VFR", "MVFR", "IFR", "LIFR"]TafPeriodType = Literal["FROM", "TEMPO", "BECMG"]@dataclassclass TafRequest: """Path/query input for GET /weather/taf/{icao}.""" icao: str parsed: bool = False@dataclassclass TafWind: direction: int speed: int gust: int | None variable: list[int]@dataclassclass TafVisibility: value: float | None repr: str@dataclassclass TafCloudLayer: type: str base: int repr: str@dataclassclass TafWxCode: repr: str value: str@dataclassclass TafProbability: repr: str value: Literal[30, 40] spoken: str@dataclassclass TafPeriod: type: TafPeriodType start_time: str end_time: str wind: TafWind visibility: TafVisibility clouds: list[TafCloudLayer] wx_codes: list[TafWxCode] flight_rules: FlightRules probability: TafProbability | None turbulence: list[object] icing: list[object]@dataclassclass TafParsed: start_time: str end_time: str forecast: list[TafPeriod]@dataclassclass TafResponse: raw: str icao: str airport_name: str timestamp: str parsed: TafParsed | None = None@dataclassclass TafModifierSummary: type: TafPeriodType category: FlightRules probability: int | None wx: list[str]@dataclassclass TafDepartureSummary: """Shape returned by taf_at_departure() in the fetch example.""" icao: str valid_to: str baseline_category: FlightRules | None modifiers: list[TafModifierSummary] worst_from_category: FlightRules | NoneIntegration
Fetch, cache, and interpret forecast periods at a departure time.
import osimport timefrom dataclasses import dataclassfrom datetime import datetime, timezonefrom typing import Literalimport requests# TafDepartureSummary - helper return type. Full wire types (TafResponse, TafPeriod, …)# are in the Client types section on the TAF docs page.FlightRules = Literal["VFR", "MVFR", "IFR", "LIFR"]TafPeriodType = Literal["FROM", "TEMPO", "BECMG"]@dataclassclass TafModifierSummary: type: TafPeriodType category: FlightRules probability: int | None wx: list[str]@dataclassclass TafDepartureSummary: icao: str valid_to: str baseline_category: FlightRules | None modifiers: list[TafModifierSummary] worst_from_category: FlightRules | NoneHEADERS = { "x-api-key": os.getenv("SKYLINK_API_KEY", "YOUR_API_KEY")}BASE = "https://data.skylinkapi.com/v3"CACHE: dict[str, tuple[dict, float]] = {}TAF_TTL = 900CATEGORY_RANK = {"VFR": 0, "MVFR": 1, "IFR": 2, "LIFR": 3}def parse_time(iso: str) -> datetime: return datetime.fromisoformat(iso.replace("Z", "+00:00"))def fetch_taf(icao: str) -> dict | None: cache_key = f"taf:{icao}:parsed" cached = CACHE.get(cache_key) if cached and time.time() < cached[1]: return cached[0] try: r = requests.get( f"{BASE}/weather/taf/{icao}", headers=HEADERS, params={"parsed": "true"}, timeout=(10, 15), ) except requests.Timeout as exc: raise RuntimeError(f"SkyLink timeout fetching TAF for {icao}") from exc if r.status_code == 404: return None r.raise_for_status() data = r.json() CACHE[cache_key] = (data, time.time() + TAF_TTL) return datadef taf_at_departure(icao: str, departure: datetime | None = None) -> TafDepartureSummary | None: taf = fetch_taf(icao) if not taf: return None at = departure or datetime.now(timezone.utc) forecast = taf["parsed"]["forecast"] baseline = next( ( p for p in forecast if p["type"] == "FROM" and parse_time(p["start_time"]) <= at <= parse_time(p["end_time"]) ), None, ) modifiers = [ TafModifierSummary( type=p["type"], category=p["flight_rules"], probability=(p["probability"] or {}).get("value"), wx=[w["repr"] for w in p["wx_codes"]], ) for p in forecast if p is not baseline and parse_time(p["start_time"]) <= at <= parse_time(p["end_time"]) ] from_groups = [p for p in forecast if p["type"] == "FROM"] worst = ( max(from_groups, key=lambda p: CATEGORY_RANK.get(p["flight_rules"], -1))["flight_rules"] if from_groups else None ) return TafDepartureSummary( icao=icao, valid_to=taf["parsed"]["end_time"], baseline_category=baseline["flight_rules"] if baseline else None, modifiers=modifiers, worst_from_category=worst, )if __name__ == "__main__": import json from dataclasses import asdict summary = taf_at_departure("KJFK") print(json.dumps(asdict(summary), indent=2) if summary else "TAF not available")Implementation notes
Reading TEMPO and probabilistic groups. TEMPO layers on the active FROM baseline. PROB30/PROB40 appear as rows with non-null probability.
TAFs update four times a day at 00Z, 06Z, 12Z, and 18Z. Caching for 15–30 minutes is appropriate.
404 without a TAF. Many GA fields publish METAR only. Treat TAF 404 as missing forecast data, not a failed request.
Error responses
404 — no TAF for this ICAO
{
"detail": "No current TAF forecast available for KOXB. Weather station may not issue TAF reports or may be temporarily offline."
}Your SkyLink licence key, for keys bought direct from skylinkapi.com.
In: header
Path Parameters
4-letter ICAO airport code
4 <= length <= 4Query Parameters
Include parsed/decoded TAF fields alongside raw text
falseResponse Body
application/json
application/json
curl -X GET "https://data.skylinkapi.com/v3/weather/taf/KJFK"{
"raw": "TAF KJFK 271720Z 2718/2824 16012KT P6SM FEW025 SCT250 FM272100 18010KT P6SM SCT025 BKN250",
"icao": "KJFK",
"airport_name": "John F Kennedy International Airport",
"timestamp": "2025-09-27T12:00:00Z"
}{
"detail": [
{
"loc": [
"string"
],
"msg": "string",
"type": "string",
"input": null,
"ctx": {}
}
]
}Related: METAR · Use cases · Errors · Production