The error contract
The most dangerous answer an API can give is 200 OK with an empty list. It means two completely different things — *"I asked and the world is empty"* and *"my question never got through"* — and it says them with identical words. We have measured fifteen variants of that failure in our own sources, and built this contract to avoid passing it on.
Rule 1: empty is an answer, a failure is a failure
If the question goes through and the answer is empty you get 200 and an empty list. If the question does not go through you get 502 or 503 with a skal (reason) field in plain language. A backend that is down never becomes an empty list on your side.
Rule 2: null means unmeasured, never zero
"spridning": {
"traffar": 0, "kommuner": 0,
"entydig": null,
"skal": "the query matches no place name — spread is UNMEASURED for this query"
}entydig: null means the query could not be measured (a street address is not a place name). Never treat it as true. If you cannot afford to point at the wrong place: require entydig === true, otherwise ask the user.
The same rule applies to hojd_m: null with nodata: true (no elevation data at the point — not sea level) and to swing: null in election results (the district may not be compared — not zero change).
Rule 3: a ceiling is not a count
antal is how many rows you got. total (place names) and antal_i_fonstret (logging) are how many exist, counted before the limit, and avkortad/trunkerad say the ceiling cut the list.
An API that only gives you the row count teaches you to publish the limit as a result: four different place names all returned exactly 50 hits in our own measurement, because 50 was the cap.
Rule 4: "not swept" is unknown, not zero
Registers we fetch per municipality carry a state field: kommunen_ar_svept, tillstand: "inlast". If it is false or missing, the count is unknown. Don't write "no shelters in this municipality" on a response that means we haven't fetched it yet — we made that mistake ourselves, for 174 municipalities, which is why the field exists.
Rule 5: an image that cannot be filled is not delivered
/v1/render/vinjett answers 503 with lagsta_fyllande_zoom when the map cannot cover the frame, instead of shipping an image with holes. The X-Fyllnad header (0–1) says how much was filled.
Three ways to be outside Sweden
They look alike when you write the code and mean different things in the response:
- Outside the coordinate window (Paris):
422. We don't even ask — the number cannot be a point in Sweden. - Inside the window, outside the country (Oslo): lookups that need a municipality answer
404with a reason. Elevation answers200withhojd_m: nullandnodata: true, reverse geocoding200withnamn: null. That is not the same as zero metres above sea level. - Inside the country, no data at that point (a lake in the forest):
200withnodata: true. The question got through; the map has no height there.
Status codes
| Code | Meaning |
|---|---|
200 | An answer. Still read the state fields above. |
400 | A parameter could not be parsed; detail says which. |
401 | Invalid or blocked key. |
404 | Unknown path, unknown municipality code, or a point inside the coordinate window but outside every Swedish municipality. Oslo returns utanför Sveriges kommuner. |
422 | A parameter is out of range. The coordinate window is lat 55–69.5, lon 10–24.5; Paris is rejected here, not as a 404. |
429 | Rate limit reached; Retry-After says for how long. |
502 | A source we call did not answer. Retry. |
503 | We cannot answer truthfully right now; detail says why. |