Skip to main content

Developers · quickstart

Weather API: current conditions and forecast as JSON

How to call HELPERG’s public weather endpoints: current conditions and an hourly and daily forecast for any coordinate, as JSON, with no credential. Parameters, a recorded response, units, caching, errors, limits, and the MET Norway attribution every response carries.

Full API referenceOpenAPI 3.1 documentThe weather page these endpoints serve

Before the first request

Who may call it
Anyone, today, without a credential: these three routes are in the public namespace /api/public/v1. They are rate-limited per client and per route, HELPERG offers no service-level agreement for them, and additive fields may appear at any time; a breaking change would be a new version segment, not a change to these paths. There is no paid tier and nothing to sign up for — and therefore no promise about tomorrow beyond the versioning rule.
Whose data it is
MET Norway’s, from its Locationforecast service, under CC BY 4.0. Every response carries the attribution text the licence asks for, and meta.attributionRecords[].required is true: show it wherever you show the readings. The readings are model output for a point, published by the provider — not a station observation, not a measurement, and HELPERG does not alter the values.
Where the point goes
Coordinates are rounded to 2 decimal places (about 1.1 km) before the provider is asked, and the point in the body is the one served. That is the provider’s own terms for anonymous use, and it means two callers inside the same square share one cached answer.

The three endpoints

GET /api/public/v1/weather/current
Current conditions at a point: the first entry of the series the provider returned, with the hour it belongs to in observedAt. lat and lon are required decimal degrees; altitude is optional metres from −500 to 9000; units is metric (default) or imperial, and the measurement field names change with the unit system (temperatureC becomes temperatureF, windSpeedMps becomes windSpeedMph). Shared cache: 600 seconds.
GET /api/public/v1/weather/forecast
Hours and days for the same point, from the same service. hours defaults to 24 and is clamped to 48; days defaults to 7 and is clamped to 10 — clamped, not refused, and the provider decides the real horizon, so fewer days than you asked for is its answer, not an error. tz is the IANA zone the daily records are assembled in (default UTC): a “day” is a local calendar day in that zone, and the first and last days can be partial. Shared cache: 1800 seconds.
GET /api/public/v1/weather/places
Finds a place by name — q, at least two characters, and an optional limit. It is an optional capability: MET Norway publishes no gazetteer, so where this deployment has no place provider configured the route answers 200 with an empty list rather than an error. Resolve a name to coordinates once and cache the result on your side; do not call it per request.

Quickstart

The coordinate in these examples is central Prague. Replace it with your own point; the response shape is the same for any coordinate.

curl

curl
curl -sS "https://helperg.com/api/public/v1/weather/current?lat=50.0755&lon=14.4378"
curl -sS "https://helperg.com/api/public/v1/weather/forecast?lat=50.0755&lon=14.4378&hours=12&days=5&tz=Europe/Prague"

JavaScript

JavaScript (fetch)
// Node.js 18+ or any runtime with fetch. No key, no header beyond Accept.
const response = await fetch("https://helperg.com/api/public/v1/weather/forecast?lat=50.0755&lon=14.4378&days=3&tz=Europe/Prague", {
  headers: { Accept: "application/json" },
});
if (!response.ok) throw new Error(`HTTP ${response.status}`); // 400, 429 or 503: see Errors below
const { data, meta } = await response.json();
for (const day of data.forecast.days) {
  console.log(day.date, day.temperatureMinC, day.temperatureMaxC, day.condition.code);
}
console.log(meta.attributionRecords[0].text); // show this wherever the readings are shown

Python

Python (standard library)
import json, urllib.request

url = "https://helperg.com/api/public/v1/weather/current?lat=50.0755&lon=14.4378"
with urllib.request.urlopen(urllib.request.Request(url, headers={"Accept": "application/json"})) as r:
    body = json.load(r)
current = body["data"]["current"]
print(current["temperatureC"], current["condition"]["code"], current["providerModelUpdatedAt"])
print(body["meta"]["attributionRecords"][0]["text"])

A recorded response

Recorded from /api/public/v1/weather/current on 17 October 2026 at 15:02 UTC and trimmed to the fields this page discusses. The values are the provider’s for that model run; yours will differ.

Recorded response, trimmed
{
  "data": {
    "current": {
      "location": { "latitude": 50.08, "longitude": 14.44, "altitudeMetres": 267 },
      "observedAt": "2026-10-17T14:00:00.000Z",
      "units": "metric",
      "temperatureC": 15.3,
      "humidityPercent": 77.7,
      "pressureHpa": 1017.3,
      "windDirectionDegrees": 2.7,
      "cloudCoverPercent": 26.6,
      "uvIndex": 0.5,
      "condition": { "code": "FAIR", "thunder": false, "dayPart": "DAY" },
      "providerModelUpdatedAt": "2026-10-17T13:17:17.000Z",
      "source": { "id": "met-no", "name": "MET Norway Weather API", "url": "https://www.met.no/" },
      "attribution": { "text": "Weather data from MET Norway (Norwegian Meteorological Institute)…", "required": true,
                       "licenseName": "CC BY 4.0", "licenseUrl": "https://creativecommons.org/licenses/by/4.0/" }
    }
  },
  "meta": {
    "requestId": "835df78a-…", "generatedAt": "2026-10-17T15:02:31.535Z",
    "cached": false, "cacheAgeSeconds": 0, "stale": false, "degraded": false,
    "coordinatePrecision": "2 decimal places (about 1.1 km); the point in the body is the one served",
    "canonicalUnits": { "temperature": "celsius", "windSpeed": "m/s", "pressure": "hPa", "precipitation": "mm" },
    "attributionRecords": [ { "sourceId": "met-no", "required": true, "licenseName": "CC BY 4.0",
                              "termsUrl": "https://api.met.no/doc/TermsOfService", "modifiedByHelperg": true } ]
  }
}

Every public response has the same two halves: data, whose shape is specific to the route, and meta, which is the same everywhere — request id, generation time, whether the answer came from HELPERG’s cache and how old it is, whether it is stale, and the attribution records. The forecast puts hours[] and days[] under data.forecast; each hour carries startsAt in UTC and each day a date in the zone you asked for.

Units, freshness and what “current” means

Units
Metric by default: degrees Celsius, metres per second, hectopascals, millimetres; meta.canonicalUnits states them. With units=imperial the values are converted and the field names say so; the body’s units field names the system in use, so a reading is never shown in one unit and stored in another.
How fresh: three layers, no fixed lag
The HTTP response carries Cache-Control: public, max-age=600 for current conditions and max-age=1800 for the forecast: a shared cache, or your own, may hold a response that long on top of everything below. Underneath, HELPERG keeps the provider’s payload for as long as the provider’s own Expires header says, clamped to 5 minutes to 1 hour for current conditions and 15 minutes to 6 hours for the forecast; when that passes it asks again with If-Modified-Since, and a 304 keeps the same payload with meta.cacheAgeSeconds back at 0 — so 0 means “just confirmed with the provider”, not “just produced”. The provider’s model run is providerModelUpdatedAt; meta.fetchedAt is when the payload was fetched or last confirmed. If the provider cannot be reached once the copy has expired, the expired copy is served with meta.stale: true, meta.degraded: true and its real age, for as long as that age — counted from the fetch or the last 304 confirmation, not from the expiry — is at most 22200 seconds (370 minutes) for current conditions and 90000 seconds (25 hours) for the forecast: the policy’s default TTL (10 minutes and 1 hour) plus its stale allowance (6 hours and 24 hours), whatever Expires actually set. A copy older than that is not served: the route answers 503. There is no promise of a maximum lag behind the provider’s newest run: a reading can be as old as these layers add up to, and the fields above say how old.
What “current” means
observedAt is the time of the first entry of the series the provider returned when the payload was fetched — normally the hour then in progress. The payload is reused for as long as the layers above allow and observedAt stays where it was, so with an older copy it can sit hours behind the clock: compare it with meta.fetchedAt and meta.cacheAgeSeconds before calling a reading “now”. It is never the time of a measurement, because none was taken; if your product needs observations from stations, this is not the source for it.

Errors, limits and browser access

The error envelope
A refused request answers with one object and a stable code; the message is for a human and is not part of the contract. A missing or out-of-range coordinate is 400 VALIDATION_FAILED.
The error envelope
{ "error": { "code": "VALIDATION_FAILED", "message": "…", "requestId": "…" } }
429 and 503
Too many requests from one client answer 429 with Retry-After in seconds: wait that long, do not retry sooner. A provider outage with nothing cacheable answers 503, also with Retry-After. Treat both as “later”, never as “the coordinate is wrong”. Requests are counted per route and per client in a fixed window on each serving instance, as the reference page describes — a ceiling, not a guarantee of the same figure from every edge.
Response size
A serialised page over 1 MB is truncated and flagged meta.truncated: true; the forecast at its maximum horizon stays far inside that.
From a browser
Not from a third-party web page. Responses carry Vary: Origin and grant Access-Control-Allow-Origin only to origins this deployment is configured with — HELPERG’s own — never *. Call the endpoints from your server, or from a serverless function, and pass the readings on with the attribution.
Conditional requests
Responses carry a weak ETag; send it back as If-None-Match and an unchanged answer costs a 304 instead of a body.

What this API does not provide

  • Warnings and alerts. A warning belongs to the meteorological authority that issued it; this deployment carries no authority feed and derives none from a threshold.
  • Sunrise, sunset, moon. They come from a different MET Norway service that this deployment does not call.
  • History. Only the current run and its horizon; past readings are not stored and cannot be requested.
  • More than the provider publishes. About 48 hours of hourly and roughly nine to ten days of daily values; there is no 14-day forecast because there is none at the source.
  • Radar, satellite, air quality. Not part of the service read.
  • A guarantee of availability. The status of the weather domain is public at API status; that page, not this one, is the place to check before relying on a call.

Attribution text to show with the readings: Weather data from MET Norway (Norwegian Meteorological Institute), CC BY 4.0 — the exact wording and links are in every response under meta.attributionRecords. Full provider detail on the sources page; the economic endpoints have their own quickstart.