Issue Nº 11 — Aug 22, 2026

InfraNode puts a retry-after header on a 200, and names the exact licence behind every number it returns

Issue eleven covers InfraNode, a free proxy over German city open data. This session called four endpoints live. Every response carried real rate-limit headers, even the successful ones. Every payload named its source agency and licence. One city's air-quality reading came back empty this session, while another city's was two hours old.

What it covers

InfraNode is an open-source proxy over public data for German cities: weather, air quality, EV charging points, and more. A live GET to https://infranode.dev/api/v1/cities this session returned 200 with 84 cities, of which 28 carry full coverage across every dataset. No key and no sign-up were needed.

Three live GETs to https://infranode.dev/api/v1/cities/berlin/weather, /air, and /charging this session all returned 200. Each payload sits inside the same envelope: a data block, a meta block with a source_status field, and an attribution block naming the upstream agency and its licence.

Auth and rate limits, confirmed live

No key. The GET to /api/v1/cities this session went out with no credentials and returned real data, which matches the auth: None already on file for this entry.

Every response this session, including the 200s, carried ratelimit-limit: 300, a live ratelimit-remaining count, and a retry-after value near 60. A retry-after header on a successful call is unusual; most APIs send it only on a 429. This session did not push past the limit, so no 429 was seen or confirmed.

CORS is open. A GET to /api/v1/cities sent this session with Origin: https://greatapis.com got access-control-allow-origin: * back, so a browser can call this API directly from client-side code.

Licence and source, named in every payload

Each record names its own upstream source and licence, instead of leaving the caller to look them up. The Berlin weather reading this session named source: dwd (the German Weather Service) under licence geonutzv. The Berlin air reading named source: uba (the Federal Environment Agency) under licence dl_de_by_2_0. The Berlin charging reading named source: bnetza (the Federal Network Agency) under licence cc_by_4_0.

Each record also carries an attribution object with the exact credit text and a link to the licence, ready to show a reader without extra lookup work.

Freshness varies by city and by dataset

Berlin's weather reading this session carried an observed_at about 24 minutes before its retrieved_at, so the number was fresh. Munich's air reading this session was about two hours old.

Berlin's air reading this session came back with source_status: ok, but every pollutant field (PM10, PM2.5, NO2, O3, SO2) was null and observed_at was null too. Munich's air reading, checked the same session, held real numbers. The status field alone does not tell a caller whether a given city has a usable reading right now.

Berlin's charging-station reading this session carried a retrieved_at about five and a half days old, against a weather reading that was under half an hour old. A build that mixes these datasets should treat each one's age on its own, not assume they refresh together.

The live OpenAPI spec, fetched this session from https://infranode.dev/openapi.json, also names a newer /api/v1/live/{slug}/air path as the successor to the /air path this session called; the older path's response carried a deprecation header pointing at it.

InfraNode, by the numbers

Rendered live from the atlas entry
AuthenticationNone required
HTTPSSupported
CORSEnabled
PricingFree
FormatsJSON
Rate limit

300 requests/minute (burst) and 6,000 requests/hour (sustained) per IP; the MCP endpoint is separately limited to 480 requests/minute per IP

Key endpoints
  • GET/api/v1/cities
  • GET/api/v1/cities/{slug}/weather
  • GET/api/v1/cities/{slug}/air
  • GET/api/v1/cities/{slug}/charging
germanyopen-datacivic-dataweathertransit

Sources

Facts checked Aug 2026