API reference

Weather

Beta

The weather archive: METAR, TAF, SIGMETs (international and US), G-AIRMETs and CWAs from aviationweather.gov, collected since 2026-09-24, METARs polled every minute and the rest every few minutes, kept as received and never deleted, plus aircraft reports (PIREPs and AIREPs) for the route check. Decoded fields are the source's, next to the raw text; ours are the ceiling, a TAF group's flight category (FAA thresholds), the altitude band a hazard is matched against, and plain titles. All times RFC 3339 UTC.

Every answer carries meta, which says what the answer describes and how far to trust it:

  • time: the time the answer describes: semantic (latest, latest_at, history, valid_at, valid_during, flight) with at, or from and to.
  • as_of: what was known: time (default now), archive_start, before_archive. Nothing first seen after as_of.time is used, so a past briefing replays as it was. as_of in the future is 400.
  • collection: per product used, last_successful_poll, age_s, fresh.
  • gaps: holes in collection overlapping the period (checked, total, recent[] with product, from, to, status, notes). Read them before trusting an answer about the past.
  • missing (ids with nothing found), resolved (ids read as another station), rules (how hazards were matched), notes.
"meta": { "time": { "semantic": "latest", "at": "2026-09-24T06:59:33.941179938Z" },
  "as_of": { "time": "2026-09-24T06:59:33.941179938Z", "archive_start": "2026-09-24T00:04:15.773259Z", "before_archive": false },
  "collection": { "metar": { "last_successful_poll": "2026-09-24T06:59:16.632769Z", "age_s": 17, "fresh": true } },
  "gaps": { "checked": { "from": "2026-09-24T04:59:33.941179938Z", "to": "2026-09-24T06:59:33.941179938Z" }, "total": 0, "recent": [] },
  "resolved": { "JFK": "KJFK" } }

The service's own refusals come back as 400 bad_request with its message, for example {"error": "bad_request", "message": "at most 50 ids"}.

GET /v1/weather/metar

  • ids (required, up to 50, comma separated): ICAO identifiers; IATA and FAA codes are read through the station list (JFK as KJFK, said in meta.resolved).
  • Then one of: nothing (the latest report now), at (the latest observed at or before it), or from and to (every report in the window, up to 31 days). as_of: only what was known then.

Each report: station, name, lat, lon, elevation_m, observed_at, report_type (METAR, SPECI), raw, flight_category (the source's), wind (direction_deg, variable, speed_kt, gust_kt), visibility (statute_mi, or_more), ceiling_ft_agl (ours: the lowest broken or overcast layer, or the vertical visibility), vertical_visibility_ft, sky[] (cover, base_ft_agl, cloud_type), temperature_c, dewpoint_c, altimeter_in_hg, altimeter_hpa, weather, corrected, auto, decoded (the rest of the source's fields), versions (corrections of the same report), first_seen_at, and without from/to age_minutes.

{ "station": "EGLL", "name": "London/Heathrow Intl", "lat": 51.477, "lon": -0.461, "elevation_m": 26,
  "observed_at": "2026-09-24T06:20:00Z", "report_type": "METAR", "raw": "METAR EGLL 240620Z AUTO 05001KT 9999 NCD 11/09 Q1026",
  "flight_category": "VFR", "wind": { "direction_deg": 50, "variable": false, "speed_kt": 1, "gust_kt": null },
  "visibility": { "statute_mi": 6, "or_more": true }, "ceiling_ft_agl": null, "vertical_visibility_ft": null, "sky": [ { "cover": "CLR" } ],
  "temperature_c": 11, "dewpoint_c": 9, "altimeter_in_hg": 30.3, "altimeter_hpa": 1026.1, "weather": null,
  "corrected": false, "auto": true, "versions": 1, "first_seen_at": "2026-09-24T06:26:03.718225Z", "age_minutes": 22.2 }

GET /v1/weather/taf

ids as for METARs, optional at and as_of. Without at: per station the newest TAF issued, even one valid only from a later hour; covers_time says whether it is in force now. With at: the TAF in force then, the newest issued by then whose validity covers it; for an at in the future, the TAF issued by now that covers it, and meta.notes says so. A station with none is in meta.missing.

Each TAF: station, name, position, issued_at, bulletin_at, valid_from, valid_to, covers_time, raw, amended, corrected, remarks, versions, first_seen_at, and periods[], the base forecast first, then the change groups by start: from, to, change (null for the base forecast, FM, BECMG, TEMPO, PROB), probability, becoming_at, wind, wind_shear, visibility, sky, vertical_visibility_ft, weather, turbulence, icing, temperatures, not_decoded, and ours: ceiling_ft_agl, flight_category, outside_validity (a group whose times are a typo at source).

// trimmed: three of the seven periods
{ "station": "EGLL", "issued_at": "2026-09-24T04:56:00Z", "valid_from": "2026-09-24T06:00:00Z", "valid_to": "2026-09-25T12:00:00Z", "covers_time": true,
  "raw": "TAF EGLL 240456Z 2406/2512 08005KT 9999 FEW030 PROB30 2406/2408 7000 BECMG 2411/2414 19010KT BECMG 2419/2422 VRB03KT TEMPO 2500/2506 7000 PROB30 TEMPO 2503/2508 3000 BR BKN004 TEMPO 2506/2510 7000 BKN006",
  "periods": [
    { "from": "2026-09-24T06:00:00Z", "to": "2026-09-24T11:00:00Z", "change": null, "wind": { "direction_deg": 80, "variable": false, "speed_kt": 5, "gust_kt": null },
      "visibility": { "statute_mi": 6, "or_more": true }, "sky": [ { "cover": "FEW", "base_ft_agl": 3000 } ], "ceiling_ft_agl": null, "flight_category": "VFR" },
    { "from": "2026-09-24T11:00:00Z", "to": "2026-09-24T19:00:00Z", "change": "BECMG", "becoming_at": "2026-09-24T14:00:00Z",
      "wind": { "direction_deg": 190, "variable": false, "speed_kt": 10, "gust_kt": null }, "visibility": { "statute_mi": 6, "or_more": true },
      "sky": [ { "cover": "FEW", "base_ft_agl": 3000 } ], "ceiling_ft_agl": null, "flight_category": "VFR" },
    { "from": "2026-09-25T03:00:00Z", "to": "2026-09-25T08:00:00Z", "change": "TEMPO", "probability": 30, "visibility": { "statute_mi": 1.86, "or_more": false },
      "sky": [ { "cover": "BKN", "base_ft_agl": 400 } ], "weather": "BR", "ceiling_ft_agl": 400, "flight_category": "LIFR" } ] }

GET /v1/weather/hazards

SIGMETs, G-AIRMETs and CWAs in force. Without an area the answer is worldwide.

  • bbox: west,south,east,north in degrees; west greater than east crosses the antimeridian.
  • lat, lon, radius_nm: within a radius, up to 2,000 NM.
  • at (default now): in force at an instant. from, to: in force at any point of a window, up to 72 hours. as_of: what was known then.
  • fl_from, fl_to: a flight level band; only a stated limit keeps a hazard out.
  • types: sigmet_intl, sigmet_us, gairmet, cwa, or sigmet for both SIGMET feeds.
  • hazard: source codes, comma separated (TS, TC, TURB-HI, ICE, IFR, LLWS, ...). FZLVL returns the G-AIRMET freezing-level contours, otherwise left out.
  • include: geometry (GeoJSON), raw (the bulletin).
  • limit: default 500; meta.truncated says when there were more.

Each hazard: id, product, ident (G-AIRMET TANGO 1W TURB-HI +3h, KZMA SIGMET FOXTROT 4, NFFF SIGMET 05), hazard, title, qualifier, severity, issuer, fir_id, fir_name, series, issued_at, valid_from, valid_to, snapshot_at and package_expires_at (G-AIRMET), status, band (base_ft, base, top_ft, top, text, and matched_from_ft, matched_to_ft: the band a level is compared with), movement, change, due_to, geometry_kind, geometry_note (a repair of a broken shape at source), listed[] (when the feed carried it: from, last_seen, until), first_seen_at.

// trimmed: listed[] left out
{ "id": 29, "product": "gairmet", "ident": "G-AIRMET TANGO 1W TURB-HI +3h", "hazard": "TURB-HI", "title": "Moderate turbulence (high level)",
  "severity": "MOD", "issuer": "KKCI", "series": "TANGO", "issued_at": "2026-09-24T02:45:00Z", "valid_from": "2026-09-24T04:30:00Z", "valid_to": "2026-09-24T07:30:00Z",
  "snapshot_at": "2026-09-24T06:00:00Z", "package_expires_at": "2026-09-24T09:00:00Z", "status": "NRML",
  "band": { "base_ft": 17000, "base": "given", "top_ft": 33000, "top": "given", "text": "17000 ft to FL330", "matched_from_ft": 17000, "matched_to_ft": 33000 },
  "geometry_kind": "area", "first_seen_at": "2026-09-24T06:06:29.198579Z" }

Matching rules, in meta.rules on every hazard and route answer:

  • in_force: valid at the time asked about and listed in the source's feed at that time, or at as_of if that is earlier; a hazard dropped from the feed before it expired (cancelled or superseded) stops counting from then.
  • vertical: only a stated limit keeps a hazard off a level: an unstated base is the surface, an unstated top is unbounded; heights are compared in feet without correcting flight levels for pressure.
  • gairmet_snapshot_window: a G-AIRMET snapshot is taken to apply 90 minutes either side of its valid time.
  • surface_based_gairmets_top: IFR, mountain obscuration, low-level wind shear and surface wind G-AIRMETs have no issued top; they are matched up to 15,000 ft (top: "assumed").
  • freezing_level_contours: stored, returned only with hazard=FZLVL.

POST /v1/weather/route

The weather along a flight in four dimensions: a hazard counts where the corridor meets it, while it is in force, and where the aircraft is within its band. The flight is the flight object (see Flight object). The service times it (each waypoint's time_source: given, departure, speed, interpolated) and levels it (level_source: given, interpolated by distance between waypoints that have a level, unknown outside them); an unknown level never keeps a hazard off. Give top of climb and top of descent as waypoints for a realistic profile.

Options beside the flight:

  • alternates (up to 10): METAR and TAF at the destination ETA.
  • half_width_nm (1 to 100, default 20: the FAA's distance to keep from a thunderstorm).
  • as_of: replay with only what was known then.
  • types, hazards: as types and hazard on GET /v1/weather/hazards.
  • pireps: significant (default), all (routine wind and temperature AIREPs too, otherwise counted in meta.notes) or none; pirep_radius_nm (1 to 200, default 50), pirep_lookback_minutes (1 to 720, default 90), pirep_level_band_ft (default 4,000: a report further from the aircraft's level there is left out).
  • aerodrome_window_minutes (0 to 360, default 60): TAF groups within this of the aircraft's time are marked in_window.
  • include_geometry (default false), include_raw (default true: the hazard bulletins).

Limits: 2 to 200 waypoints, each leg under 9,000 NM, the route at most 13,000 NM and 36 hours.

The answer:

  • summary: the answer in one paragraph. Show it to people as it is.
  • flight: length_nm, departure, arrival, duration_min, half_width_nm, sample_step_nm, waypoints[] (index, id, lat, lon, chainage_nm, time, time_source, alt_ft, level_source, codes).
  • hazards[]: in the corridor while in force and within the band, in order along the track. The hazard fields above, plus placement (crosses: the track enters the area; abeam: within the half width), affects, chainage_nm (NM from the first waypoint), offset_nm, track[] (where the aircraft is in it: from_nm, to_nm, from_time, to_time, level_from_ft, level_to_ft, min_offset_nm, track_inside) and spatial[] (where the corridor meets it at all).
  • hazards_clear[]: in the corridor, but not at the aircraft's time or level: clear_by (for example before_valid, after_expiry, above) and clear_reason.
  • unplaced[]: in force during the flight, without usable geometry.
  • aerodromes[]: role (departure: the first waypoint's codes, or its id when the station list knows it; destination: the last waypoint's; alternate; enroute: codes on other waypoints), code, station, name, time (when the aircraft is there), reference (the instant the reports are chosen for: that time, or as_of, default now, when that is earlier), window, metar, taf (with covers_time, in_window on its groups, worst_category_in_window), missing.
  • pireps[]: observed_at, report_type, aircraft, raw, lat, lon, altitude_ft, chainage_nm, offset_nm, aircraft_passes_at, aircraft_level_ft, level_diff_ft, significant, and when reported turbulence, icing, weather, temperature_c.
  • meta (with rules and notes), and resolved_route for a route string.
curl -s https://api.bravoecho.ai/v1/weather/route \
  -H "Authorization: Bearer $BRAVOECHO_KEY" -H "Content-Type: application/json" \
  -d '{"route":"KMSP DWN6.ABR J158 RAP MBW MTU J148 OAL.MOD5 KSFO","departure":"2026-09-24T08:00:00Z","speed_kt":450,"cruise_fl":350,"alternates":["KOAK"]}'

Live (that call, run at 06:42Z): the aircraft climbs into a high-level turbulence G-AIRMET at 222 NM and stays in it to 953 NM; a surface-based IFR G-AIRMET it crosses in the climb is clear because the aircraft is above its assumed top by then.

{ "summary": "1436 NM, 24 08:00Z to 24 11:11Z. 2 hazard(s) affect the flight: Moderate turbulence (high level) (G-AIRMET TANGO 3W TURB-HI +6h) crosses from 222 NM, 08:29Z at FL350; IFR conditions (G-AIRMET SIERRA 2E IFR +9h) crosses from 1428 NM, 11:10Z at 15000 ft. 5 more meet the corridor but not the aircraft's time or level. departure KMSP: METAR VFR, TAF worst VFR around 08:00Z. destination KSFO: METAR VFR, TAF worst MVFR around 11:11Z. Collection has 1 gap(s) in the period: see meta.gaps.",
  "flight": { "length_nm": 1436.4, "departure": "2026-09-24T08:00:00Z", "arrival": "2026-09-24T11:11:31Z", "duration_min": 191.5, "half_width_nm": 20, "sample_step_nm": 1,
    "waypoints": [ { "index": 0, "id": "KMSP", "lat": 44.880081, "lon": -93.221741, "chainage_nm": 0, "time": "2026-09-24T08:00:00Z", "time_source": "departure", "alt_ft": 841, "level_source": "given", "codes": ["KMSP"] },
                   { "index": 1, "id": "ABR", "lat": 45.4174, "lon": -98.368698, "chainage_nm": 220.3, "time": "2026-09-24T08:29:22Z", "time_source": "speed", "alt_ft": 35000, "level_source": "given" }, ... ] },
  "hazards": [ { "ident": "G-AIRMET TANGO 3W TURB-HI +6h", "hazard": "TURB-HI", "title": "Moderate turbulence (high level)", "severity": "MOD",
                 "valid_from": "2026-09-24T07:30:00Z", "valid_to": "2026-09-24T10:30:00Z", "band": { "text": "FL250 to FL420", "matched_from_ft": 25000, "matched_to_ft": 42000, ... },
                 "placement": "crosses", "affects": true, "chainage_nm": 222.2, "offset_nm": 0,
                 "track": [ { "from_nm": 222.2, "to_nm": 952.9, "from_time": "2026-09-24T08:29:37Z", "to_time": "2026-09-24T10:07:02Z",
                              "level_from_ft": 35000, "level_to_ft": 35000, "min_offset_nm": 0, "track_inside": true } ], ... }, ... ],
  "hazards_clear": [ ..., { "ident": "G-AIRMET SIERRA 5E IFR +6h", "placement": "crosses", "valid_from": "2026-09-24T07:30:00Z", "valid_to": "2026-09-24T10:30:00Z",
                            "band": { "text": "SFC to 15000 ft (top assumed)", ... }, "chainage_nm": 162.9,
                            "spatial": [ { "from_nm": 162.9, "to_nm": 370.9, "from_time": "2026-09-24T08:21:43Z", "to_time": "2026-09-24T08:49:27Z", "level_from_ft": 26106, "level_to_ft": 35000, ... } ],
                            "clear_by": "above", "clear_reason": "the aircraft is above the hazard's top" }, ... ],
  "aerodromes": [ ..., { "role": "destination", "code": "KSFO", "station": "KSFO", "time": "2026-09-24T11:11:31Z", "window": { "from": "2026-09-24T10:11:31Z", "to": "2026-09-24T12:11:31Z" },
                         "metar": { "observed_at": "2026-09-24T05:56:00Z", "flight_category": "VFR", "raw": "METAR KSFO 240556Z 29008KT 10SM CLR 17/12 A2986 RMK AO2 SLP113 T01720122 10250 20172 51005", ... },
                         "taf": { "valid_from": "2026-09-24T06:00:00Z", "valid_to": "2026-09-25T12:00:00Z", "covers_time": true, "worst_category_in_window": "MVFR", ... } } ],
  "resolved_route": { "route": "KMSP DWN6.ABR J158 RAP MBW MTU J148 OAL.MOD5 KSFO",
                      "notes": [ "Procedure DWN6 is not in AIRAC 2609 (US procedures only): flown direct to its transition ABR.",
                                 "MTU: 2 places share this identifier; took Myton VORTAC (US), the one on the way.",
                                 "MOD5 is not published at KSFO in AIRAC 2609; flown as MOD9, its current revision." ], ... } }

For a flight ahead, meta.notes says so: "the flight is in the future: hazards are the forecasts listed now, aerodrome reports are the latest now and TAF groups are marked for the times of the flight".