# Census Wazi — Raw Data API

A free, static JSON API exposing South African Census data (Statistics South Africa,
weighted estimates) per municipality. No key, no rate limit, no server — just files on a CDN.

> **Source:** Statistics South Africa — Census. Figures are **weighted estimates at full
> decimal precision**. Please attribute StatsSA when you use this data.

---

## Quick start

Every municipality is one JSON file, named by its municipality code:

```
https://<host>/index.json          ← manifest: every available municipality
https://<host>/LIM344.json         ← all data for Makhado
https://<host>/CPT.json            ← all data for City of Cape Town
```

```js
const res  = await fetch('https://<host>/LIM344.json');
const data = await res.json();
```

Start with **`index.json`** to discover the 214 available codes (213 municipalities + a
national `ZA` aggregate). Metros use 3-letter codes (`CPT`, `JHB`, `ETH`, `EKU`, `TSH`,
`NMA`, `BUF`, `MAN`); all others use their StatsSA `CAT_B` code (e.g. `LIM344`, `WC011`).

```jsonc
// index.json
{
  "count": 214,
  "source": "Statistics South Africa — Census (weighted estimates, full precision)",
  "municipalities": [
    { "code": "BUF", "name": "Buffalo City", "province": "EC", "district": "Buffalo City" },
    { "code": "LIM344", "name": "Makhado", "province": "LIM", "district": "Vhembe" }
    // …
  ]
}
```

---

## File structure

```jsonc
{
  "code": "LIM344",
  "name": "Makhado",
  "province": "LIM",
  "district": "Vhembe",
  "source": "…",
  "sections": {
    "population": { "label": …, "unit": …, "years": { … } },
    "language": { … },
    "early_childhood_development": { … },
    "education": { … },
    "country_of_birth": { … },
    "source_of_water": { … },
    "piped_water": { … },
    "toilet": { … },
    "cooking_fuel": { … },
    "lighting": { … },
    "refuse_removal": { … }
  }
}
```

Every section has the same wrapper: **`label`**, **`unit`**, and **`years`**. Under each year
is that section's breakdown.

---

## ⚠️ Read this before using the numbers

1. **These are raw, fully-disaggregated counts. There are NO totals.** A value like "how many
   residents use candles" is **not stored** — you compute it by summing the disaggregated
   leaves yourself. This is deliberate: the API stays faithful and generic; you decide how to
   aggregate, round, and present.

2. **Units differ by section** (the unit is in each section's `unit` field, because it is *not*
   in the raw data — it's domain knowledge):
   - **Everything is `residents` (people)** — including water, toilets, cooking fuel, lighting,
     refuse (these are resident counts, *not* household counts).
   - **The one exception: `early_childhood_development` is `children aged 0–4`.**

3. **Full-precision decimals.** Counts are weighted estimates like `6363.0092269`. Round only
   at presentation time.

4. **Year coverage:** only **`population`** spans all four censuses (`1996`, `2001`, `2011`,
   `2022`). **Every other section is `2022` only.**

5. **Sections do NOT partition the population — each has its own universe.** Do not assume a
   section's categories sum to the total population. For example (Makhado 2022): country-of-birth
   ≈ 100% of residents, language ≈ 96.5%, the infrastructure sections ≈ 95.7%, education ≈ 87.4%
   (age-restricted), and ECD counts only children 0–4. The "missing" residents are simply
   out-of-universe — they are **not** always captured in an `Unspecified` bucket.

6. **`Unspecified` buckets** appear in some sections (population race/age, language, education,
   country_of_birth, ECD) but **not** in the six infrastructure sections. Keep them when summing
   if you want the full universe.

---

## The four breakdown shapes

### 1. `population` — sex → race → age (all 4 years)

The complete cross-tabulation. Sum along whichever dimension you need.

```jsonc
"population": {
  "label": "Population", "unit": "residents",
  "years": {
    "2022": {
      "female": {
        "Black African": { "0-9": 52211.9492, "10-19": 44731.62021, /* … */ "Unspecified": 1 },
        "White": { /* … */ }
        // + Coloured, Indian or Asian, Other, Unspecified
      },
      "male": { /* … */ }
    }
    // + 1996, 2001, 2011
  }
}
```

### 2. race → category (`language`, `early_childhood_development`)

No sex dimension.

```jsonc
"language": {
  "label": "Language (first/home language)", "unit": "residents",
  "years": { "2022": {
    "Black African": { "Tshivenda": 367203.9823308, "Xitsonga": 70490.8556204, /* … */ "Unspecified": 0 }
    // + other race groups
  }}
}
```

### 3. race → sex → category (`education` + the 6 infrastructure sections)

```jsonc
"lighting": {
  "label": "Lighting Energy/Fuel", "unit": "residents",
  "years": { "2022": {
    "Black African": {
      "male":   { "Electricity from mains": 208468.1235631, "Candles": 3172.4097458, /* … */ },
      "female": { "Electricity from mains": 240429.7661087, "Candles": 3178.4430773, /* … */ }
    }
    // + other race groups
  }}
}
```

`education` uses this shape too, with its full education-level labels as the categories.

### 4. country → { iso_code, sex } (`country_of_birth`)

```jsonc
"country_of_birth": {
  "label": "Country of Birth", "unit": "residents",
  "years": { "2022": {
    "Zimbabwe":     { "iso_code": "ZWE", "male": 7102.2569222, "female": 5351.6014086 },
    "South Africa": { "iso_code": "ZAR", "male": 227311, "female": 259903 },
    "Unspecified":  { "iso_code": null, "male": 136.787874, "female": /* … */ }
  }}
}
```

---

## Worked example — "How many residents use candles for lighting in Makhado (2022)?"

There is no `total` field, so sum the disaggregated leaves:

```js
const data = await (await fetch('https://<host>/LIM344.json')).json();
const byRace = data.sections.lighting.years['2022'];

let candles = 0;
for (const race of Object.values(byRace))
  for (const sex of Object.values(race))
    candles += sex['Candles'] ?? 0;

Math.round(candles); // → 6363
```

---

## Notes on labels

Values are never altered, but a few labels are cleaned for usability:
- **Country of birth** keys are split into a name + `iso_code` (e.g. the raw `"Zimbabwe (ZWE)"`
  becomes `"Zimbabwe"` with `"iso_code": "ZWE"`). South-Africa-born is coded `ZAR`; buckets with
  no country (`Unspecified`) have `"iso_code": null`.
- **Education** levels are given as readable labels (the raw data stores them as a positional array).
- Sex keys are lowercased (`male` / `female`).

---

## Coverage & limits

- 214 files: 213 municipalities + national `ZA`.
- Data is static (the census doesn't change), cached on a CDN, and CORS-enabled (`*`) for use
  from any website.
- Map boundaries / geometry are **not** included — this is a data API only.
