CLARA docs

CLARA API

Street-level environmental intelligence, per address. CLARA simulates how air quality, wind and heat behave between the buildings of a city, modelled at up to 1×1 m, and serves the results as a clean JSON API: annual statistics, live conditions, forecasts, generated reports and map layers.

What CLARA provides

One API, several products. A key can hold any combination of them.

ProductWhat it answersEndpoints
Historical statistics What a full reference year looks like at this address: air quality, pedestrian wind comfort and thermal comfort, with ratings. GET /v1/historical/stats
Real-time Conditions right now, hour by hour. GET /v1/realtime
Forecast The coming hours, roughly two days ahead. GET /v1/forecast
Reports A complete Environmental Intelligence Report for an address, as HTML and PDF. POST /v1/reports
Map City-wide map layers as vector tiles, live and forecast. See the map guide, or skip the plumbing with the browser SDK. GET /v1/map/{city}
Geocoding helper Turns an address into coordinates for CLARA queries. A helper, not a standalone service: results may only be used to query CLARA and must not be stored or redistributed. GET /v1/geocode

API keys

Every data endpoint needs a key, sent as a bearer token. Keys are issued by the CLARA team: write to [email protected] to get yours. Each key is scoped to the cities and products you use, and GET /v1/key shows you that scope at any time.

There are two kinds, told apart by prefix:

PrefixKindWhere it lives
clara_sk_ Secret key Your server only. Never ship it in a browser page, an app bundle or a public repository. Requests carrying a secret key from a browser are rejected.
clara_pk_ Publishable key Safe inside a browser page. Locked to the origins you register and limited in what it can do and how fast.
Before your first browser test: a publishable key only works from the origins registered on it, matched exactly on scheme, host and port. https://example.com, https://www.example.com and http://localhost:3000 are three separate entries. Tell us every origin you will use, production, staging and your local dev port, or your first day of integration will be a wall of 403s that has nothing to do with your code. And serve your test page over http rather than opening it from disk: a file:// page sends Origin: null, which matches nothing.

Send the key with either header, whichever fits your HTTP client:

Authorization: Bearer YOUR_CLARA_KEY
X-API-Key: YOUR_CLARA_KEY

Your first call

Start with GET /v1/key. It costs nothing, confirms your key works, and shows exactly what the key can reach, so you never have to guess what you were given.

curl -sS https://api.clara.city/v1/key \
  -H "Authorization: Bearer YOUR_CLARA_KEY"

The response is your key's scope:

{
  "label": "your-company",
  "type": "sk",
  "cities": ["brussels"],
  "products": ["geocode", "historical", "realtime", "forecast", "reports"],
  "limits": { "rate_per_min": 100, "reports_per_day": 100 },
  "created": "2026-08-20",
  "expires_at": null
}

Your first data call

Ask for the historical statistics of an address. You can pass an address directly, or geocode first and pass coordinates; coordinates are the primary way and skip a geocoding step on every call.

curl -sS "https://api.clara.city/v1/historical/stats?lat=50.82068&lon=4.35821" \
  -H "Authorization: Bearer YOUR_CLARA_KEY"
The first request for a location computes its statistics from a year of hourly data and can take a few minutes. Every later request for the same location answers instantly from cache; the X-Cache header says which happened. Set your client timeout generously on the first call.

Abridged response:

{
  "city": { "id": "brussels", "name": "Brussels", "country": "BE" },
  "location": { "address": null, "lat": 50.82068, "lon": 4.35821 },
  "meta": {
    "dataset": "brussels_extended",
    "resolution_m": 8,
    "reference_year": 2025,
    "sampling": "nearest_outdoor",
    "distance_m": 7.5,
    "n_cells": 3,
    "hours": 8760,
    "generated_utc": "2026-08-20T09:12:04Z"
  },
  "air":     { "rating": 7.3, "good_pct": 72.7, "dominant_pollutant": "NO2", "...": "..." },
  "wind":    { "rating": 8.6, "calm_pct": 96.2, "...": "..." },
  "thermal": { "rating": 5.7, "tropical_nights": 8, "...": "..." }
}

Reading a response

Every data response opens the same way, so once you can read one, you can read them all.

BlockMeaning
city Which city answered. Present when the location falls inside a covered city; absent when a nationwide dataset answered.
location The resolved address label (or null for raw coordinates) and the coordinates actually used, rounded to five decimals.
meta What produced the answer: the dataset, its grid resolution in metres, the reference year, how the location was sampled and when the numbers were computed. Keep it next to any value you store, so you always know what you are holding.

After the envelope come the data blocks, air, wind and thermal. Each carries a rating, one number from 0 to 10 that is the headline answer, plus the full statistics behind it. The API reference documents every field.

Where CLARA works

CLARA covers a growing set of cities, each with its own products, and the list is itself an endpoint. No key needed:

curl -sS https://api.clara.city/v1/cities

Each entry carries the city identifier, its bounding box and which products it has; GET /v1/cities/{city} adds the exact boundary polygon as GeoJSON, ready to draw on a map or to pre-filter your locations before spending requests. With a key attached, each entry also says whether your key is scoped to it.

In Belgium, historical statistics and reports go further: any Belgian address is served. Inside the Brussels coverage zone the answer comes from the street-level model; elsewhere it comes from a lower-resolution nationwide dataset, and meta.dataset tells you which one you got.

Scales and colours

Every classified value CLARA returns, air quality classes, thermal stress categories, wind comfort classes, follows a published scale with official labels, bounds and colours. The scales are served by the API so you never hardcode them:

curl -sS https://api.clara.city/v1/scales/air-belaqi

Available scales: belaqi (the Belgian air quality index, 1 to 10), utci (thermal stress categories) and wind (pedestrian wind comfort classes). Responses are public and cacheable for an hour.

Errors

Errors are RFC 9457 problem documents, application/problem+json, and every one carries a machine-readable code to branch on and a request_id to quote when something needs investigating:

{
  "type": "https://www.clara.city/docs/errors#outside_coverage",
  "code": "outside_coverage",
  "title": "Outside coverage area",
  "status": 422,
  "detail": "this location is outside the CLARA service area",
  "request_id": "7c1f4c2a-0f3e-4d2b-9a6e-5b8d0c1e2f3a"
}

One rule makes them easy to reason about:

  • A 403 is always about your key: deactivated, expired, or not scoped to that city or product. Fix it by talking to us, not by changing your request.
  • A 404 or 422 is always about the data: the resource does not exist, the location is not covered, or the product does not exist there. Your key is fine.
CodeStatusMeaning
validation_error400A parameter is missing or malformed.
unauthorized401The key is missing or unknown.
forbidden403The key is valid but not entitled to this.
not_found404No such resource.
outside_coverage422The location is outside every covered area.
product_unavailable422The city exists but does not have this product.
rate_limited429Too many requests this minute; retry after Retry-After seconds.
quota_exceeded429The daily report quota is used up; it resets at midnight UTC.
upstream_error502Something behind the API failed; retry.
upstream_timeout504The computation did not finish in time; retry.

Rate limits

Limits are per key: a per-minute request limit and a daily report quota, both visible in GET /v1/key. Every authenticated response reports where you stand:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute.
X-RateLimit-RemainingRequests left in the current minute.
X-RateLimit-ResetWhen the next minute window starts, epoch seconds.
Retry-AfterOn a 429: seconds to wait before retrying.

If the defaults do not fit your integration, ask: limits are per key and adjustable.

Where to go next