Overview
The Encumbrance API answers "what restricts this property?" It intersects a land parcel — or a point, an area, or a route — against the surveyed servitude, lease, mineral-rights, state-land and proclamation layers of the national cadastre, and reports every encumbrance it touches: the kind of right, its surveyed reference, the overlapping geometry, the overlap in square metres and as a share of the parcel, and whether the encumbrance crosses, sits within, contains or merely touches the parcel.
It sits alongside the Parcel Intelligence API: where that product answers "what is this parcel?", this one answers what burdens it. The two share the same parcel identifiers, so an LPI from one drops straight into the other.
| Use case | How the API helps |
|---|---|
| Mortgage & conveyancing due diligence | One call per parcel returns every surveyed encumbrance on it, with overlap figures a valuer can defend — or an explicit statement that none was found in the dataset. |
| Utility & fibre route screening | POST /intersect takes a corridor up to 200 km and
reports every parcel and encumbrance it crosses, with the length of
route inside each. |
| Mining-rights work | Mineral-rights areas are a first-class layer: find them at a point, within an area, or overlapping a specific parcel. |
| Portfolio screening | POST /batch summarises up to 500 parcels in one
request; /summary gives the cheap yes/no before the
full detail. |
Authentication
Every request carries your API key in the X-API-KEY header. Keys
are issued per contract by PlotFinder and carry the encumbrance
product entitlement; a PlotFinder key for another product answers
403 ProductNotPermitted until the product is added to it.
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \ "https://api.starlightsoftware.co.bw/api/v1/encumbrances/coverage"
https://api.starlightsoftware.co.bw/api/v1/encumbrances
Response conventions
- Lists arrive under a top-level
encumbranceskey, never a bare array. - Every response carries a human-readable
message. An empty result says so explicitly — it reports that nothing was found in the dataset, and never asserts that a property is unencumbered. dataset_versionnames the source export the answer came from (e.g.csg-2026-07-18);as_ofis that export's run date. Data is refreshed on the source's weekly export cycle.- Absent fields are omitted, never null — except
overlap_sqmon an undimensioned servitude line, which is an explicitnullwith anote. relationshipis one ofcrosses|within|contains|touches—touchesmeans boundary-only contact and reportsoverlap_sqm: 0.- Every response carries an
X-Request-Idheader; error bodies repeat it asrequestId. Quote it when reporting a problem.
Rate limits
Limits are per key, per minute, set by your contract. Responses carry the IETF draft standard headers:
RateLimit-Policy: 60;w=60 RateLimit: limit=60, remaining=57, reset=41
Exceeding the limit answers 429
RateLimitExceeded with a Retry-After header. The
/summary/parcel and /batch endpoints exist for
screening at volume — they are priced below the full intersection endpoints;
ask about tier pricing.
The type vocabulary — ease_type_normalised
The source's type codes are free text that varies by province. Every raw
value maps to one controlled vocabulary word, and the raw value always
travels alongside as ease_type — nothing is ever guessed into
a category.
Records the source marks as cancelled, withdrawn, historic or pending carry
a record_status and are excluded from all intersection
results. They remain reachable through /search with
record_status=any.
ease_type_normalised | Meaning | Raw source values that map to it |
|---|---|---|
right_of_way | Registered right of way over another property. | RIGHT OF WAY, ROW |
road | Road servitude or road reserve. | ROAD |
railway | Railway servitude. | RAILWAY |
access | Access servitude, including common passages. | ACCESS, COMM_PASS |
pipeline | Pipeline servitude (fuel, gas, bulk water). | PIPE, PIPELINE |
powerline | Electricity transmission or distribution servitude. | ELECTRIC |
water | Water servitude — abstraction, dams, boreholes, irrigation furrows. | BOREHOLE, DAM, FURROW, WATER |
sewer | Sewerage servitude. | SEWER |
drainage | Stormwater and drainage servitudes. | DRAINAGE, STORM, STORMWATER |
telecom | Telecommunications servitude. (None present in the current dataset.) | — |
support | Lateral or party-wall support servitude. | COMM_WALL |
municipal_services | Municipal or general services servitude. | MUNICIPAL, SERVICES |
habitatio | Personal servitude of habitatio. (None present in the current dataset.) | — |
usufruct | Personal servitude of usufruct. (None present in the current dataset.) | — |
mineral | Mineral rights area. | MINERAL |
lease | Registered lease area. | LEASE |
state_land | Unalienated state land. | (the state_land layer itself) |
river_bed | Unalienated river bed. | (the river_bed layer itself) |
proclamation | Proclamation area or proclamation line. | PROC_AREA, PROC_LINE |
established_boundary | Boundary established by survey or court order. | EST_BND |
unspecified | A servitude whose kind the source diagram does not state. This is the most common value, covering roughly 77% of servitude areas. | (blank), AREA, CANCELLED, GENERAL, HIST, LINE, PRE_APP, RURAL, Servitude Area, URBAN, WITHDRAWN |
other | A specific type that has no vocabulary word of its own yet; the raw value always travels alongside as `ease_type`. | DWS_COAST, HWM, INT_BND, OUTSPAN, PROV, RESERVE |
Coverage caveats, by province
Coverage follows what the source publishes, and not every layer is published
for every province. GET /coverage reports live counts and
the known gaps for the active dataset; the standing caveats in the current
export (csg-2026-07-18) are:
| Province | Caveat |
|---|---|
| KwaZulu-Natal (KZ) | No lease layer in this export. KZ also uses a distinct deeds registry history; servitude coverage is dense (158k areas) but lease screening must go to the Deeds Office. |
| Gauteng (GP) | No state_land layer in this export. |
| EC, KZ, WC only | river_bed (unalienated river beds) exists only in these three provinces' exports. |
| NC, NW | No established_boundary layer in this export. |
| All provinces | About 77% of servitude areas do not state the kind of servitude; these are served as unspecified. |
Worked examples
1 · Mortgage due diligence — screen, then inspect
A lender screening a security property starts cheap: does anything touch this erf at all?
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \ "https://api.starlightsoftware.co.bw/api/v1/encumbrances/summary/parcel/T0IQ00000000019400285"
const res = await fetch(
"https://api.starlightsoftware.co.bw/api/v1/encumbrances/summary/parcel/T0IQ00000000019400285",
{ headers: { "X-API-KEY": process.env.PLOTFINDER_API_KEY } },
);
const summary = await res.json();
if (summary.has_encumbrances) {
// pull the full picture only for the flagged parcels
}
{
"lpi": "T0IQ00000000019400285",
"province": "GP",
"has_encumbrances": true,
"counts_by_class": { "servitude_area": 9, "servitude_line": 6 },
"classes": ["servitude_area", "servitude_line"],
"highest_impact_pct": 1.52,
"as_of": "2026-07-18",
"dataset_version": "csg-2026-07-18",
"message": "Encumbrance summary for this parcel"
}
Flagged parcels get the full intersection, with geometry:
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \ "https://api.starlightsoftware.co.bw/api/v1/encumbrances/parcel/T0IQ00000000019400285"
{
"encumbrances": [
{
"encumbrance_uid": 49752,
"encumbrance_class": "servitude_area",
"ease_type_normalised": "unspecified",
"ease_type": "URBAN",
"sg_number": "4452/1997001",
"province": "GP",
"extent_sqm": 3197.6,
"date_stamp": "2019-05-30",
"relationship": "within",
"overlap_sqm": 3196.8,
"overlap_pct_of_parcel": 1.52,
"geometry": { "type": "MultiPolygon", "coordinates": [[ "…" ]] }
},
{
"encumbrance_uid": 119289,
"encumbrance_class": "servitude_line",
"ease_type_normalised": "unspecified",
"ease_type": "URBAN",
"sg_number": "4455/1997001",
"province": "GP",
"date_stamp": "2019-06-28",
"relationship": "within",
"overlap_sqm": null,
"overlap_m": 76.3,
"note": "undimensioned servitude line — extent not defined on diagram",
"geometry": { "type": "LineString", "coordinates": [[ "…" ]] }
}
],
"parcel": { "lpi": "T0IQ00000000019400285", "parcel_type": "FARM_PORTION", "province": "GP", "area_sqm": 210895.7 },
"message": "Encumbrances intersecting this parcel",
"as_of": "2026-07-18",
"dataset_version": "csg-2026-07-18"
}
And when nothing is found, the body says exactly what that means:
{
"encumbrances": [],
"parcel": { "lpi": "C01600030000109400000", "parcel_type": "ERF", "province": "WC", "area_sqm": 890.4 },
"message": "No encumbrances found in the cadastral dataset for this parcel. This is not a title-deed search and does not constitute confirmation that the property is unencumbered.",
"as_of": "2026-07-18",
"dataset_version": "csg-2026-07-18"
}
2 · Fibre route screening — a corridor in one call
POST the proposed route as a GeoJSON LineString (up to 200 km) and get every encumbrance it meets and every parcel it enters, each with the overlap length along your route.
curl -X POST -H "X-API-KEY: YOUR_API_KEY_HERE" -H "Content-Type: application/json" \
-d '{
"geometry": {
"type": "LineString",
"coordinates": [[28.030, -26.180], [28.052, -26.171], [28.081, -26.158]]
}
}' \
"https://api.starlightsoftware.co.bw/api/v1/encumbrances/intersect"
const res = await fetch(
"https://api.starlightsoftware.co.bw/api/v1/encumbrances/intersect",
{
method: "POST",
headers: {
"X-API-KEY": process.env.PLOTFINDER_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ geometry: routeLineString }),
},
);
const { encumbrances, parcels } = await res.json();
const wayleavesNeeded = parcels.filter((p) => p.overlap_m > 0);
{
"encumbrances": [
{
"encumbrance_uid": 62696,
"encumbrance_class": "servitude_area",
"ease_type_normalised": "unspecified",
"ease_type": "URBAN",
"sg_number": "3032/2006002",
"province": "GP",
"extent_sqm": 934.2,
"date_stamp": "2009-03-20",
"relationship": "crosses",
"overlap_m": 21.7,
"geometry": { "type": "Polygon", "coordinates": [[ "…" ]] }
}
],
"parcels": [
{ "lpi": "T0IR00000000005300035", "parcel_type": "FARM_PORTION", "province": "GP", "extent_sqm": 3376604.6, "overlap_m": 2143.8 },
{ "lpi": "T0IR00000000005800008", "parcel_type": "FARM_PORTION", "province": "GP", "extent_sqm": 802020.0, "overlap_m": 938.3 }
],
"message": "Encumbrances and affected parcels along the supplied geometry",
"as_of": "2026-07-18",
"dataset_version": "csg-2026-07-18"
}
3 · Mining right overlap — who else holds ground here?
Mining and prospecting work starts from the surveyed mineral-rights areas. Filter them by province, then pull one with its affected parcels.
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \ "https://api.starlightsoftware.co.bw/api/v1/encumbrances/search?encumbrance_class=mineral_right&province=NW&limit=50"
const search = await fetch(
"https://api.starlightsoftware.co.bw/api/v1/encumbrances/search?" +
new URLSearchParams({ encumbrance_class: "mineral_right", province: "NW", limit: "50" }),
{ headers: { "X-API-KEY": process.env.PLOTFINDER_API_KEY } },
).then((r) => r.json());
const detail = await fetch(
`https://api.starlightsoftware.co.bw/api/v1/encumbrances/${search.encumbrances[0].encumbrance_uid}`,
{ headers: { "X-API-KEY": process.env.PLOTFINDER_API_KEY } },
).then((r) => r.json());
// detail.affected_parcels lists every parcel the right overlies, with overlap_sqm
{
"encumbrance": {
"encumbrance_uid": 401383,
"encumbrance_class": "mineral_right",
"ease_type_normalised": "mineral",
"ease_type": "MINERAL",
"sg_number": "10450/1997001",
"province": "NW",
"extent_sqm": 129333.0,
"date_stamp": "2017-04-07",
"geometry": { "type": "Polygon", "coordinates": [[ "…" ]] }
},
"affected_parcels": [
{ "lpi": "T0JQ00000000030600082", "parcel_type": "FARM_PORTION", "province": "NW", "overlap_sqm": 129330.6 },
{ "lpi": "T0JQ00000000030600033", "parcel_type": "FARM_PORTION", "province": "NW", "overlap_sqm": 0.0 },
{ "lpi": "T0JQ00000000030600034", "parcel_type": "FARM_PORTION", "province": "NW", "overlap_sqm": 0.0 }
],
"as_of": "2026-07-18",
"dataset_version": "csg-2026-07-18",
"message": "Encumbrance 401383"
}
Endpoint reference
The machine-readable versions are at
/encumbrance/openapi.json and
/encumbrance/postman.json.
GET /api/v1/encumbrances/parcel/{lpi}
Everything that restricts a parcel
All encumbrances intersecting the parcel with this LPI: class, raw and normalised type, SG number, the intersecting geometry, overlap in m² and as a share of the parcel, and the relationship (crosses | within | contains | touches).
| Parameter | In | Required | Description |
|---|---|---|---|
lpi | path | yes |
GET /api/v1/encumbrances/summary/parcel/{lpi}
Cheap screening summary for one parcel
Boolean-ish screening at volume: does anything touch this parcel, how many per class, and the largest single overlap as a percentage of the parcel. Priced below the full /parcel endpoint.
| Parameter | In | Required | Description |
|---|---|---|---|
lpi | path | yes |
GET /api/v1/encumbrances/at
Encumbrances at a point
| Parameter | In | Required | Description |
|---|---|---|---|
latitude | query | no | |
longitude | query | no | |
radius_m | query | no |
GET /api/v1/encumbrances/within
Encumbrances in a bounding box
| Parameter | In | Required | Description |
|---|---|---|---|
bbox | query | yes | minlon,minlat,maxlon,maxlat |
encumbrance_class | query | no | |
limit | query | no | |
cursor | query | no |
POST /api/v1/encumbrances/intersect
Corridor screening
POST a GeoJSON LineString or Polygon — a proposed fibre route, pipeline or road — and get every encumbrance and every affected parcel along it, with per-parcel overlap length or area. Lines up to 200 km; polygons up to 2,500 km².
Send a JSON body; see the worked examples above for the shape.
GET /api/v1/encumbrances/search
Filter encumbrances by attribute
| Parameter | In | Required | Description |
|---|---|---|---|
province | query | no | |
encumbrance_class | query | no | |
ease_type | query | no | |
ease_type_normalised | query | no | |
sg_number | query | no | exact, or * as wildcard |
record_status | query | no | |
bbox | query | no | |
limit | query | no | |
cursor | query | no |
POST /api/v1/encumbrances/batch
Screen up to 500 parcels in one call
Items are {lpi} or {latitude, longitude}; results align positionally, null where no parcel was found.
Send a JSON body; see the worked examples above for the shape.
GET /api/v1/encumbrances/coverage
What is loaded, per province and class
GET /api/v1/encumbrances/heatmap
Coverage-map data: density cells or class-keyed boundaries
Drives coverage maps, including the PlotFinder admin panel's Encumbrance layer. The server picks the answer from the viewport: density cells (whole multiples of 0.01°, busiest-first, capped at 4,000 with an explicit truncation flag) when zoomed out, actual encumbrance geometries — scale-gated per class — when zoomed in or when view=boundaries.
| Parameter | In | Required | Description |
|---|---|---|---|
north | query | no | |
south | query | no | |
east | query | no | |
west | query | no | |
view | query | no | |
classes | query | no | comma list; default all |
GET /api/v1/encumbrances/tiles/{z}/{x}/{y}.mvt
Vector tiles, colour-keyed by class
| Parameter | In | Required | Description |
|---|---|---|---|
z | path | yes | |
x | path | yes | |
y | path | yes |
GET /api/v1/encumbrances/{encumbrance_uid}
One encumbrance, with affected parcels
| Parameter | In | Required | Description |
|---|---|---|---|
encumbrance_uid | path | yes |
Error codes
Errors share one flat envelope; details appears when there is something structured to say:
{
"error": "ValidationError",
"message": "Invalid query parameters",
"details": [{ "field": "latitude", "message": "latitude is required" }],
"requestId": "78be60d3-8000-4601-9fda-7d5e6838388d"
}
| Status | error | Meaning |
|---|---|---|
| 400 | ValidationError | A parameter or body failed validation; see details. |
| 401 | ApiKeyMissing / ApiKeyInvalid / ApiKeySuspended / ApiKeyExpired | The X-API-KEY header is absent, wrong, suspended, or past its expiry. |
| 403 | ProductNotPermitted | The key does not carry the Encumbrance API product. |
| 403 | CountryNotPermitted | The key is scoped to Botswana only; this product serves South Africa. |
| 404 | ParcelNotFound | No parcel carries the requested LPI. |
| 404 | EncumbranceNotFound | No encumbrance carries the requested id. |
| 429 | RateLimitExceeded | Per-minute limit reached; honour Retry-After. |
| 503 | EncumbranceDataUnavailable | The dataset is not installed on this deployment yet. |