Precipitation nowcast API

Minute-by-minute rain for the next three hours at any point in the radar coverage, in one GET request.

Quickstart

Get a free key (no card), then ask for a point. This asks for Amsterdam:

curl "https://nimboweather.com/v1/nowcast?lat=52.37&lon=4.90" \
  -H "X-API-Key: $NIMBO_KEY"
// Node 18+ or a server-side function: keep the key out of web pages
const res = await fetch("https://nimboweather.com/v1/nowcast?lat=52.37&lon=4.90", {
  headers: { "X-API-Key": process.env.NIMBO_KEY },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message}`);

console.log(data.summary?.text ?? "No radar coverage at this point");
for (const m of data.minutely.slice(0, 6)) console.log(m.time, m.precip_mm_h);
import os
import requests

r = requests.get(
    "https://nimboweather.com/v1/nowcast",
    params={"lat": 52.37, "lon": 4.90},
    headers={"X-API-Key": os.environ["NIMBO_KEY"]},
    timeout=10,
)
data = r.json()
if not r.ok:
    raise RuntimeError(f"{data['error']}: {data['message']}")

print(data["summary"]["text"] if data["summary"] else "No radar coverage at this point")
for m in data["minutely"][:6]:
    print(m["time"], m["precip_mm_h"])

The answer starts with a sentence you can show as it is, such as Light rain starting in 25 min, for about 40 min, then gives every step of the next hours.

Your key

Every call needs a key, except /v1/status (service health) and /v1/palettes (the colour tables of the tiles). Keys start with nimbo_. Send yours in one of three ways; they are equivalent:

  • the header X-API-Key: nimbo_... (recommended);
  • the header Authorization: Bearer nimbo_...;
  • the query parameter ?key=nimbo_..., which map tiles require.

Keep the key on your server when you can. A key used in a web page or an app can be copied; if that happens, regenerate it from your dashboard. The old key stops working at once and your plan and usage are kept.

Request

GET /v1/nowcast

ParameterRequiredMeaning
latyesLatitude in degrees, WGS 84.
lonyesLongitude in degrees, WGS 84.
horizonnoHow far ahead minutely goes, in minutes: 5 to 180. Default 180.
stepnoMinutes between entries of minutely: 1, 5, 10 or 15. Default 5. It changes only the list: summary is the same for every step (see steps).

Every call counts as one call against your monthly allowance, whatever the horizon and step.

Response

A JSON object. The fields you will use most:

{
  "issue_time": "2026-11-12T17:10:00Z",
  "data_age_s": 420,
  "stale": false,
  "coverage": "radar",
  "radar_source": "opera",
  "lat": 52.37, "lon": 4.9,
  "summary": {
    "text": "Light rain starting in 23 min, for about 40 min",
    "rain_now": false,
    "rain_starts_at": "2026-11-12T17:40:00Z",
    "rain_ends_at": "2026-11-12T18:20:00Z",
    "minutes_until_rain": 23,
    "max_intensity": "light",
    "now_source": "radar"
  },
  "observed_now": {"time": "2026-11-12T17:15:00Z", "precip_mm_h": 0.0},
  "minutely": [
    {"time": "2026-11-12T17:15:00Z", "precip_mm_h": 0.0, "precip_probability": 0.05, "precip_type": "rain"},
    ...
  ],
  "attribution": ["..."]
}

This answer was made at 17:17 UTC: the forecast starts from the radar of 17:10, so its first entry (17:15) is already 2 minutes old, and minutes_until_rain (23) counts from 17:17.

FieldMeaning
issue_timeWhen the forecast was made (UTC): the time of the last radar image it starts from.
data_age_sSeconds since issue_time. A new forecast is issued every 10 minutes.
staletrue when the radar data is late (see data age).
noticePresent during a radar outage: a sentence saying since when the radar feed is down.
coverageradar inside the radar coverage, no_radar outside it (then summary is null and rates are null).
radar_sourceWhich radar covers the point: opera almost everywhere, aemet where Spain’s national radars fill a gap.
summaryThe next three hours in a sentence and a few fields: see the summary.
observed_nowThe newest radar at the point (time, precip_mm_h), as the map shows it, when it is at most 15 minutes old; else null. The summary’s “now” is this observation when there is one.
minutely[].timeValid time of the entry (UTC): issue_time plus one step, two steps, ... The first entries can be in the past (an answer is typically 7 to 17 minutes younger than its issue time).
minutely[].precip_mm_hRain rate in mm/h, rounded to 2 decimals: the value the map draws at this point. null where it is unknown (outside the radar coverage).
minutely[].precip_probabilityChance of rain (0.2 mm/h or more), 0 to 1.
minutely[].precip_probability_1mm_h, minutely[].precip_probability_5mm_hChance of at least 1 mm/h and of at least 5 mm/h, 0 to 1. Never above the chance of a lower threshold.
minutely[].precip_typeAlways rain for now: snow and other types are not told apart yet, and the value will stay rain until they are.
horizon_skillHow well the forecast has scored at each lead time (hit score at 1 mm/h), so you can see where skill fades.
attributionThe credit line for the data, to show with it (see attribution).

The answer may carry more fields than these. Ignore the ones you do not use: new fields can be added at any time (see versions).

The summary

Rain means a rate of 0.2 mm/h or more, as published in minutely (rounded to 2 decimals, so an entry showing 0.2 is rain). The text speaks from the time of your request: “now” is the newest radar at the point when it is at most 15 minutes old (now_source: "radar", the same radar the map shows), else the forecast step nearest to your request (now_source: "forecast"). The reach it names (“in the next 3 hours”) is the length of the forecast, counted from its issue time: a request made a few minutes later has that many minutes less ahead of it, as the times in minutely show.

The text starts with the intensity at the start of the shower (the radar when it is raining now, else the shower’s first 30 minutes) and adds its peak when heavier (“Light rain (up to moderate)”), or “(lighter later)” when a shower of an hour or more ends lighter than it starts. max_intensity is the peak of the first shower: light (from 0.2 mm/h), moderate (from 2.5), heavy (from 10) or violent (from 50). Start times beyond an hour are rounded to 5 minutes (“in about 2 h 15 min”). The text is in English.

CaseWhat you get
Rain comingrain_now: false, start and end times, minutes_until_rain; text like “Light rain starting in 25 min, for about 40 min”.
Raining nowrain_now: true, minutes_until_rain: 0; text like “Light rain, stopping in about 30 min”.
Rain lasting past the horizonrain_ends_at: null; the text says “for at least the next 3 hours” (raining now) or “lasting at least 2 h 25 min” (rain coming).
Several showersThe first one is described; the text adds “more showers later”.
No rainStart and end null; text “No rain expected in the next 3 hours”. Where the map draws drizzle (0.1 to 0.2 mm/h) now or later, it adds “, a few drops possible”.
Outside the radar coveragesummary: null and coverage: "no_radar".
Radar data latestale: true, start and end null; text “Forecast delayed: radar data is N min old”.

Steps

step only resamples minutely; the summary always reads the forecast every 5 minutes. With step=5 (the default) each entry is the forecast at its time. With step=1 the entries are interpolated linearly between the 5-minute values (the first 5 minutes repeat the first one: the forecast starts at issue time + 5 min). With step=10 or 15 each entry carries the largest value (rate and probabilities) of the 5-minute steps it covers, the minutes after the previous entry up to its own time, so a 15-minute entry never hides a 5-minute shower the text speaks of.

Data age and outages

When new radar data stops arriving, there is nothing new to forecast from, and the API says so instead of guessing:

  • after 18 minutes without new radar data, stale becomes true;
  • after 30 minutes, answers carry a notice and the status page shows the outage;
  • after 2 hours, the API answers 503 with forecast_unavailable rather than serve an old forecast.

If rain matters to your users, show data_age_s or the notice.

Errors

Every error is JSON with a code you can test and a sentence you can log:

{"error": "out_of_coverage", "message": "This point is outside the forecast area."}

Some errors add fields, such as the quota on a 429. The ones to handle:

StatusCodeWhat to do
401api_key_required, invalid_api_keySend a valid key (see your key). Tiles also answer key_in_query_only to a key sent in a header.
404out_of_coverageThe point is outside the forecast area: do not retry.
422invalid_parameterA parameter is missing or out of range: fix the request.
429monthly_quota_exceededThe month’s allowance is used up: wait for the reset or upgrade.
429tile_rate_limitedToo many tiles in one second: wait Retry-After seconds. Not counted.
503forecast_unavailableNo forecast younger than 2 hours (a radar outage): retry in 10 minutes.

Limits and headers

Every answer to a call made with a key carries the state of your monthly allowance:

HeaderMeaning
X-RateLimit-LimitCalls included in your plan this month.
X-RateLimit-RemainingCalls left this month.
X-RateLimit-ResetWhen the allowance resets: the Unix time (seconds) at which the month ends, UTC.

GET /v1/keys/me answers your key’s plan, monthly allowance and calls used this month. It is not counted, and it still answers once your calls have stopped.

On the Free plan, calls stop at 20,000 with a 429 until the month ends: no surprise bills. Paid plans keep working 10 % past their allowance, then stop the same way. Map tiles also have a short burst limit; a 429 from it carries only Retry-After (see tiles).

Attribution

On the Free plan, show “Rain forecast: Nimbo Weather” with a link to this site wherever you show the data. On every plan, also show the attribution field of the answer as it comes: it credits the radar data, as the data’s licence requires.

Versions

v1 is stable. We may add fields, but we won’t rename or remove any without 6 months’ notice by email. Breaking changes go to /v2.

Questions or a field you need: [email protected].