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.
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.
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.
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.
|
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \ "https://api.starlightsoftware.co.bw/api/v1/parcels/at?latitude=-31.466380&longitude=19.758913"
{
"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"
}
{
"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.
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.
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.
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. |
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \ "https://api.starlightsoftware.co.bw/api/v1/parcels/C01500020000392900000/geometry"
{
"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"
}
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. |
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \ "https://api.starlightsoftware.co.bw/api/v1/parcels/C01500020000392900000/neighbours?mode=within&distance=250"
Attribute search, cursor-paginated. At least one of
province, minor_code or
township is required.
Parameters
| Name | Type | Description | |
|---|---|---|---|
province |
string | Optional |
Code or full name: EC, FS,
GP, KZN, LP,
MP, NC, NW,
WC.
|
township |
string | Optional |
Township / minor region name, e.g.
CALVINIA.
|
minor_code |
string | Optional |
The cadastre's township code, e.g.
C0150002.
|
parcel_number |
integer | Optional | The erf / parcel number. |
portion |
integer | Optional | Portion number; 0 is the remainder. |
parcel_type |
string | Optional |
One layer only — values as on
/at.
|
min_extent_sqm /
max_extent_sqm
|
integer | Optional | Extent bounds in square metres. |
limit |
integer | Optional | Page size, 1–500. Defaults to 100. |
cursor |
integer | Optional |
The next_cursor from the previous page.
Omit for the first page.
|
include_status |
string | Optional | all to include retired records. |
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \ "https://api.starlightsoftware.co.bw/api/v1/parcels/search?province=NC&township=CALVINIA&parcel_number=3929"
| 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.
|
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.
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"
{
"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"
}
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:
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": "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.