PlotFinder — Jurisdiction API

Who governs this property?

One coordinate in, the full chain of administrative authority out: province, district and local municipality, ward, registration division and the Deeds Office of jurisdiction — who rates it, who services it, which councillor covers it, where to file.

Overview

The Jurisdiction API answers "who governs this property?" Give it any coordinate or land parcel in South Africa and it returns the full chain of administrative authority in one call: the province, the district and local municipality, the ward, the registration division and the Deeds Office of jurisdiction — each with its code, name and vintage.

It sits alongside the Parcel Intelligence API and the Encumbrance API: where those describe the parcel itself and what restricts it, this one answers who rates it, who services it, and where its paperwork belongs.

Use caseHow the API helps
Rates & municipal accounts One call per property returns the municipality that rates and services it — with the metro / local / district distinction made for you.
Conveyancing & deeds work The registration division and the Deeds Office of jurisdiction for any point, so documents are filed at the right registry the first time.
Bulk-tagging a customer database POST /batch takes up to 5,000 coordinates per call and returns the chain for each, in order — municipality and ward columns for a whole CRM in a handful of calls.
Constituency & ward reporting The 2016-delimitation ward for any address or asset, plus /boundaries/{type}/{code}/children to walk a municipality's wards.
Service-area qualification /boundaries listings, boundary polygons and fuzzy /search ("Overstrand", "Ward 42 Ekurhuleni") for building coverage rules.

Authentication

Every request carries your API key in the X-API-KEY header. Keys are issued per contract by PlotFinder and carry the jurisdiction product entitlement; a PlotFinder key for another product answers 403 ProductNotPermitted until the product is added to it.

Request
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/jurisdiction/at?lat=-34.4187&lon=19.2345"
Base URL
https://api.starlightsoftware.co.bw/api/v1/jurisdiction

Response conventions

Vintage & accuracy

Administrative boundaries change on a cycle, and different layers are published at different moments. Every chain link names its vintage so there is never a question about which boundary set answered:

LayersVintageWhat that means
Province, district municipality, local municipality 2018 demarcation The Municipal Demarcation Board's 2018 boundary set, in force since the 2016 local government elections.
Ward 2016 delimitation The ward set used at the 2016 local government elections.
Registration division, Deeds Office, township cadastral register The national cadastral dataset's administrative layers — a continuously maintained register rather than a dated cycle.

How ?as_at behaves. Boundaries carry the dates they took and left force. A request without as_at resolves against today's boundaries. With as_at, only the sets in force on that date answer — so when a future demarcation is loaded, both eras stay queryable side by side. A date before the earliest loaded set answers with an empty chain and a note, not a wrong answer.

Points are resolved against the full-resolution boundary geometry, including within a metre of a shared edge — the same coordinate always returns the same chain.

Municipality categories

South African municipalities come in three constitutional categories:

CategoryKindIn this API
A Metropolitan — one municipality for a whole metro (Johannesburg, Cape Town, eThekwini, …) Returned as the local_municipality with "category": "A"; district_municipality is null with a note, because a metro is both the local and the district authority.
B Local — a town or rural municipality inside a district Returned as the local_municipality, with its Category C district in district_municipality.
C District — the umbrella over several Category B locals Returned in district_municipality, and listable with its children.

Coverage

GET /coverage reports live counts, vintages and the load ledger. The standing picture:

LayerBoundariesCoverage
Province9National
District municipality52National (44 districts + the 8 metros' Category A records)
Local municipality213National (205 locals + 8 metros)
Ward4,392National
Registration division221National
Township5,796National — the suburb-scale layer
Deeds Office area5Partial: published for FS, GP and NC only — a null can simply mean the point lies outside the published areas
Suburb74Partial: EC, FS and NC only
Pre-1994 municipal452Partial: EC, FS, NC and WC — for legacy title research

Rate limits

Limits are per key, per minute, set by your contract. Responses carry the IETF draft standard headers:

Response headers
RateLimit-Policy: 60;w=60
RateLimit: limit=60, remaining=57, reset=41

Exceeding the limit answers 429 RateLimitExceeded with a Retry-After header. POST /batch exists for volume work — one request covers up to 5,000 points.

Worked examples

1 · The chain at a point

A coordinate in Hermanus. One call returns everything from the province down to the township, each link carrying its vintage:

Request — cURL
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/jurisdiction/at?lat=-34.4187&lon=19.2345"
200 Response
{
  "jurisdiction": {
    "province": {
      "code": "WC",
      "name": "Western Cape",
      "vintage": "2018 demarcation",
      "effective_from": "2016-08-03"
    },
    "district_municipality": {
      "code": "DC3",
      "name": "Overberg",
      "category": "C",
      "vintage": "2018 demarcation",
      "effective_from": "2016-08-03"
    },
    "local_municipality": {
      "code": "WC032",
      "name": "Overstrand",
      "category": "B",
      "vintage": "2018 demarcation",
      "effective_from": "2016-08-03"
    },
    "ward": {
      "code": "10302003",
      "name": "WC032_3",
      "vintage": "2016 delimitation",
      "effective_from": "2016-08-03"
    },
    "registration_division": {
      "code": "C013",
      "name": "C013",
      "category": "ADMIN",
      "vintage": "cadastral register"
    },
    "deeds_office": null,
    "township": {
      "code": "C0130013",
      "name": "HERMANUS",
      "vintage": "cadastral register"
    }
  },
  "notes": [
    "Deeds Office areas are published for three provinces only (FS, GP, NC); a null here can mean the point lies outside the published areas."
  ],
  "as_at": "2026-08-20",
  "message": "Administrative jurisdiction at this point",
  "as_of": "2026-08-20",
  "dataset_version": "boundaries-2026-08-20"
}
Overstrand (WC032)Ward WC032_3 · Overberg District · Western Cape10 km
Local municipalityWardYour coordinateOne coordinate resolves the whole chain — drawn here: the municipality and the ward that contain it

2 · Where do I file?

A property in central Johannesburg: the deeds registry that holds its records, its registration division, and the metro that rates it — with the Category A note making the null district self-explanatory:

Request — cURL
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/jurisdiction/at?lat=-26.2041&lon=28.0473"
200 Response
{
  "jurisdiction": {
    "province": {
      "code": "GP",
      "name": "Gauteng",
      "vintage": "2018 demarcation",
      "effective_from": "2016-08-03"
    },
    "district_municipality": null,
    "local_municipality": {
      "code": "JHB",
      "name": "City of Johannesburg",
      "category": "A",
      "vintage": "2018 demarcation",
      "effective_from": "2016-08-03"
    },
    "ward": {
      "code": "79800060",
      "name": "JHB_60",
      "vintage": "2016 delimitation",
      "effective_from": "2016-08-03"
    },
    "registration_division": {
      "code": "IR",
      "name": "IR",
      "category": "ADMIN",
      "vintage": "cadastral register"
    },
    "deeds_office": {
      "code": "RAND TOWNSHIP REGISTER",
      "name": "Rand Township Register",
      "vintage": "cadastral register"
    },
    "township": {
      "code": "T0IR0310",
      "name": "JOHANNESBURG",
      "vintage": "cadastral register"
    }
  },
  "notes": [
    "This point falls inside a Category A metropolitan municipality, which is both the local and the district authority — no separate district municipality applies."
  ],
  "as_at": "2026-08-20",
  "message": "Administrative jurisdiction at this point",
  "as_of": "2026-08-20",
  "dataset_version": "boundaries-2026-08-20"
}
Rand Township RegisterRegistration division IR · City of Johannesburg (Category A metro)10 km
Deeds Office areaYour propertyFile here: the deeds registry whose area holds this property's records

3 · Bulk-tagging a customer list

The volume product: up to 5,000 coordinates per call, results in input order, null where a point falls outside South Africa's demarcated area. Tag a whole customer database with municipality and ward in a handful of calls:

Request — cURL
curl -X POST -H "X-API-KEY: YOUR_API_KEY_HERE" -H "Content-Type: application/json" \
  -d '{
    "points": [
      {"latitude": -34.4187, "longitude": 19.2345},
      {"latitude": -26.2041, "longitude": 28.0473},
      {"latitude": -29.8285, "longitude": 31.0303},
      {"latitude": -35.5,    "longitude": 19.0}
    ]
  }' \
  "https://api.starlightsoftware.co.bw/api/v1/jurisdiction/batch"
Request — JavaScript
const rows = customers.map((c) => ({ latitude: c.lat, longitude: c.lng }));
const res = await fetch(
  "https://api.starlightsoftware.co.bw/api/v1/jurisdiction/batch",
  {
    method: "POST",
    headers: {
      "X-API-KEY": process.env.PLOTFINDER_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ points: rows.slice(0, 5000) }),
  },
);
const { results } = await res.json();
customers.forEach((c, i) => {
  c.municipality = results[i]?.jurisdiction.local_municipality?.name ?? null;
  c.ward = results[i]?.jurisdiction.ward?.code ?? null;
});
200 Response (abridged — chains shortened)
{
  "results": [
    {
      "jurisdiction": {
        "province": {
          "code": "WC",
          "name": "Western Cape",
          "vintage": "2018 demarcation",
          "effective_from": "2016-08-03"
        },
        "district_municipality": {
          "code": "DC3",
          "name": "Overberg",
          "category": "C",
          "vintage": "2018 demarcation",
          "effective_from": "2016-08-03"
        },
        "local_municipality": {
          "code": "WC032",
          "name": "Overstrand",
          "category": "B",
          "vintage": "2018 demarcation",
          "effective_from": "2016-08-03"
        },
        "ward": {
          "code": "10302003",
          "name": "WC032_3",
          "vintage": "2016 delimitation",
          "effective_from": "2016-08-03"
        },
        "registration_division": {
          "code": "C013",
          "name": "C013",
          "category": "ADMIN",
          "vintage": "cadastral register"
        },
        "deeds_office": null,
        "township": {
          "code": "C0130013",
          "name": "HERMANUS",
          "vintage": "cadastral register"
        }
      },
      "notes": [
        "Deeds Office areas are published for three provinces only (FS, GP, NC); a null here can mean the point lies outside the published areas."
      ]
    },
    {
      "jurisdiction": {
        "province": {
          "code": "GP",
          "name": "Gauteng",
          "vintage": "2018 demarcation",
          "effective_from": "2016-08-03"
        },
        "district_municipality": null,
        "local_municipality": {
          "code": "JHB",
          "name": "City of Johannesburg",
          "category": "A",
          "vintage": "2018 demarcation",
          "effective_from": "2016-08-03"
        },
        "ward": {
          "code": "79800103",
          "name": "JHB_103",
          "vintage": "2016 delimitation",
          "effective_from": "2016-08-03"
        },
        "registration_division": {
          "code": "IR",
          "name": "IR",
          "category": "ADMIN",
          "vintage": "cadastral register"
        },
        "deeds_office": null,
        "township": {
          "code": "T0IR0591",
          "name": "SANDOWN",
          "vintage": "cadastral register"
        }
      },
      "notes": [
        "This point falls inside a Category A metropolitan municipality, which is both the local and the district authority — no separate district municipality applies.",
        "Deeds Office areas are published for three provinces only (FS, GP, NC); a null here can mean the point lies outside the published areas."
      ]
    },
    {
      "jurisdiction": {
        "province": {
          "code": "KZ",
          "name": "KwaZulu-Natal",
          "vintage": "2018 demarcation",
          "effective_from": "2016-08-03"
        },
        "district_municipality": null,
        "local_municipality": {
          "code": "ETH",
          "name": "eThekwini",
          "category": "A",
          "vintage": "2018 demarcation",
          "effective_from": "2016-08-03"
        },
        "ward": {
          "code": "59500027",
          "name": "ETH_27",
          "vintage": "2016 delimitation",
          "effective_from": "2016-08-03"
        },
        "registration_division": {
          "code": "FU",
          "name": "FU",
          "category": "ADMIN",
          "vintage": "cadastral register"
        },
        "deeds_office": null,
        "township": {
          "code": "N0FU0085",
          "name": "DURBAN",
          "vintage": "cadastral register"
        }
      },
      "notes": [
        "This point falls inside a Category A metropolitan municipality, which is both the local and the district authority — no separate district municipality applies.",
        "Deeds Office areas are published for three provinces only (FS, GP, NC); a null here can mean the point lies outside the published areas."
      ]
    },
    null
  ],
  "found": 3,
  "not_found": 1,
  "as_at": "2026-08-20",
  "message": "Resolved 3 of 4 points",
  "as_of": "2026-08-20",
  "dataset_version": "boundaries-2026-08-20"
}
OverstrandCity of JohannesburgeThekwininull — outside the demarcated area500 km
Tagged pointNo jurisdictionResults in input order: 3 tagged, 1 honestly null — up to 5,000 points per call

Endpoint reference

The machine-readable versions are at /jurisdiction/openapi.json and /jurisdiction/postman.json.

GET /api/v1/jurisdiction/at

The full chain of administrative authority at a point

Who rates and taxes this property, who services it, which ward covers it, where to file: province, district municipality, local municipality, ward, registration division, Deeds Office of jurisdiction and township, resolved from one coordinate. Each link carries its code, name and vintage.

ParameterInRequiredDescription
latquerynoLatitude, -90..90
lonquerynoLongitude, -180..180
as_atquerynoResolve as at this date (YYYY-MM-DD)

GET /api/v1/jurisdiction/parcel/{lpi}

The chain for a land parcel, by LPI

The same chain as /at, resolved from the parcel's label point — with an explicit note when the parcel's boundary straddles an administrative boundary, and which boundaries it crosses.

ParameterInRequiredDescription
lpipathyes
as_atquerynoResolve as at this date (YYYY-MM-DD)

POST /api/v1/jurisdiction/batch

The chain for up to 5,000 coordinates in one call

Bulk-tag a customer database with municipality and ward: POST {"points": [{"latitude": …, "longitude": …}, …]} and results come back in input order — null where a point lies outside South Africa's demarcated area.

Send a JSON body; see the worked examples above for the shape.

GET /api/v1/jurisdiction/boundaries/{boundary_type}

List boundaries of a type

ParameterInRequiredDescription
boundary_typepathyes
provincequerynoFamily code, e.g. GP
municipalityquerynoParent municipality code, e.g. CPT
parentquerynoParent code at any level
as_atqueryno
limitqueryno
cursorqueryno

GET /api/v1/jurisdiction/boundaries/{boundary_type}/{code}

One boundary, with attributes

ParameterInRequiredDescription
boundary_typepathyes
codepathyes
as_atqueryno

GET /api/v1/jurisdiction/boundaries/{boundary_type}/{code}/geometry

A boundary's polygon

ParameterInRequiredDescription
boundary_typepathyes
codepathyes
formatqueryno
simplifyquerynometres
as_atqueryno

GET /api/v1/jurisdiction/boundaries/{boundary_type}/{code}/children

The boundaries one level down

Wards in a municipality, municipalities in a district, districts in a province.

ParameterInRequiredDescription
boundary_typepathyes
codepathyes
as_atqueryno

GET /api/v1/jurisdiction/coverage

Layer vintages, counts and known gaps

GET /api/v1/jurisdiction/heatmap

Coverage-map data: administrative boundaries for a viewport

Drives coverage maps, including the PlotFinder admin panel's Jurisdiction layer. Always geometry (the whole set is small): province, district and municipal outlines at any span, wards and townships scale-gated the way every family map layer behaves.

ParameterInRequiredDescription
northqueryno
southqueryno
eastqueryno
westqueryno
typesquerynocomma list; default the municipal chain

GET /api/v1/jurisdiction/tiles/{z}/{x}/{y}.mvt

Administrative overlay vector tiles

ParameterInRequiredDescription
zpathyes
xpathyes
ypathyes
layersqueryno

Error codes

Errors share one flat envelope; details appears when there is something structured to say:

400 Response
{
  "error": "ValidationError",
  "message": "Invalid query parameters",
  "details": [{ "field": "lat", "message": "lat is required" }],
  "requestId": "78be60d3-8000-4601-9fda-7d5e6838388d"
}
StatuserrorMeaning
400ValidationErrorA parameter or body failed validation; see details.
401ApiKeyMissing / ApiKeyInvalid / ApiKeySuspended / ApiKeyExpiredThe X-API-KEY header is absent, wrong, suspended, or past its expiry.
403ProductNotPermittedThe key does not carry the Jurisdiction API product.
403CountryNotPermittedThe key is scoped to Botswana only; this product serves South Africa.
404NoJurisdictionAtPointNo boundary contains the point; the nearest boundary is under details.nearest.
404BoundaryNotFoundNo boundary of that type carries the requested code at the requested date.
404ParcelNotFoundNo parcel carries the requested LPI.
429RateLimitExceededPer-minute limit reached; honour Retry-After.
503JurisdictionDataUnavailableThe boundary dataset is not installed on this deployment yet.