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

Auth
x-api-key on every request (direct subscription). Learn more →
ICAO
4-letter airport code (e.g. KJFK). US fields often use a leading K. Learn more →

Send a GET with the airport ICAO in the path. Optional query flags control decoding.

GEThttps://data.skylinkapi.com/v3.1/weather/taf/{icao}
ParameterTypeRequiredDefaultDescription
icaostringYes-4-letter ICAO code — lookup, e.g. KJFK
parsedbooleanNofalseAdd 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"
}
FieldTypeRequiredDescription
rawstringYesFull TAF string as issued
icaostringYesICAO code
airport_namestringYesAirport name
timestampstring (ISO 8601)YesWhen 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": []
      }
    ]
  }
}
FieldTypeRequiredDescription
parsedobjectYes (with parsed=true)Absent when parsed=false — not null
parsed.start_timestring (ISO 8601)YesStart of the TAF valid period (DDHH/DDHH in raw)
parsed.end_timestring (ISO 8601)YesEnd of the TAF valid period
parsed.forecastarrayYesForecast periods in chronological order — see Forecast period types
parsed.forecast[].typestringYes (per item)FROM, TEMPO, or BECMG
parsed.forecast[].start_timestring (ISO 8601)Yes (per item)When this group becomes active
parsed.forecast[].end_timestring (ISO 8601)Yes (per item)When this group ends
parsed.forecast[].windobjectYes (per item)Same structure as METAR parsed.wind
parsed.forecast[].visibilityobjectYes (per item)Same structure as METAR parsed.visibility
parsed.forecast[].cloudsarrayYes (per item)Same structure as METAR parsed.clouds — cloud cover codes
parsed.forecast[].wx_codesarrayYes (per item)Expected weather phenomena — { repr, value }[]
parsed.forecast[].flight_rulesstringYes (per item)Expected flight category for this group
parsed.forecast[].probabilityobject | nullYes (key always present)Populated on PROB30/PROB40 groups — see below
parsed.forecast[].turbulencearrayYes (per item)Atmospheric turbulence intensity when issued — [] when none
parsed.forecast[].icingarrayYes (per item)Airframe 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.

FieldTypeRequiredNull / emptyNotes
parsed.forecast[].windobjectYesAlways presentSame shape as METAR parsed.wind
parsed.forecast[].visibilityobjectYesAlways presentvalue: null common for P6SM (repr: "P6")
parsed.forecast[].cloudsarrayYes[] possibleSame element shape as METAR
parsed.forecast[].wx_codesarrayYes[] when noneSame { repr, value } item shape as METAR
parsed.forecast[].probabilityobject | nullYesnull on most groupsObject on PROB30/PROB40 — see below
parsed.forecast[].turbulencearrayYes[] when not issuedNever null
parsed.forecast[].icingarrayYes[] when not issuedNever null

When parsed=false, the parsed key is absent entirely.

Probability on PROB groups

In the raw TAF, PROB30 and PROB40 mark probabilistic sub-periods (30 % or 40 % probability of occurrence). In production JSON they appear as a FROM or TEMPO entry with a populated probability object — there is no separate type: "PROB" (verified on KORD, EGLL, EDDF).

"probability": {
  "repr": "30",
  "value": 30,
  "spoken": "three zero"
}
FieldTypeRequiredDescription
reprstringYesRaw token: "30" or "40"
valueintegerYes30 or 40
spokenstringYesSpoken form from the bulletin

When the group is not probabilistic, probability is null.

Turbulence, icing, and forecast period typesExpand section

Turbulence and icing groups

Most forecast periods return "turbulence": [] and "icing": []. When the source TAF includes TURB or ICE groups, each array contains one or more objects with at least repr (raw token) and value (human-readable intensity) — same { repr, value } pattern as wx_codes. Additional altitude keys may appear when the bulletin specifies a vertical layer; treat unknown keys as forward-compatible.

"turbulence": [{ "repr": "MOD", "value": "Moderate" }],
"icing": []

Forecast period types

Walk parsed.forecast[] chronologically: active FROM baseline, then overlapping modifiers.

TAF tokentype in JSONTime fieldsWhat it means
Initial / FMFROMstart_time → end_timeBaseline conditions. Each FM group replaces everything before it from start_time onward
TEMPOTEMPOstart_time → end_timeTemporary fluctuations within the baseline — show as a caveat, not a replacement
BECMGBECMGstart_time → end_timeGradual change toward values in this group by end_time
PROB30 / PROB40FROM or TEMPO + probabilitysame as parent typeProbabilistic sub-period — non-null probability on the row

Reading periods for a departure time

  1. Filter parsed.forecast[] where type == "FROM" and start_time <= departure <= end_time — baseline.
  2. Collect overlapping TEMPO, BECMG, and probabilistic rows — modifiers.
  3. Display baseline flight_rules prominently; list modifiers separately.
  4. Worst-case across the TAF: highest flight_rules rank among all FROM groups.

Example at KORD: baseline FROM may be VFR while overlapping TEMPO shows MVFR with -SHRA — show both.

Client types

Copy into your project. Nullability matches Object schemas and nullability and METAR nullability for shared nested objects.

"""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 | None

TafResponse / TafParsed / TafPeriod match API JSON. TafDepartureSummary is defined at the top of each tab in the code example.

Integration

Fetch, cache, and interpret forecast periods at a departure time.

The example caches each TAF for fifteen minutes (TAFs reissue four times daily), uses 10 s / 15 s connect/read timeouts, selects the active FROM group at a departure time, and lists overlapping TEMPO/BECMG modifiers.

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.1"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")

Period layering walkthrough: TAF period layering.

Implementation notes

Reading TEMPO and probabilistic groups. TEMPO layers on the active FROM baseline. PROB30/PROB40 appear as rows with non-null probability — check probability.value, not a separate type. See Forecast period types.

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.

Combining with METAR. Fetch both in parallel. Use METAR flight_rules for current conditions and the worst FROM flight_rules across the TAF as worst expected.

Error responses

Only TAF-specific failures belong here. Shared errors: Error Handling. METAR vs TAF 404 in production: When weather data is missing.

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."
}

Common case: the airport has a METAR but no TAF. Fetch METAR normally; skip TAF and label the UI Forecast not available.

GET
/weather/taf/{icao}
x-api-key<token>

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

In: header

Path Parameters

icao*Icao

4-letter ICAO airport code

Length4 <= length <= 4

Query Parameters

parsed?Parsed

Include parsed/decoded TAF fields alongside raw text

Defaultfalse

Response Body

application/json

application/json

curl -X GET "https://data.skylinkapi.com/v3.1/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