PlotFinder — Encumbrance API

What restricts this property?

Servitudes, leases, mineral rights, state-land status and proclamation areas that burden or touch a South African land parcel — intersected on demand from the national cadastral spatial dataset, for mortgage due diligence, utility corridor screening and mining-rights work.

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 caseHow 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.

Request
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/encumbrances/coverage"
Base URL
https://api.starlightsoftware.co.bw/api/v1/encumbrances

Response conventions

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. 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_normalisedMeaningRaw source values that map to it
right_of_wayRegistered right of way over another property.RIGHT OF WAY, ROW
roadRoad servitude or road reserve.ROAD
railwayRailway servitude.RAILWAY
accessAccess servitude, including common passages.ACCESS, COMM_PASS
pipelinePipeline servitude (fuel, gas, bulk water).PIPE, PIPELINE
powerlineElectricity transmission or distribution servitude.ELECTRIC
waterWater servitude — abstraction, dams, boreholes, irrigation furrows.BOREHOLE, DAM, FURROW, WATER
sewerSewerage servitude.SEWER
drainageStormwater and drainage servitudes.DRAINAGE, STORM, STORMWATER
telecomTelecommunications servitude. (None present in the current dataset.)
supportLateral or party-wall support servitude.COMM_WALL
municipal_servicesMunicipal or general services servitude.MUNICIPAL, SERVICES
habitatioPersonal servitude of habitatio. (None present in the current dataset.)
usufructPersonal servitude of usufruct. (None present in the current dataset.)
mineralMineral rights area.MINERAL
leaseRegistered lease area.LEASE
state_landUnalienated state land.(the state_land layer itself)
river_bedUnalienated river bed.(the river_bed layer itself)
proclamationProclamation area or proclamation line.PROC_AREA, PROC_LINE
established_boundaryBoundary established by survey or court order.EST_BND
unspecifiedA 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
otherA 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:

ProvinceCaveat
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 onlyriver_bed (unalienated river beds) exists only in these three provinces' exports.
NC, NWNo established_boundary layer in this export.
All provincesAbout 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?

Request — cURL
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/encumbrances/summary/parcel/T0IQ00000000019400285"
Request — JavaScript
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
}
200 Response
{
  "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"
}
T0IQ00000000019400285 · GP9 servitude areas6 servitude linesHighest impact on the parcel1.52%
One cheap call answers: is anything here at all, and how much of the parcel does it touch?

Flagged parcels get the full intersection, with geometry:

Request — cURL
curl -H "X-API-KEY: YOUR_API_KEY_HERE" \
  "https://api.starlightsoftware.co.bw/api/v1/encumbrances/parcel/T0IQ00000000019400285"
200 Response (abridged — 15 encumbrances in full)
{
  "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"
}
T0IQ00000000019400285500 m
ParcelServitude areaServitude line15 encumbrances on this parcel · highest impact 1.52% of its area

And when nothing is found, the body says exactly what that means:

200 Response — no results
{
  "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"
}
C0160003000010940000025 m
ParcelThe parcel alone — no surveyed encumbrance intersects it in the current dataset

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.

Request — cURL
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"
Request — JavaScript
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);
200 Response (abridged — 13 encumbrances, 113 parcels in full)
{
  "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"
}
1 km
Proposed routeServitude areaServitude line13 encumbrances met and 113 parcels entered along this 5.6 km route

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.

Request — cURL
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"
Request — JavaScript
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
200 Response (abridged)
{
  "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"
}
SG 10450/1997001250 m
Mineral rights areaA 129 333 m² mineral rights area · 3 farm portions affected

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).

ParameterInRequiredDescription
lpipathyes

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.

ParameterInRequiredDescription
lpipathyes

GET /api/v1/encumbrances/at

Encumbrances at a point

ParameterInRequiredDescription
latitudequeryno
longitudequeryno
radius_mqueryno

GET /api/v1/encumbrances/within

Encumbrances in a bounding box

ParameterInRequiredDescription
bboxqueryyesminlon,minlat,maxlon,maxlat
encumbrance_classqueryno
limitqueryno
cursorqueryno

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.

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.

ParameterInRequiredDescription
northqueryno
southqueryno
eastqueryno
westqueryno
viewqueryno
classesquerynocomma list; default all

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

Vector tiles, colour-keyed by class

ParameterInRequiredDescription
zpathyes
xpathyes
ypathyes

GET /api/v1/encumbrances/{encumbrance_uid}

One encumbrance, with affected parcels

ParameterInRequiredDescription
encumbrance_uidpathyes

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": "latitude", "message": "latitude 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 Encumbrance API product.
403CountryNotPermittedThe key is scoped to Botswana only; this product serves South Africa.
404ParcelNotFoundNo parcel carries the requested LPI.
404EncumbranceNotFoundNo encumbrance carries the requested id.
429RateLimitExceededPer-minute limit reached; honour Retry-After.
503EncumbranceDataUnavailableThe dataset is not installed on this deployment yet.