Alluv API
HTTP endpoints at api.alluv.dev: the state of the service, and how many Swedish housing cooperatives the corpus covers.
Getting started
Every endpoint answers GET over HTTPS with JSON, and every response is marked Cache-Control: no-store. The base URL is https://api.alluv.dev.
An endpoint that needs a key says so below. With a key in hand, this is the whole of a request:
curl -H "Authorization: Bearer $ALLUV_API_KEY" \
https://api.alluv.dev/association/latest/counts
Versions
The version is part of the association path. latest is the only version so far.
Authentication
Send your key in either of these headers:
Authorization: Bearer YOUR_KEY
X-API-Key: YOUR_KEY
Treat a key as an opaque string: its length and format differ from key to key. It is shown once when it is issued, so keep it somewhere safe, and keep it out of public repositories. The API sends no CORS headers, so call it from a server rather than from a web page.
A key can carry an expiry date and a list of addresses it may be used from. Outside either, it is refused as invalid_key.
A key you send is always checked, even on an endpoint that does not need one: a bad key gets an error there too, rather than being ignored. The health endpoints are the exception, and never look at a key.
Getting a key
Keys are issued by hand. There is no sign-up. To ask for one, write to [email protected] and say what you intend to build.
Rate limits
A key can carry a limit: a number of requests per window. The window is fixed, so the count starts over when it ends instead of sliding along with your requests. One count covers the key across every API here. The health endpoints do not count.
A response to a request made with a limited key says where you stand:
RateLimit-Policy: "default";q=100;w=60
RateLimit: "default";r=42;t=17
| Field | Meaning |
|---|---|
q | Requests allowed per window |
w | Length of the window, in seconds |
r | Requests left in this window |
t | Seconds until this window ends |
Over the limit, you get 429 with the error rate_limited and a Retry-After header in seconds. Requests without a key are not counted against any key.
Association API
Under /association/latest.
GET /association/latest/counts
Needs a key. How many associations the corpus covers.
{
"corpus": 17331,
"available": 17309,
"as_of": "2026-11-04T07:44:04Z"
}
| Field | Meaning |
|---|---|
corpus | Associations with at least one stored annual report, whether or not it has been approved |
available | Associations with at least one approved annual report. Never more than corpus |
as_of | When the figures were computed, in UTC. They are normally refreshed every five minutes |
The figures shown are an example. If no figures have been read yet, the answer is 503 with the error not_ready, never zero. If they cannot be refreshed, you keep getting the last ones, and as_of shows their age.
GET /association/latest/status
Open. Whether the things people use are working.
{
"services": [
{"name": "Uploads", "status": "ok"},
{"name": "Processing", "status": "ok"},
{"name": "Publishing", "status": "ok"},
{"name": "Search", "status": "ok"},
{"name": "Sign-in", "status": "ok"}
]
}
A status is ok, down, or unknown, which means the last reading is more than two minutes old and nothing can be said either way. The list of services may grow, so match on name rather than on position.
GET /association/latest
Open. The commit this API is running.
{"version":"f2b430b0"}
GET /association/latest/health
Open. Whether this API is up, and nothing about anything behind it. It looks at no key and counts against no limit.
{"status":"ok"}
Service API
At the root of the host. It holds no data.
GET /
Open. The commit this API is running.
{"version":"f2b430b0"}
GET /health
Open. Whether this API is up. It looks at no key and counts against no limit.
{"status":"ok"}
Errors
An error from the API is JSON with one field:
{"error":"key_required"}
| Error | Meaning |
|---|---|
401key_required | This endpoint needs a key and the request had none. Send a key. |
401invalid_key | The key is unknown, revoked, expired, or used from an address it does not allow. Check the key; retrying will not help. |
429rate_limited | The key has used its limit for this window. Wait for Retry-After. |
503key_check_unavailable | The key could not be checked just now. This says nothing about the key. Retry after Retry-After. |
503not_ready | The figures have not been read yet. Retry after Retry-After. |
A path that is not listed here answers 404, and a method other than GET or HEAD answers 405, neither with a JSON body.