DeveloperAPITypeScriptSDKNode.js

By Township Canada

Adding Legal Land Description Search to a Node.js App: @townshipcanada/sdk Walkthrough

Install the townshipcanada npm package and add DLS, NTS, and LSD search to a Node.js application with typed methods for single lookups, batch conversion, and boundary retrieval.

You have a Node.js application that needs to resolve Canadian legal land descriptions to GPS coordinates. Maybe it is a field dispatch tool that routes crews to well sites identified by DLS locations. Maybe it is an internal dashboard that maps quarter sections for a farmland portfolio. The location data is in your database as strings like NW-36-42-3-W5 and 06-32-048-07W5, and your application needs latitude, longitude, and parcel boundaries.

The townshipcanada npm package handles that conversion. It wraps the Township Canada API with typed methods for single lookups, batch conversion, reverse geocoding, and boundary retrieval. Instead of writing fetch calls, parsing GeoJSON responses, and managing chunking logic, you call search() or batchSearch() and get structured results back.

This walkthrough covers the full path: installing the SDK, authenticating, running your first search, converting locations in bulk, and retrieving parcel boundary polygons.

Install and authenticate

The package requires Node.js 18 or later (it uses native fetch). It also works in Bun, Deno, and Cloudflare Workers.

npm install townshipcanada

Create a client instance with your API key. You can generate a key from the API portal after creating an account (see API key management for details on separating dev, staging, and production keys).

import { TownshipClient } from "townshipcanada";

const client = new TownshipClient({
  apiKey: process.env.TOWNSHIP_API_KEY
});

The constructor also accepts baseUrl (defaults to the production endpoint), timeout (defaults to 30 seconds), and a custom fetch implementation if your environment needs one.

The search() method converts one legal land description to GPS coordinates. It accepts DLS, LSD, NTS, Geographic Township, and Federal Permit System formats in a single call.

const result = await client.search("NW-36-42-3-W5");

console.log(result.latitude); // 52.123456
console.log(result.longitude); // -114.654321
console.log(result.province); // "Alberta"
console.log(result.surveySystem); // "DLS"
console.log(result.unit); // "Quarter Section"

The response is typed as a SearchResult. Your editor knows every field before you run the code: legalLocation, latitude, longitude, province, surveySystem, unit, boundary, and raw (the full GeoJSON features array).

A few input variations that all resolve correctly:

await client.search("NW-36-42-3-W5"); // Quarter section (DLS)
await client.search("10-36-42-3-W5"); // LSD (Legal Subdivision)
await client.search("36-42-3-W5"); // Full section
await client.search("A-2-F/93-P-8"); // NTS quarter unit (BC)
await client.search("Lot 2 Con 4 Osprey"); // Ontario geographic township

If the description does not resolve, the SDK throws a NotFoundError rather than returning null. This matters for pipelines: a silent failure means bad data downstream. An exception means you handle it explicitly.

Convert locations in bulk with batchSearch

Single lookups work for interactive search and on-demand resolution. For batch jobs (importing a well list, processing regulatory filings, geocoding a lease portfolio), batchSearch() converts up to thousands of descriptions in a single call.

const locations = [
  "06-32-048-07W5",
  "NE-14-032-21W4",
  "NW-25-024-01W5",
  "A-2-F/93-P-8"
  // ... hundreds more
];

const batch = await client.batchSearch(locations);

console.log(batch.total); // number of descriptions submitted
console.log(batch.success); // successful conversions
console.log(batch.failed); // descriptions that did not resolve

for (const result of batch.results) {
  console.log(result.legalLocation, result.latitude, result.longitude);
}

The Batch API accepts a maximum of 100 descriptions per request. The SDK handles chunking automatically: pass 500 locations and it sends five sequential requests, then returns a single BatchResult with all results merged. You can adjust the chunk size with { chunkSize: 50 } if you want smaller batches.

This is the scenario where the SDK saves the most work compared to raw HTTP calls. Without it, you write the chunking loop, manage request sequencing, aggregate partial results, and handle failures mid-batch. With batchSearch(), one method call covers all of that.

A practical example: Petrinex well data to KML

An O&G data team exports 500 well records from a Petrinex production report. Each row has a UWI like 100/06-32-048-07W5/00. They need GPS coordinates for every well site to generate a KML file for ArcGIS.

The UWI encodes the DLS location between the two slashes. Extract it, then batch-convert:

import { TownshipClient } from "townshipcanada";

const client = new TownshipClient({
  apiKey: process.env.TOWNSHIP_API_KEY
});

// Extract DLS from UWIs: "100/06-32-048-07W5/00" → "06-32-048-07W5"
const uwis = getWellRecords(); // your data source
const locations = uwis.map((uwi) => uwi.split("/")[1]);

const batch = await client.batchSearch(locations);

// batch.results now has GPS coordinates for each LSD
for (const result of batch.results) {
  console.log(`${result.legalLocation}: ${result.latitude}, ${result.longitude}`);
}

// Feed batch.results into your KML generator

The batchSearch() call handles the full 500-location list. The data team gets structured results with latitude, longitude, province, and surveySystem for each record, ready for KML export or any other format their GIS tooling expects.

For more on UWI parsing and the edge cases around directional wells and NTS-format identifiers, see UWI to GPS: The O&G Developer's Guide.

Retrieve parcel boundary polygons

Beyond centre-point coordinates, many applications need the parcel boundary itself: a quarter-section outline on a map, a polygon for spatial queries, or a GeoJSON feature for a data export. The boundary() method returns the polygon directly.

const polygon = await client.boundary("06-32-048-07W5");

if (polygon) {
  console.log(polygon.type); // "Polygon"
  console.log(polygon.coordinates); // [[[lng, lat], [lng, lat], ...]]
}

The return type is GeoJSONPolygon | GeoJSONMultiPolygon | null. A standard quarter section returns a simple polygon. Parcels that span survey grid irregularities (correction lines, lake boundaries) may return a multi-polygon.

If you need both the coordinates and the full GeoJSON feature collection (including metadata properties), use raw() instead:

const fc = await client.raw("NW-36-42-3-W5");
// fc.type → "FeatureCollection"
// fc.features → LocationFeature[]

Handle errors with typed exceptions

API errors are typed classes, not generic HTTP status codes. This makes error handling in a Node.js application straightforward:

import {
  TownshipClient,
  AuthenticationError,
  NotFoundError,
  RateLimitError,
  ValidationError
} from "townshipcanada";

try {
  const result = await client.search("NW-36-42-3-W5");
} catch (error) {
  if (error instanceof NotFoundError) {
    // The description did not match any known parcel
  } else if (error instanceof RateLimitError) {
    // You have exceeded your plan's request quota
  } else if (error instanceof AuthenticationError) {
    // The API key is missing or invalid
  } else if (error instanceof ValidationError) {
    // The input was malformed
  }
}

Five error classes cover the full API surface: AuthenticationError (401), ValidationError (400), NotFoundError (404), RateLimitError (429), and PayloadTooLargeError (413). Each carries the HTTP status code and the response message, so you can log the specifics without parsing a response body.

What you can build from here

With search(), batchSearch(), boundary(), reverse(), autocomplete(), and typed error handling, the SDK covers the full Township Canada API surface. A few directions this opens up:

Type-ahead search in your UI. The autocomplete() method returns suggestions as a user types a partial legal land description. Feed it into a search input and you have location autocomplete backed by the full DLS/NTS/FPS grid. See the Autocomplete API guide for the endpoint details.

Reverse geocoding. The reverse() method converts GPS coordinates back to a legal land description. Useful when field crews collect GPS readings and need the corresponding LSD or quarter section for a regulatory filing.

Map overlays. Combine boundary() with a mapping library to draw parcel outlines on a map. There are integration guides for Google Maps, Mapbox GL JS, Leaflet, and OpenLayers.

For a broader look at who builds with the API and the common industry use cases, see Building with Canadian Land Data. For data warehouse teams, converting legal land descriptions in Snowflake SQL uses the same underlying API as an external function.

The SDK is MIT licensed and available on npm. API plans start with the Search Build tier. See the API pricing page for current rates.