PlotFinder — Parcel Intelligence API

The South African cadastre, parcel by parcel

Everything the national cadastre records about a land parcel: its true surveyed boundary, its identifiers, its survey history and its neighbours — across all nine provinces. The flagship question it answers is “what parcel is at this exact point?”, with the polygon to prove it.

Overview

The Parcel Intelligence API serves the South African cadastre itself: the Erf, Farm Portion, Holding, Parent Farm, Public Place and Street parcel layers, plus survey beacons and General Plan figures export. It is the paid tier above the Plot/Coordinate API — where that product answers with a point, this one answers with the parcel: full boundary polygon, both national identifiers, region codes, sectional-scheme links, extent and survey history.

What this API is not. It answers “what is this parcel?” It does not answer what restricts a parcel (servitudes and encumbrances), who governs it (municipal jurisdictions), or resolve place names — those are separate PlotFinder products, planned to sit alongside this one. And it is South Africa only: Botswana lookups live in the Plot/Coordinate API.

Use case How the API helps
Conveyancing & property law Resolve a 21-digit LPI or a Surveyor-General key to the parcel record, its boundary and the General Plan it was surveyed under.
Proptech & valuation Draw the actual erf boundary on a map, read its extent in square metres, and pull every adjoining parcel for comparables.
Utilities, telecoms & delivery Turn a GPS position into the exact parcel a crew is standing on — not the nearest point, the containing polygon.
Banking & insurance Verify that a stated erf exists, is current rather than withdrawn, and matches the extent on the documents — in bulk, through the batch endpoint.

Base URL

All endpoints are versioned under /api/v1.

Base URL
https://api.starlightsoftware.co.bw/api/v1/parcels

The two identifiers

Every parcel carries up to two national identifiers, and the API accepts both:

Identifier Shape What it is
lpi 21 characters, e.g. C01500020000392900000 The Land Parcel Identifier: deeds registry, region codes, parcel number and portion in one string. The primary key most documents quote.
source_key 26 characters, e.g. N065C015000200003929000001 The Surveyor-General's own parcel key (PRCL_KEY) — the identity the cadastre maintains internally, stable across exports.

One LPI can match several records. The cadastre keeps a parcel's history — a current version and superseded ones. Lookups therefore return a parcels array, current version first, and every record carries its legal_status and work_status so versions can be told apart. By default only current records are returned — see Status codes.

Getting Access

Access is issued under contract, per product. If your key was sold the Plot/Coordinate API only, requests here answer 403 ProductNotPermitted — adding Parcel Intelligence to an existing key is a contract change, not a new credential, so your integration keeps the key it already uses. Email info@starlightsoftware.co.bw with your business name, use case and expected volume.

Authentication

Every request carries your API key in the X-API-KEY header — the same header, and the same key, as the Plot/Coordinate API.

Request
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/parcels/at?latitude=-31.466380&longitude=19.758913"

Keys are scoped: to products (this page requires the parcels product), to a country (Parcel Intelligence data is all South African, so a Botswana-only key is refused with CountryNotPermitted), and optionally to a term. The refusals are listed under Errors, each with a distinct error code so your integration can tell configuration problems from data problems.

Status codes

Every parcel record carries two status fields from the cadastre, each returned as an object with the raw code and a human label — never a bare letter.

Legal status (legal_status, the cadastre's LSTATUS)

Code Label Meaning
R Registered Registered in a deeds office.
S Surveyed Approved by the Surveyor-General, not yet registered.
P Proposed A proposed parcel, not yet approved.
A Archived Retired from the live cadastre.
W Cancelled/Withdrawn Cancelled or withdrawn survey.

Work status (work_status, the cadastre's WSTATUS)

Code Label Meaning
C Current The current version of the parcel.
P Proposed A proposed change.
H Historic A superseded version, kept for history.

The default filter, and how to widen it. By default every endpoint hides parcels the source has retired: legal status A or W, and work status H. Pass include_status=all to receive the cadastre's full content, history included. Note that a status field can be absent: a substantial share of records — around half of the Northern Cape — carries no work status at all in the source. Those records are treated as current and returned by default; filtering them out would silently hide live parcels, so the filter works by exclusion, never by requiring C.

Coverage & limitations

The dataset is per-province export: all nine provinces, across the six parcel layers, plus survey beacons and General Plan figures. Coordinates are geographic WGS84 (Hartebeesthoek94 datum). The live per-province, per-layer counts — including the export each number came from — are served by GET /coverage, which is always more current than this page.

Known gaps:

  • Holding (agricultural holdings) exists in six provinces — Free State, Gauteng, KwaZulu-Natal, Limpopo, Mpumalanga and North West. The Eastern Cape, Northern Cape and Western Cape record none in the source.
  • Beacons are sparse in some provinces — they are published where the offices have captured them, not as a complete national set.
  • Suburb boundaries exist for only a handful of areas in the source and are not served by this API.
  • Around half of Northern Cape records carry a blank work status — see Status codes for how that is handled.

Data currency

This API is loaded and is refreshed quarterly. Every response carries a dataset_version naming the export its data came from, so you always know how fresh an answer is.

Endpoints

All read-only. Lookups share one envelope: a parcels array, a message, and the dataset_version the answer came from. Paginated endpoints add next_cursor when there is more.

GET /api/v1/parcels/at

The flagship lookup: exact point-in-polygon. Returns every parcel whose boundary contains the point, most specific first — a point in a town erf can also fall inside the parent farm the township was cut from, so the first element is the answer and the rest are its context.

Parameters

Name Type Description
latitude float Required Between −90 and 90.
longitude float Required Between −180 and 180.
parcel_type string Optional Restrict to one layer: ERF, FARM_PORTION, HOLDING, PARENT_FARM, PUBLIC_PLACE or STREET.
include_status string Optional all to include retired and historic records.
Request
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/parcels/at?latitude=-31.466380&longitude=19.758913"
200 Response
{
  "parcels": [
    {
      "id": 1012,
      "parcel_type": "ERF",
      "province": "NORTHERN CAPE",
      "source_key": "N065C015000200003929000001",
      "dataset_version": "csg-2026-07-18",
      "lpi": "C01500020000392900000",
      "parcel_number": 3929,
      "portion": 0,
      "tag_value": "RE/3929",
      "major_region": "CALVINIA",
      "major_code": "C0150000",
      "minor_region": "CALVINIA",
      "minor_code": "C0150002",
      "legal_status": { "code": "R", "label": "Registered" },
      "work_status": { "code": "C", "label": "Current" },
      "extent_sqm": 518.1,
      "label_point": { "latitude": -31.4663795, "longitude": 19.7589129 },
      "date_stamp": "2018-02-01"
    }
  ],
  "message": "Parcels containing this point, most specific first",
  "dataset_version": "csg-2026-07-18"
}
C0150002000039290000010 m
Parcel boundaryYour coordinateThe parcel containing your point — erf 3929, Calvinia, 518 m²
404 Response — a point no parcel contains (here: at sea, 420 m off Hondeklip Bay)
{
  "error": "NoParcelAtPoint",
  "message": "No parcel contains this point. The nearest parcel is included under details.nearest.",
  "details": {
    "nearest": {
      "id": 244183,
      "parcel_type": "ERF",
      "province": "NORTHERN CAPE",
      "source_key": "N064C053000500000001000000",
      "dataset_version": "csg-2026-07-18",
      "lpi": "C05300050000000100000",
      "parcel_number": 1,
      "portion": 0,
      "tag_value": "1",
      "major_region": "NAMAQUALAND",
      "major_code": "C0530000",
      "minor_region": "HONDEKLIP BAY",
      "minor_code": "C0530005",
      "legal_status": { "code": "R", "label": "Registered" },
      "work_status": { "code": "C", "label": "Current" },
      "extent_sqm": 21731924.1,
      "label_point": { "latitude": -30.3251414, "longitude": 17.2921652 },
      "date_stamp": "2025-04-07",
      "distance_meters": 420
    }
  },
  "requestId": "6b966920-4451-4ba3-a247-dbac783e9d63"
}

Absent fields are omitted, never null — a Parent Farm has no township, so it simply has no minor_region. A missing status field means the source recorded none.

GET /api/v1/parcels/{lpi}

Full record(s) for a 21-character Land Parcel Identifier, current version first. Also available by Surveyor-General key: GET /api/v1/parcels/by-key/{source_key} with the 26-character PRCL_KEY, same response shape.

Request
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/parcels/C01500020000392900000"

The response envelope is identical to /at above. Add ?include_status=all to receive superseded versions of the parcel alongside the current one.

GET /api/v1/parcels/{lpi}/geometry

The parcel's boundary polygon — the current version's, as surveyed.

Parameters

Name Type Description
format string Optional geojson (default) or wkt. GeoJSON arrives as an object ready for a map library; WKT as a string.
simplify integer Optional Simplification tolerance in metres, 1–1000. Topology-preserving — a ring can never collapse or self-intersect. Omit for the full-resolution boundary.
Request
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/parcels/C01500020000392900000/geometry"
200 Response (parcel object abridged)
{
  "parcel": {
    "id": 1012,
    "parcel_type": "ERF",
    "province": "NORTHERN CAPE",
    "source_key": "N065C015000200003929000001",
    "dataset_version": "csg-2026-07-18",
    "lpi": "C01500020000392900000",
    "parcel_number": 3929,
    "…": "…"
  },
  "format": "geojson",
  "geometry": {
    "type": "MultiPolygon",
    "coordinates": [[[[19.7588904, -31.4662545], [19.7591002, -31.4662505], [19.7591052, -31.4664855], [19.7588959, -31.4664884], [19.7588904, -31.4662545]]]]
  },
  "dataset_version": "csg-2026-07-18",
  "message": "Boundary fetched successfully"
}
C0150002000039290000010 m
Parcel boundaryThe surveyed boundary, ready for a map library
GET /api/v1/parcels/{lpi}/neighbours

Parcels adjacent to this one.

Parameters

Name Type Description
mode string Optional touching (default): parcels sharing a boundary. within: parcels within distance metres.
distance integer Optional For within mode: 1–5000 metres. Defaults to 100.
limit integer Optional Maximum parcels, 1–500. Defaults to 100.
include_status string Optional all to include retired records.
Request
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/parcels/C01500020000392900000/neighbours?mode=within&distance=250"
GET /beacons · /general-plan · /within · /coverage
Endpoint What it returns
GET /{lpi}/beacons Surveyed beacon points on the parcel's boundary, ordered around the ring, each with its position along the boundary (ring_position, 0–1). An empty list means no beacons are captured for this parcel in the export.
GET /{lpi}/general-plan The General Plan figure(s) covering the parcel, each with its SG number (e.g. F750/1969) — the document reference a conveyancer requests.
GET /within Parcels intersecting a bounding box (min_lat, min_lng, max_lat, max_lng), cursor paginated. The box may span at most 0.05° (~5.5 km) per side — page a suburb, not a province.
GET /coverage The load ledger: per province and per layer, the record count, the export it came from (dataset_version), when it was loaded and how many geometries needed repair on ingest. This is the authoritative statement of what the API holds.
POST /api/v1/parcels/batch

Up to 1,000 lookups in one call. Send either points (each resolved to the most specific published parcel containing it) or lpis (each resolved to its current record). Results come back in input order, with null where nothing matched.

Request
curl -X POST -H "X-API-KEY: YOUR_API_KEY_HERE" -H "Content-Type: application/json" \
  -d '{"points": [{"latitude": -31.466380, "longitude": 19.758913}, {"latitude": -30.323000, "longitude": 17.266000}]}' \
  "https://api.starlightsoftware.co.bw/api/v1/parcels/batch"
200 Response (result object abridged)
{
  "results": [
    {
      "id": 1012,
      "parcel_type": "ERF",
      "province": "NORTHERN CAPE",
      "source_key": "N065C015000200003929000001",
      "dataset_version": "csg-2026-07-18",
      "lpi": "C01500020000392900000",
      "parcel_number": 3929,
      "…": "…"
    },
    null
  ],
  "found": 1,
  "not_found": 1,
  "dataset_version": "csg-2026-07-18",
  "message": "Resolved 1 of 2 items"
}
points[0] → resolved to its parcelpoints[1] → null: no parcel contains itresults keep your input order
Parcel boundarySubmitted pointTwo points in, two results out — one resolved, one null

Rate Limits

Your key's per-minute allowance is shared across all PlotFinder products and is agreed when the key is issued (60 requests/minute by default). Every response carries the standard headers:

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

Exceeding it answers 429 RateLimitExceeded. A batch call counts as one request against the limit, which is what batch is for.

Errors

Errors share one JSON shape. The requestId is echoed from the X-Request-Id header (yours if you sent one) — include it when reporting a problem.

Error shape
{
  "error": "ProductNotPermitted",
  "message": "This API key covers the Plot/Coordinate API only. Contact PlotFinder to add the Parcel Intelligence API.",
  "requestId": "78be60d3-4d29-4b30-a1c4-6f9f2e7f8f21"
}
Status error Meaning
200 Success — including a search that finds nothing (an empty parcels array).
400 ValidationError A parameter is missing or malformed; details lists every field at fault.
401 ApiKeyMissing / ApiKeyInvalid / ApiKeySuspended / ApiKeyExpired The credential is absent, wrong, suspended or lapsed — each named so you know which.
403 ProductNotPermitted The key is valid but was not sold Parcel Intelligence.
403 CountryNotPermitted The key is scoped to Botswana; Parcel Intelligence data is South African.
404 NoParcelAtPoint No parcel contains the point. details.nearest carries the closest parcel within 2 km, when one exists.
404 ParcelNotFound No parcel carries the requested LPI.
429 RateLimitExceeded Per-minute allowance exhausted; retry after reset seconds.
503 ParcelDataUnavailable The parcel dataset is not installed on this deployment. You should never see this in production.

Questions?

Email info@starlightsoftware.co.bw. If you are reporting a problem with a specific request, include the requestId from the response.

Request API Access