Errors
Always JSON, never an HTML page. Two fields: what went wrong, and what to do.
json
{
"error": "API key required",
"detail": "Send it as `Authorization: Bearer <key>` or `X-API-Key: <key>`. Get a free key at https://www.barcelona-rent.fyi/data"
}Status codes
| Status | Meaning | What to do |
|---|---|---|
| 401 | Unauthorized | No key sent, or the key is unknown or revoked. Not retryable — fix the key. |
| 429 | Too Many Requests | Daily limit reached, or a fourth key requested from one address in a day. Retry-After gives the seconds. |
| 400 | Bad Request | Malformed JSON body on POST /api/keys. |
| 500 | Server Error | Ours. Retry with backoff; if it persists, open an issue. |
Retrying
Only 429 and 500 are worth retrying. On a 429 the reset is at midnight UTC, so backing off for seconds is pointless — read Retry-After and sleep, or fail the job and run it tomorrow.
js
const res = await fetch(url, {headers: {Authorization: `Bearer ${key}`}});
if (res.status === 401) throw new Error("bad key — not retryable");
if (res.status === 429) {
const wait = Number(res.headers.get("Retry-After") ?? 3600);
throw new Error(`quota spent, resets in ${Math.round(wait / 60)} min`);
}
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);Missing values are not errors
A neighbourhood with too few registered contracts in a quarter comes back as null, with a 200. The Generalitat suppresses thin cells rather than publishing a figure built on a handful of contracts, and so do we — treat null as “not published”, never as zero.