Built for trading desks and anyone who needs programmatic weather — the Benchmark Labs forecast plus 29 global and regional models (ECMWF, GFS, ICON, GEM, UKMO, JMA, HRRR and more), every model in one hourly schema, any location, up to 16 days out. Sign in to see the docs and your API key.
| 30 forecast models | Benchmark Labs (default), ECMWF IFS + AIFS, NOAA GFS/HRRR/NBM, ICON, GEM/HRDPS, Météo-France, UKMO, JMA, Nordic blends, MeteoSwiss, NWS and more |
| Hourly resolution | Temperature, wind, humidity, precipitation, snow, precip probability |
| Any location | Query by latitude/longitude, worldwide |
| Actuals | Measured conditions from the nearest weather station — verify which model is actually right |
| Simple auth | Personal API key — works from Python, Excel, cron, anything that can make an HTTP request |
| Free trial | 14 days · up to 10 locations · unlimited pulls on those locations |
Access is enabled per company. If your team isn't set up yet, contact info@benchmarklabs.com.
isn't part of an organization with access to the Forecast API. Ask us to enable your company's email domain — every colleague on it gets access at once.
Contact info@benchmarklabs.com to activate your team.
The first 10 unique coordinates you pull are locked to your account automatically (rounded to 2 decimal places ≈ 1 km — nearby jitter reuses the same slot). Pull these as often as you like. Locations are shared account-wide when access is enabled for your whole domain.
One endpoint. Pass a latitude, a longitude, a model, and your key:
Your key can go in the X-API-Key header, an Authorization: Bearer header, or the ?key= query parameter — whichever is easiest in your tooling.
GET /v1/forecast
| Param | Required | Description |
|---|---|---|
lat | yes | Latitude, −90 … 90 |
lon | yes | Longitude, −180 … 180 |
model | no | One of the model ids below. Default benchmark. |
units | no | metric (default) or imperial |
key | no | Your API key, if not sent as a header |
| id | Model | Time scale | Coverage |
|---|
Every model returns one value per hour, in UTC, for its full horizon (a model's native step can be coarser at long lead times — values are served as a uniform hourly series). GET /v1/models returns this list as JSON (no auth needed).
{
"location": { "lat": 39.74, "lon": -104.99 },
"model": "benchmark",
"units": "metric",
"generated_at": "2026-08-04T22:00:00Z",
"offsetMinutes": -360, // location's UTC offset
"hourly": [
{
"time": "2026-08-04T23:00:00.000Z", // UTC
"tempC": 23.0, // °C (imperial: tempF, °F)
"rh": 38, // relative humidity %
"windMs": 3.1, // m/s (imperial: windMph)
"windDir": 210, // degrees, meteorological
"precipMm": 0, // mm/hour (imperial: precipIn)
"snowMm": 0, // mm/hour (imperial: snowIn)
"precipChance": 10, // %
"uvIndex": 6,
"feelsLikeC": 23.5 // (imperial: feelsLikeF)
}
]
}
Fields a model does not provide come back as null (e.g. NWS has no hourly precipitation amounts or UV).
Every time value is UTC (ISO-8601, Z suffix), top-of-hour, for every model and observation source — join series from different models directly on time.
Forecast series start at the current hour: no past values, ever.
offsetMinutes is the location's current UTC offset — local time = time + offsetMinutes minutes. Station observation responses also include the IANA timeZone name (e.g. America/Los_Angeles).
One caveat: offsetMinutes is the offset right now — if a daylight-saving change falls inside a 15-day horizon, convert with a proper tz library (Python zoneinfo) using the timeZone name instead of adding a fixed offset.
| Status | error | Meaning |
|---|---|---|
| 401 | missing_key / invalid_key | No key, or the key is wrong or revoked |
| 403 | not_enabled | Account not activated |
| 403 | trial_expired | Trial ended — contact us to continue |
| 403 | location_limit | New location beyond your plan's cap; response lists your locked locations |
| 429 | rate_limited | Per-minute (60) or daily cap reached — check scope and Retry-After |
| 400 | bad_request / unknown_model / model_unavailable_for_location | Fix the request (NWS is US-only) |
| 502 | upstream_unavailable | The model's feed hiccuped — retry shortly |
Trial default: 10 locations, unlimited pulls on them up to 10,000 calls/day, soft limit 60 calls/minute. Forecasts update continuously; pulling more than once per 10 minutes per location returns the same data. Need more locations or volume? Talk to us.
Forecasts across models tell you the spread (how much the models disagree — a direct uncertainty signal). Observations tell you the truth: what actually happened, so you can score which model has been most accurate at your locations lately.
GET /v1/observations
| Param | Required | Description |
|---|---|---|
lat, lon | yes | Same as the forecast endpoint — counts toward the same location pool |
source | no | station (default): recent hourly measurements from the nearest official weather station, with station id, name and distance. US only. current: single current-conditions snapshot at the point, worldwide. |
hours | no | How far back (station source only), 1–168. Default 48. |
units | no | metric (default) or imperial |
{
"location": { "lat": 32.72, "lon": -117.16 },
"source": "station",
"station": {
"id": "KSAN", "name": "San Diego International Airport",
"lat": 32.73, "lon": -117.18, "distance_km": 2.1
},
"timeZone": "America/Los_Angeles",
"offsetMinutes": -420,
"hours": 48,
"hourly": [
{ "time": "...", "tempC": 22.8, "rh": 64, "windMs": 4.1, "windDir": 290,
"precipMm": 0, "dewPointC": 15.6, "pressureHpa": 1013.2 }
]
}
Pull yesterday's forecasts each day, store them, then compare against station actuals — after a couple of weeks you know exactly which model to trust at each of your locations:
Data → Get Data → From Other Sources → Blank Query, then: