Build a fast map like the explorer

The whole trick of the explorer fits in one file: ask /grid?format=bin for a step, decode the 72-byte header + float32 payload into a typed array, colourise it onto a canvas, and hand that canvas to deck.gl as one GPU texture. Scrubbing through forecast steps is just texture swaps — no tiles, no vector data, no build step.

Download the file and open it in any editor — set API_BASE (and your key).
Open it in a browser. It discovers the cycle + variables, renders step 0, and wires the slider.
Click anywhere: the same /forecast call your backend would make, shown inline.
The decode + texture core (excerpt)
// binary grid decode (matches /grid?format=bin)
function parseGrid(buf){
  const dv = new DataView(buf);
  const g = {
    nlat: dv.getUint32(8,true),  nlon: dv.getUint32(12,true),
    lat0: dv.getFloat64(24,true), dlat: dv.getFloat64(32,true),
    lon0: dv.getFloat64(40,true), dlon: dv.getFloat64(48,true),
    vmin: dv.getFloat32(56,true), vmax: dv.getFloat32(60,true),
  };
  g.values = new Float32Array(buf, 72, g.nlat * g.nlon); // zero-copy
  return g;
}

// …colourise into a canvas, then ONE BitmapLayer for the whole planet:
new deck.BitmapLayer({
  image: art.canvas, bounds: art.bounds, opacity: 0.65,
  // equirectangular source — deck reprojects per fragment
  _imageCoordinateSystem: deck.COORDINATE_SYSTEM.LNGLAT,
  textureParameters: { minFilter: 'linear', magFilter: 'linear' },
})

Quota: one native global frame ≈ 4 MB decoded; use max_cells or a region= crop while developing to stay light. Pin a cycle in the path for an immutable, endlessly cacheable demo.

Plot your sites in Power BI

A Power Query function that calls /forecast/daily once per row of a sites table and expands the result: one row per site per forecast day with min/max/mean in °C. Drop Lat/Lon on a map visual, Date on an axis, and you have a weather panel in your existing report.

Power BI Desktop → Get Data → Blank Query → Advanced Editor → paste the file.
Set ApiKey (make it a parameter for the service) and replace the inline Sites table with yours.
Close & Apply. Scheduled refresh works — the key travels in a header, the host is static.
The call that does the work (excerpt)
GetDaily = (lat as number, lon as number) as table =>
    let
        Resp = Json.Document(Web.Contents(ApiBase, [
            RelativePath = "v1/gfs/latest/forecast/daily",
            Query = [ lat = Number.ToText(lat), lon = Number.ToText(lon), #"var" = "t2m" ],
            Headers = [#"Authorization" = "Bearer " & ApiKey]
        ])),
        Days = Table.FromRecords(Resp[daily]),
        Celsius = Table.TransformColumns(Days, {
            {"min", each _ - 273.15, type number},
            {"max", each _ - 273.15, type number},
            {"mean", each _ - 273.15, type number}
        })
    in Celsius

Quota: one call per site per refresh (32 KiB each) — a 50-site report refreshed hourly uses ~37 MB/day, inside a Developer plan.

Every variable into Excel, one Power Query

Discovers the live variable list from /vars, fetches the full 209-step series for each surface variable at your coordinates, and joins them on valid_time — a wide, analysis-ready sheet: one row per forecast hour, one column per variable. Refresh = one click.

Excel → Data → Get Data → From Other Sources → Blank Query → Advanced Editor → paste.
Set Lat/Lon and your ApiKey; Close & Load.
Add derived columns in Excel (=[t2m]-273.15 for °C) — units come from /vars.
Discover, fetch, join (excerpt)
SurfaceVars = List.Transform(
    List.Select(VarsResp, each [kind] = "surface"), each [name]),

GetSeries = (v as text) as table =>
    let Resp = Json.Document(Web.Contents(ApiBase, [
            RelativePath = "v1/gfs/latest/forecast",
            Query = [ lat = Number.ToText(Lat), lon = Number.ToText(Lon),
                      #"var" = v, interp = "bilinear" ],
            Headers = Auth ]))
    in Table.FromColumns({Resp[valid_times], Resp[values]}, {"valid_time", v}),

Joined = List.Accumulate(List.Skip(Tables, 1), Tables{0},
    (acc, t) => Table.Join(acc, "valid_time", t, "valid_time"))

Quota: ~7 point calls per refresh — about 230 KiB. You could refresh this sheet 40 times a day on the free tier.

A Python dashboard: lines, wind, rain — and a map

One script, no project setup: the PEP 723 header lets uv run resolve requests, pandas and plotly on the fly. It pulls multi-variable series for your sites, derives wind speed from u/v, converts units, fetches a coarsened global temperature field, and writes a dark-mode HTML dashboard.

Download, then: WEATHERLABS_API_KEY=wl_live_… uv run weatherlabs_dashboard.py
Edit the SITES list — everything else adapts (titles, legends, map pins).
Open weatherlabs_dashboard.html — or schedule the script and publish the output.
The data pull (excerpt)
def point_df(lat, lon, var):
    fc = get('/forecast', lat=lat, lon=lon, var=var, interp='bilinear')
    return pd.DataFrame({'time': pd.to_datetime(fc['valid_times']), var: fc['values']})

for name, lat, lon in SITES:
    df = (point_df(lat, lon, 't2m')
          .merge(point_df(lat, lon, 'u10'), on='time')
          .merge(point_df(lat, lon, 'v10'), on='time')
          .merge(point_df(lat, lon, 'prate'), on='time'))
    df['t2m_c'] = df['t2m'] - 273.15
    df['wind'] = (df['u10']**2 + df['v10']**2) ** 0.5
    df['precip_mmhr'] = df['prate'] * 3600

Quota: 4 calls per site + one coarsened grid (~120 KiB) — three sites ≈ 0.5 MB per run.

Building something else? The API reference covers every endpoint and parameter, the playground writes curl/Python/JS for any query, and the explorer shows the exact call behind anything you click. Tell us what you're integrating — we'll help.