[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-\u002Fblog\u002Fadding-legal-land-description-search-nodejs-sdk-walkthrough":3},{"id":4,"title":5,"author":6,"body":7,"category":1731,"cover":1732,"date":1733,"description":1734,"extension":1735,"meta":1736,"navigation":145,"path":1737,"seo":1738,"stem":1739,"tags":1740,"__hash__":1746},"blog\u002Fblog\u002Fadding-legal-land-description-search-nodejs-sdk-walkthrough.md","Adding Legal Land Description Search to a Node.js App: @townshipcanada\u002Fsdk Walkthrough","Township Canada",{"type":8,"value":9,"toc":1721},"minimark",[10,23,49,52,57,64,89,101,209,223,227,232,399,428,431,566,573,577,583,848,863,869,874,881,884,1170,1184,1191,1195,1202,1306,1313,1320,1364,1368,1371,1603,1625,1629,1645,1660,1668,1693,1706,1717],[11,12,13,14,18,19,22],"p",{},"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 ",[15,16,17],"code",{},"NW-36-42-3-W5"," and ",[15,20,21],{},"06-32-048-07W5",", and your application needs latitude, longitude, and parcel boundaries.",[11,24,25,26,35,36,40,41,44,45,48],{},"The ",[27,28,32],"a",{"href":29,"rel":30},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Ftownshipcanada",[31],"nofollow",[15,33,34],{},"townshipcanada"," npm package handles that conversion. It wraps the ",[27,37,39],{"href":38},"\u002Fapi","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 ",[15,42,43],{},"search()"," or ",[15,46,47],{},"batchSearch()"," and get structured results back.",[11,50,51],{},"This walkthrough covers the full path: installing the SDK, authenticating, running your first search, converting locations in bulk, and retrieving parcel boundary polygons.",[53,54,56],"h2",{"id":55},"install-and-authenticate","Install and authenticate",[11,58,59,60,63],{},"The package requires Node.js 18 or later (it uses native ",[15,61,62],{},"fetch","). It also works in Bun, Deno, and Cloudflare Workers.",[65,66,71],"pre",{"className":67,"code":68,"language":69,"meta":70,"style":70},"language-bash shiki shiki-themes material-theme-lighter vitesse-light vitesse-dark","npm install townshipcanada\n","bash","",[15,72,73],{"__ignoreMap":70},[74,75,78,82,86],"span",{"class":76,"line":77},"line",1,[74,79,81],{"class":80},"sYn-s","npm",[74,83,85],{"class":84},"sTbE_"," install",[74,87,88],{"class":84}," townshipcanada\n",[11,90,91,92,95,96,100],{},"Create a client instance with your API key. You can generate a key from the ",[27,93,94],{"href":38},"API portal"," after creating an account (see ",[27,97,99],{"href":98},"\u002Fguides\u002Fapi-key-management","API key management"," for details on separating dev, staging, and production keys).",[65,102,106],{"className":103,"code":104,"language":105,"meta":70,"style":70},"language-typescript shiki shiki-themes material-theme-lighter vitesse-light vitesse-dark","import { TownshipClient } from \"townshipcanada\";\n\nconst client = new TownshipClient({\n  apiKey: process.env.TOWNSHIP_API_KEY\n});\n","typescript",[15,107,108,140,147,174,198],{"__ignoreMap":70},[74,109,110,114,118,122,125,128,132,134,137],{"class":76,"line":77},[74,111,113],{"class":112},"siDh9","import",[74,115,117],{"class":116},"soVBu"," {",[74,119,121],{"class":120},"sSC40"," TownshipClient",[74,123,124],{"class":116}," }",[74,126,127],{"class":112}," from",[74,129,131],{"class":130},"sbYkP"," \"",[74,133,34],{"class":84},[74,135,136],{"class":130},"\"",[74,138,139],{"class":116},";\n",[74,141,143],{"class":76,"line":142},2,[74,144,146],{"emptyLinePlaceholder":145},true,"\n",[74,148,150,154,157,160,164,167,171],{"class":76,"line":149},3,[74,151,153],{"class":152},"s5Kfy","const",[74,155,156],{"class":120}," client",[74,158,159],{"class":116}," =",[74,161,163],{"class":162},"sVsLi"," new",[74,165,121],{"class":166},"sljsM",[74,168,170],{"class":169},"snCua","(",[74,172,173],{"class":116},"{\n",[74,175,177,181,184,187,190,193,195],{"class":76,"line":176},4,[74,178,180],{"class":179},"suXOh","  apiKey",[74,182,183],{"class":116},":",[74,185,186],{"class":120}," process",[74,188,189],{"class":116},".",[74,191,192],{"class":120},"env",[74,194,189],{"class":116},[74,196,197],{"class":120},"TOWNSHIP_API_KEY\n",[74,199,201,204,207],{"class":76,"line":200},5,[74,202,203],{"class":116},"}",[74,205,206],{"class":169},")",[74,208,139],{"class":116},[11,210,211,212,215,216,219,220,222],{},"The constructor also accepts ",[15,213,214],{},"baseUrl"," (defaults to the production endpoint), ",[15,217,218],{},"timeout"," (defaults to 30 seconds), and a custom ",[15,221,62],{}," implementation if your environment needs one.",[53,224,226],{"id":225},"look-up-a-single-legal-land-description","Look up a single legal land description",[11,228,25,229,231],{},[15,230,43],{}," 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.",[65,233,235],{"className":103,"code":234,"language":105,"meta":70,"style":70},"const result = await client.search(\"NW-36-42-3-W5\");\n\nconsole.log(result.latitude); \u002F\u002F 52.123456\nconsole.log(result.longitude); \u002F\u002F -114.654321\nconsole.log(result.province); \u002F\u002F \"Alberta\"\nconsole.log(result.surveySystem); \u002F\u002F \"DLS\"\nconsole.log(result.unit); \u002F\u002F \"Quarter Section\"\n",[15,236,237,268,272,301,325,349,374],{"__ignoreMap":70},[74,238,239,241,244,246,249,251,253,256,258,260,262,264,266],{"class":76,"line":77},[74,240,153],{"class":152},[74,242,243],{"class":120}," result",[74,245,159],{"class":116},[74,247,248],{"class":112}," await",[74,250,156],{"class":120},[74,252,189],{"class":116},[74,254,255],{"class":166},"search",[74,257,170],{"class":169},[74,259,136],{"class":130},[74,261,17],{"class":84},[74,263,136],{"class":130},[74,265,206],{"class":169},[74,267,139],{"class":116},[74,269,270],{"class":76,"line":142},[74,271,146],{"emptyLinePlaceholder":145},[74,273,274,277,279,282,284,287,289,292,294,297],{"class":76,"line":149},[74,275,276],{"class":120},"console",[74,278,189],{"class":116},[74,280,281],{"class":166},"log",[74,283,170],{"class":169},[74,285,286],{"class":120},"result",[74,288,189],{"class":116},[74,290,291],{"class":120},"latitude",[74,293,206],{"class":169},[74,295,296],{"class":116},";",[74,298,300],{"class":299},"s9Tkl"," \u002F\u002F 52.123456\n",[74,302,303,305,307,309,311,313,315,318,320,322],{"class":76,"line":176},[74,304,276],{"class":120},[74,306,189],{"class":116},[74,308,281],{"class":166},[74,310,170],{"class":169},[74,312,286],{"class":120},[74,314,189],{"class":116},[74,316,317],{"class":120},"longitude",[74,319,206],{"class":169},[74,321,296],{"class":116},[74,323,324],{"class":299}," \u002F\u002F -114.654321\n",[74,326,327,329,331,333,335,337,339,342,344,346],{"class":76,"line":200},[74,328,276],{"class":120},[74,330,189],{"class":116},[74,332,281],{"class":166},[74,334,170],{"class":169},[74,336,286],{"class":120},[74,338,189],{"class":116},[74,340,341],{"class":120},"province",[74,343,206],{"class":169},[74,345,296],{"class":116},[74,347,348],{"class":299}," \u002F\u002F \"Alberta\"\n",[74,350,352,354,356,358,360,362,364,367,369,371],{"class":76,"line":351},6,[74,353,276],{"class":120},[74,355,189],{"class":116},[74,357,281],{"class":166},[74,359,170],{"class":169},[74,361,286],{"class":120},[74,363,189],{"class":116},[74,365,366],{"class":120},"surveySystem",[74,368,206],{"class":169},[74,370,296],{"class":116},[74,372,373],{"class":299}," \u002F\u002F \"DLS\"\n",[74,375,377,379,381,383,385,387,389,392,394,396],{"class":76,"line":376},7,[74,378,276],{"class":120},[74,380,189],{"class":116},[74,382,281],{"class":166},[74,384,170],{"class":169},[74,386,286],{"class":120},[74,388,189],{"class":116},[74,390,391],{"class":120},"unit",[74,393,206],{"class":169},[74,395,296],{"class":116},[74,397,398],{"class":299}," \u002F\u002F \"Quarter Section\"\n",[11,400,401,402,405,406,409,410,409,412,409,414,409,416,409,418,409,420,423,424,427],{},"The response is typed as a ",[15,403,404],{},"SearchResult",". Your editor knows every field before you run the code: ",[15,407,408],{},"legalLocation",", ",[15,411,291],{},[15,413,317],{},[15,415,341],{},[15,417,366],{},[15,419,391],{},[15,421,422],{},"boundary",", and ",[15,425,426],{},"raw"," (the full GeoJSON features array).",[11,429,430],{},"A few input variations that all resolve correctly:",[65,432,434],{"className":103,"code":433,"language":105,"meta":70,"style":70},"await client.search(\"NW-36-42-3-W5\"); \u002F\u002F Quarter section (DLS)\nawait client.search(\"10-36-42-3-W5\"); \u002F\u002F LSD (Legal Subdivision)\nawait client.search(\"36-42-3-W5\"); \u002F\u002F Full section\nawait client.search(\"A-2-F\u002F93-P-8\"); \u002F\u002F NTS quarter unit (BC)\nawait client.search(\"Lot 2 Con 4 Osprey\"); \u002F\u002F Ontario geographic township\n",[15,435,436,462,488,514,540],{"__ignoreMap":70},[74,437,438,441,443,445,447,449,451,453,455,457,459],{"class":76,"line":77},[74,439,440],{"class":112},"await",[74,442,156],{"class":120},[74,444,189],{"class":116},[74,446,255],{"class":166},[74,448,170],{"class":169},[74,450,136],{"class":130},[74,452,17],{"class":84},[74,454,136],{"class":130},[74,456,206],{"class":169},[74,458,296],{"class":116},[74,460,461],{"class":299}," \u002F\u002F Quarter section (DLS)\n",[74,463,464,466,468,470,472,474,476,479,481,483,485],{"class":76,"line":142},[74,465,440],{"class":112},[74,467,156],{"class":120},[74,469,189],{"class":116},[74,471,255],{"class":166},[74,473,170],{"class":169},[74,475,136],{"class":130},[74,477,478],{"class":84},"10-36-42-3-W5",[74,480,136],{"class":130},[74,482,206],{"class":169},[74,484,296],{"class":116},[74,486,487],{"class":299}," \u002F\u002F LSD (Legal Subdivision)\n",[74,489,490,492,494,496,498,500,502,505,507,509,511],{"class":76,"line":149},[74,491,440],{"class":112},[74,493,156],{"class":120},[74,495,189],{"class":116},[74,497,255],{"class":166},[74,499,170],{"class":169},[74,501,136],{"class":130},[74,503,504],{"class":84},"36-42-3-W5",[74,506,136],{"class":130},[74,508,206],{"class":169},[74,510,296],{"class":116},[74,512,513],{"class":299}," \u002F\u002F Full section\n",[74,515,516,518,520,522,524,526,528,531,533,535,537],{"class":76,"line":176},[74,517,440],{"class":112},[74,519,156],{"class":120},[74,521,189],{"class":116},[74,523,255],{"class":166},[74,525,170],{"class":169},[74,527,136],{"class":130},[74,529,530],{"class":84},"A-2-F\u002F93-P-8",[74,532,136],{"class":130},[74,534,206],{"class":169},[74,536,296],{"class":116},[74,538,539],{"class":299}," \u002F\u002F NTS quarter unit (BC)\n",[74,541,542,544,546,548,550,552,554,557,559,561,563],{"class":76,"line":200},[74,543,440],{"class":112},[74,545,156],{"class":120},[74,547,189],{"class":116},[74,549,255],{"class":166},[74,551,170],{"class":169},[74,553,136],{"class":130},[74,555,556],{"class":84},"Lot 2 Con 4 Osprey",[74,558,136],{"class":130},[74,560,206],{"class":169},[74,562,296],{"class":116},[74,564,565],{"class":299}," \u002F\u002F Ontario geographic township\n",[11,567,568,569,572],{},"If the description does not resolve, the SDK throws a ",[15,570,571],{},"NotFoundError"," rather than returning null. This matters for pipelines: a silent failure means bad data downstream. An exception means you handle it explicitly.",[53,574,576],{"id":575},"convert-locations-in-bulk-with-batchsearch","Convert locations in bulk with batchSearch",[11,578,579,580,582],{},"Single lookups work for interactive search and on-demand resolution. For batch jobs (importing a well list, processing regulatory filings, geocoding a lease portfolio), ",[15,581,47],{}," converts up to thousands of descriptions in a single call.",[65,584,586],{"className":103,"code":585,"language":105,"meta":70,"style":70},"const locations = [\n  \"06-32-048-07W5\",\n  \"NE-14-032-21W4\",\n  \"NW-25-024-01W5\",\n  \"A-2-F\u002F93-P-8\"\n  \u002F\u002F ... hundreds more\n];\n\nconst batch = await client.batchSearch(locations);\n\nconsole.log(batch.total); \u002F\u002F number of descriptions submitted\nconsole.log(batch.success); \u002F\u002F successful conversions\nconsole.log(batch.failed); \u002F\u002F descriptions that did not resolve\n\nfor (const result of batch.results) {\n  console.log(result.legalLocation, result.latitude, result.longitude);\n}\n",[15,587,588,600,612,623,634,643,648,655,660,688,693,719,744,769,774,802,842],{"__ignoreMap":70},[74,589,590,592,595,597],{"class":76,"line":77},[74,591,153],{"class":152},[74,593,594],{"class":120}," locations",[74,596,159],{"class":116},[74,598,599],{"class":169}," [\n",[74,601,602,605,607,609],{"class":76,"line":142},[74,603,604],{"class":130},"  \"",[74,606,21],{"class":84},[74,608,136],{"class":130},[74,610,611],{"class":116},",\n",[74,613,614,616,619,621],{"class":76,"line":149},[74,615,604],{"class":130},[74,617,618],{"class":84},"NE-14-032-21W4",[74,620,136],{"class":130},[74,622,611],{"class":116},[74,624,625,627,630,632],{"class":76,"line":176},[74,626,604],{"class":130},[74,628,629],{"class":84},"NW-25-024-01W5",[74,631,136],{"class":130},[74,633,611],{"class":116},[74,635,636,638,640],{"class":76,"line":200},[74,637,604],{"class":130},[74,639,530],{"class":84},[74,641,642],{"class":130},"\"\n",[74,644,645],{"class":76,"line":351},[74,646,647],{"class":299},"  \u002F\u002F ... hundreds more\n",[74,649,650,653],{"class":76,"line":376},[74,651,652],{"class":169},"]",[74,654,139],{"class":116},[74,656,658],{"class":76,"line":657},8,[74,659,146],{"emptyLinePlaceholder":145},[74,661,663,665,668,670,672,674,676,679,681,684,686],{"class":76,"line":662},9,[74,664,153],{"class":152},[74,666,667],{"class":120}," batch",[74,669,159],{"class":116},[74,671,248],{"class":112},[74,673,156],{"class":120},[74,675,189],{"class":116},[74,677,678],{"class":166},"batchSearch",[74,680,170],{"class":169},[74,682,683],{"class":120},"locations",[74,685,206],{"class":169},[74,687,139],{"class":116},[74,689,691],{"class":76,"line":690},10,[74,692,146],{"emptyLinePlaceholder":145},[74,694,696,698,700,702,704,707,709,712,714,716],{"class":76,"line":695},11,[74,697,276],{"class":120},[74,699,189],{"class":116},[74,701,281],{"class":166},[74,703,170],{"class":169},[74,705,706],{"class":120},"batch",[74,708,189],{"class":116},[74,710,711],{"class":120},"total",[74,713,206],{"class":169},[74,715,296],{"class":116},[74,717,718],{"class":299}," \u002F\u002F number of descriptions submitted\n",[74,720,722,724,726,728,730,732,734,737,739,741],{"class":76,"line":721},12,[74,723,276],{"class":120},[74,725,189],{"class":116},[74,727,281],{"class":166},[74,729,170],{"class":169},[74,731,706],{"class":120},[74,733,189],{"class":116},[74,735,736],{"class":120},"success",[74,738,206],{"class":169},[74,740,296],{"class":116},[74,742,743],{"class":299}," \u002F\u002F successful conversions\n",[74,745,747,749,751,753,755,757,759,762,764,766],{"class":76,"line":746},13,[74,748,276],{"class":120},[74,750,189],{"class":116},[74,752,281],{"class":166},[74,754,170],{"class":169},[74,756,706],{"class":120},[74,758,189],{"class":116},[74,760,761],{"class":120},"failed",[74,763,206],{"class":169},[74,765,296],{"class":116},[74,767,768],{"class":299}," \u002F\u002F descriptions that did not resolve\n",[74,770,772],{"class":76,"line":771},14,[74,773,146],{"emptyLinePlaceholder":145},[74,775,777,780,783,785,787,790,792,794,797,799],{"class":76,"line":776},15,[74,778,779],{"class":112},"for",[74,781,782],{"class":169}," (",[74,784,153],{"class":152},[74,786,243],{"class":120},[74,788,789],{"class":162}," of",[74,791,667],{"class":120},[74,793,189],{"class":116},[74,795,796],{"class":120},"results",[74,798,206],{"class":169},[74,800,801],{"class":116}," {\n",[74,803,805,808,810,812,815,817,819,821,824,826,828,830,832,834,836,838,840],{"class":76,"line":804},16,[74,806,807],{"class":120},"  console",[74,809,189],{"class":116},[74,811,281],{"class":166},[74,813,170],{"class":814},"sLdnO",[74,816,286],{"class":120},[74,818,189],{"class":116},[74,820,408],{"class":120},[74,822,823],{"class":116},",",[74,825,243],{"class":120},[74,827,189],{"class":116},[74,829,291],{"class":120},[74,831,823],{"class":116},[74,833,243],{"class":120},[74,835,189],{"class":116},[74,837,317],{"class":120},[74,839,206],{"class":814},[74,841,139],{"class":116},[74,843,845],{"class":76,"line":844},17,[74,846,847],{"class":116},"}\n",[11,849,25,850,854,855,858,859,862],{},[27,851,853],{"href":852},"\u002Fguides\u002Fbatch-api-guide","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 ",[15,856,857],{},"BatchResult"," with all results merged. You can adjust the chunk size with ",[15,860,861],{},"{ chunkSize: 50 }"," if you want smaller batches.",[11,864,865,866,868],{},"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 ",[15,867,47],{},", one method call covers all of that.",[870,871,873],"h3",{"id":872},"a-practical-example-petrinex-well-data-to-kml","A practical example: Petrinex well data to KML",[11,875,876,877,880],{},"An O&G data team exports 500 well records from a Petrinex production report. Each row has a UWI like ",[15,878,879],{},"100\u002F06-32-048-07W5\u002F00",". They need GPS coordinates for every well site to generate a KML file for ArcGIS.",[11,882,883],{},"The UWI encodes the DLS location between the two slashes. Extract it, then batch-convert:",[65,885,887],{"className":103,"code":886,"language":105,"meta":70,"style":70},"import { TownshipClient } from \"townshipcanada\";\n\nconst client = new TownshipClient({\n  apiKey: process.env.TOWNSHIP_API_KEY\n});\n\n\u002F\u002F Extract DLS from UWIs: \"100\u002F06-32-048-07W5\u002F00\" → \"06-32-048-07W5\"\nconst uwis = getWellRecords(); \u002F\u002F your data source\nconst locations = uwis.map((uwi) => uwi.split(\"\u002F\")[1]);\n\nconst batch = await client.batchSearch(locations);\n\n\u002F\u002F batch.results now has GPS coordinates for each LSD\nfor (const result of batch.results) {\n  console.log(`${result.legalLocation}: ${result.latitude}, ${result.longitude}`);\n}\n\n\u002F\u002F Feed batch.results into your KML generator\n",[15,888,889,909,913,929,945,953,957,962,982,1040,1044,1068,1072,1077,1099,1156,1160,1164],{"__ignoreMap":70},[74,890,891,893,895,897,899,901,903,905,907],{"class":76,"line":77},[74,892,113],{"class":112},[74,894,117],{"class":116},[74,896,121],{"class":120},[74,898,124],{"class":116},[74,900,127],{"class":112},[74,902,131],{"class":130},[74,904,34],{"class":84},[74,906,136],{"class":130},[74,908,139],{"class":116},[74,910,911],{"class":76,"line":142},[74,912,146],{"emptyLinePlaceholder":145},[74,914,915,917,919,921,923,925,927],{"class":76,"line":149},[74,916,153],{"class":152},[74,918,156],{"class":120},[74,920,159],{"class":116},[74,922,163],{"class":162},[74,924,121],{"class":166},[74,926,170],{"class":169},[74,928,173],{"class":116},[74,930,931,933,935,937,939,941,943],{"class":76,"line":176},[74,932,180],{"class":179},[74,934,183],{"class":116},[74,936,186],{"class":120},[74,938,189],{"class":116},[74,940,192],{"class":120},[74,942,189],{"class":116},[74,944,197],{"class":120},[74,946,947,949,951],{"class":76,"line":200},[74,948,203],{"class":116},[74,950,206],{"class":169},[74,952,139],{"class":116},[74,954,955],{"class":76,"line":351},[74,956,146],{"emptyLinePlaceholder":145},[74,958,959],{"class":76,"line":376},[74,960,961],{"class":299},"\u002F\u002F Extract DLS from UWIs: \"100\u002F06-32-048-07W5\u002F00\" → \"06-32-048-07W5\"\n",[74,963,964,966,969,971,974,977,979],{"class":76,"line":657},[74,965,153],{"class":152},[74,967,968],{"class":120}," uwis",[74,970,159],{"class":116},[74,972,973],{"class":166}," getWellRecords",[74,975,976],{"class":169},"()",[74,978,296],{"class":116},[74,980,981],{"class":299}," \u002F\u002F your data source\n",[74,983,984,986,988,990,992,994,997,999,1001,1005,1007,1011,1014,1016,1019,1021,1023,1026,1028,1031,1035,1038],{"class":76,"line":662},[74,985,153],{"class":152},[74,987,594],{"class":120},[74,989,159],{"class":116},[74,991,968],{"class":120},[74,993,189],{"class":116},[74,995,996],{"class":166},"map",[74,998,170],{"class":169},[74,1000,170],{"class":116},[74,1002,1004],{"class":1003},"sqOPj","uwi",[74,1006,206],{"class":116},[74,1008,1010],{"class":1009},"sIOqK"," =>",[74,1012,1013],{"class":120}," uwi",[74,1015,189],{"class":116},[74,1017,1018],{"class":166},"split",[74,1020,170],{"class":169},[74,1022,136],{"class":130},[74,1024,1025],{"class":84},"\u002F",[74,1027,136],{"class":130},[74,1029,1030],{"class":169},")[",[74,1032,1034],{"class":1033},"s7CZa","1",[74,1036,1037],{"class":169},"])",[74,1039,139],{"class":116},[74,1041,1042],{"class":76,"line":690},[74,1043,146],{"emptyLinePlaceholder":145},[74,1045,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064,1066],{"class":76,"line":695},[74,1047,153],{"class":152},[74,1049,667],{"class":120},[74,1051,159],{"class":116},[74,1053,248],{"class":112},[74,1055,156],{"class":120},[74,1057,189],{"class":116},[74,1059,678],{"class":166},[74,1061,170],{"class":169},[74,1063,683],{"class":120},[74,1065,206],{"class":169},[74,1067,139],{"class":116},[74,1069,1070],{"class":76,"line":721},[74,1071,146],{"emptyLinePlaceholder":145},[74,1073,1074],{"class":76,"line":746},[74,1075,1076],{"class":299},"\u002F\u002F batch.results now has GPS coordinates for each LSD\n",[74,1078,1079,1081,1083,1085,1087,1089,1091,1093,1095,1097],{"class":76,"line":771},[74,1080,779],{"class":112},[74,1082,782],{"class":169},[74,1084,153],{"class":152},[74,1086,243],{"class":120},[74,1088,789],{"class":162},[74,1090,667],{"class":120},[74,1092,189],{"class":116},[74,1094,796],{"class":120},[74,1096,206],{"class":169},[74,1098,801],{"class":116},[74,1100,1101,1103,1105,1107,1109,1112,1116,1119,1121,1123,1125,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154],{"class":76,"line":776},[74,1102,807],{"class":120},[74,1104,189],{"class":116},[74,1106,281],{"class":166},[74,1108,170],{"class":814},[74,1110,1111],{"class":130},"`",[74,1113,1115],{"class":1114},"s8XtY","${",[74,1117,286],{"class":1118},"su_V2",[74,1120,189],{"class":116},[74,1122,408],{"class":1118},[74,1124,203],{"class":1114},[74,1126,1127],{"class":84},": ",[74,1129,1115],{"class":1114},[74,1131,286],{"class":1118},[74,1133,189],{"class":116},[74,1135,291],{"class":1118},[74,1137,203],{"class":1114},[74,1139,409],{"class":84},[74,1141,1115],{"class":1114},[74,1143,286],{"class":1118},[74,1145,189],{"class":116},[74,1147,317],{"class":1118},[74,1149,203],{"class":1114},[74,1151,1111],{"class":130},[74,1153,206],{"class":814},[74,1155,139],{"class":116},[74,1157,1158],{"class":76,"line":804},[74,1159,847],{"class":116},[74,1161,1162],{"class":76,"line":844},[74,1163,146],{"emptyLinePlaceholder":145},[74,1165,1167],{"class":76,"line":1166},18,[74,1168,1169],{"class":299},"\u002F\u002F Feed batch.results into your KML generator\n",[11,1171,25,1172,1174,1175,409,1177,409,1179,423,1181,1183],{},[15,1173,47],{}," call handles the full 500-location list. The data team gets structured results with ",[15,1176,291],{},[15,1178,317],{},[15,1180,341],{},[15,1182,366],{}," for each record, ready for KML export or any other format their GIS tooling expects.",[11,1185,1186,1187,189],{},"For more on UWI parsing and the edge cases around directional wells and NTS-format identifiers, see ",[27,1188,1190],{"href":1189},"\u002Fblog\u002Fuwi-to-gps-developers-guide","UWI to GPS: The O&G Developer's Guide",[53,1192,1194],{"id":1193},"retrieve-parcel-boundary-polygons","Retrieve parcel boundary polygons",[11,1196,1197,1198,1201],{},"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 ",[15,1199,1200],{},"boundary()"," method returns the polygon directly.",[65,1203,1205],{"className":103,"code":1204,"language":105,"meta":70,"style":70},"const polygon = await client.boundary(\"06-32-048-07W5\");\n\nif (polygon) {\n  console.log(polygon.type); \u002F\u002F \"Polygon\"\n  console.log(polygon.coordinates); \u002F\u002F [[[lng, lat], [lng, lat], ...]]\n}\n",[15,1206,1207,1236,1240,1254,1278,1302],{"__ignoreMap":70},[74,1208,1209,1211,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234],{"class":76,"line":77},[74,1210,153],{"class":152},[74,1212,1213],{"class":120}," polygon",[74,1215,159],{"class":116},[74,1217,248],{"class":112},[74,1219,156],{"class":120},[74,1221,189],{"class":116},[74,1223,422],{"class":166},[74,1225,170],{"class":169},[74,1227,136],{"class":130},[74,1229,21],{"class":84},[74,1231,136],{"class":130},[74,1233,206],{"class":169},[74,1235,139],{"class":116},[74,1237,1238],{"class":76,"line":142},[74,1239,146],{"emptyLinePlaceholder":145},[74,1241,1242,1245,1247,1250,1252],{"class":76,"line":149},[74,1243,1244],{"class":112},"if",[74,1246,782],{"class":169},[74,1248,1249],{"class":120},"polygon",[74,1251,206],{"class":169},[74,1253,801],{"class":116},[74,1255,1256,1258,1260,1262,1264,1266,1268,1271,1273,1275],{"class":76,"line":176},[74,1257,807],{"class":120},[74,1259,189],{"class":116},[74,1261,281],{"class":166},[74,1263,170],{"class":814},[74,1265,1249],{"class":120},[74,1267,189],{"class":116},[74,1269,1270],{"class":120},"type",[74,1272,206],{"class":814},[74,1274,296],{"class":116},[74,1276,1277],{"class":299}," \u002F\u002F \"Polygon\"\n",[74,1279,1280,1282,1284,1286,1288,1290,1292,1295,1297,1299],{"class":76,"line":200},[74,1281,807],{"class":120},[74,1283,189],{"class":116},[74,1285,281],{"class":166},[74,1287,170],{"class":814},[74,1289,1249],{"class":120},[74,1291,189],{"class":116},[74,1293,1294],{"class":120},"coordinates",[74,1296,206],{"class":814},[74,1298,296],{"class":116},[74,1300,1301],{"class":299}," \u002F\u002F [[[lng, lat], [lng, lat], ...]]\n",[74,1303,1304],{"class":76,"line":351},[74,1305,847],{"class":116},[11,1307,1308,1309,1312],{},"The return type is ",[15,1310,1311],{},"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.",[11,1314,1315,1316,1319],{},"If you need both the coordinates and the full GeoJSON feature collection (including metadata properties), use ",[15,1317,1318],{},"raw()"," instead:",[65,1321,1323],{"className":103,"code":1322,"language":105,"meta":70,"style":70},"const fc = await client.raw(\"NW-36-42-3-W5\");\n\u002F\u002F fc.type → \"FeatureCollection\"\n\u002F\u002F fc.features → LocationFeature[]\n",[15,1324,1325,1354,1359],{"__ignoreMap":70},[74,1326,1327,1329,1332,1334,1336,1338,1340,1342,1344,1346,1348,1350,1352],{"class":76,"line":77},[74,1328,153],{"class":152},[74,1330,1331],{"class":120}," fc",[74,1333,159],{"class":116},[74,1335,248],{"class":112},[74,1337,156],{"class":120},[74,1339,189],{"class":116},[74,1341,426],{"class":166},[74,1343,170],{"class":169},[74,1345,136],{"class":130},[74,1347,17],{"class":84},[74,1349,136],{"class":130},[74,1351,206],{"class":169},[74,1353,139],{"class":116},[74,1355,1356],{"class":76,"line":142},[74,1357,1358],{"class":299},"\u002F\u002F fc.type → \"FeatureCollection\"\n",[74,1360,1361],{"class":76,"line":149},[74,1362,1363],{"class":299},"\u002F\u002F fc.features → LocationFeature[]\n",[53,1365,1367],{"id":1366},"handle-errors-with-typed-exceptions","Handle errors with typed exceptions",[11,1369,1370],{},"API errors are typed classes, not generic HTTP status codes. This makes error handling in a Node.js application straightforward:",[65,1372,1374],{"className":103,"code":1373,"language":105,"meta":70,"style":70},"import {\n  TownshipClient,\n  AuthenticationError,\n  NotFoundError,\n  RateLimitError,\n  ValidationError\n} from \"townshipcanada\";\n\ntry {\n  const result = await client.search(\"NW-36-42-3-W5\");\n} catch (error) {\n  if (error instanceof NotFoundError) {\n    \u002F\u002F The description did not match any known parcel\n  } else if (error instanceof RateLimitError) {\n    \u002F\u002F You have exceeded your plan's request quota\n  } else if (error instanceof AuthenticationError) {\n    \u002F\u002F The API key is missing or invalid\n  } else if (error instanceof ValidationError) {\n    \u002F\u002F The input was malformed\n  }\n}\n",[15,1375,1376,1382,1389,1396,1403,1410,1415,1429,1433,1440,1469,1485,1505,1510,1534,1539,1560,1565,1586,1592,1598],{"__ignoreMap":70},[74,1377,1378,1380],{"class":76,"line":77},[74,1379,113],{"class":112},[74,1381,801],{"class":116},[74,1383,1384,1387],{"class":76,"line":142},[74,1385,1386],{"class":120},"  TownshipClient",[74,1388,611],{"class":116},[74,1390,1391,1394],{"class":76,"line":149},[74,1392,1393],{"class":120},"  AuthenticationError",[74,1395,611],{"class":116},[74,1397,1398,1401],{"class":76,"line":176},[74,1399,1400],{"class":120},"  NotFoundError",[74,1402,611],{"class":116},[74,1404,1405,1408],{"class":76,"line":200},[74,1406,1407],{"class":120},"  RateLimitError",[74,1409,611],{"class":116},[74,1411,1412],{"class":76,"line":351},[74,1413,1414],{"class":120},"  ValidationError\n",[74,1416,1417,1419,1421,1423,1425,1427],{"class":76,"line":376},[74,1418,203],{"class":116},[74,1420,127],{"class":112},[74,1422,131],{"class":130},[74,1424,34],{"class":84},[74,1426,136],{"class":130},[74,1428,139],{"class":116},[74,1430,1431],{"class":76,"line":657},[74,1432,146],{"emptyLinePlaceholder":145},[74,1434,1435,1438],{"class":76,"line":662},[74,1436,1437],{"class":112},"try",[74,1439,801],{"class":116},[74,1441,1442,1445,1447,1449,1451,1453,1455,1457,1459,1461,1463,1465,1467],{"class":76,"line":690},[74,1443,1444],{"class":152},"  const",[74,1446,243],{"class":120},[74,1448,159],{"class":116},[74,1450,248],{"class":112},[74,1452,156],{"class":120},[74,1454,189],{"class":116},[74,1456,255],{"class":166},[74,1458,170],{"class":814},[74,1460,136],{"class":130},[74,1462,17],{"class":84},[74,1464,136],{"class":130},[74,1466,206],{"class":814},[74,1468,139],{"class":116},[74,1470,1471,1473,1476,1478,1481,1483],{"class":76,"line":695},[74,1472,203],{"class":116},[74,1474,1475],{"class":112}," catch",[74,1477,782],{"class":169},[74,1479,1480],{"class":120},"error",[74,1482,206],{"class":169},[74,1484,801],{"class":116},[74,1486,1487,1490,1492,1494,1497,1501,1503],{"class":76,"line":721},[74,1488,1489],{"class":112},"  if",[74,1491,782],{"class":814},[74,1493,1480],{"class":120},[74,1495,1496],{"class":162}," instanceof",[74,1498,1500],{"class":1499},"sD-vU"," NotFoundError",[74,1502,206],{"class":814},[74,1504,801],{"class":116},[74,1506,1507],{"class":76,"line":746},[74,1508,1509],{"class":299},"    \u002F\u002F The description did not match any known parcel\n",[74,1511,1512,1515,1518,1521,1523,1525,1527,1530,1532],{"class":76,"line":771},[74,1513,1514],{"class":116},"  }",[74,1516,1517],{"class":112}," else",[74,1519,1520],{"class":112}," if",[74,1522,782],{"class":814},[74,1524,1480],{"class":120},[74,1526,1496],{"class":162},[74,1528,1529],{"class":1499}," RateLimitError",[74,1531,206],{"class":814},[74,1533,801],{"class":116},[74,1535,1536],{"class":76,"line":776},[74,1537,1538],{"class":299},"    \u002F\u002F You have exceeded your plan's request quota\n",[74,1540,1541,1543,1545,1547,1549,1551,1553,1556,1558],{"class":76,"line":804},[74,1542,1514],{"class":116},[74,1544,1517],{"class":112},[74,1546,1520],{"class":112},[74,1548,782],{"class":814},[74,1550,1480],{"class":120},[74,1552,1496],{"class":162},[74,1554,1555],{"class":1499}," AuthenticationError",[74,1557,206],{"class":814},[74,1559,801],{"class":116},[74,1561,1562],{"class":76,"line":844},[74,1563,1564],{"class":299},"    \u002F\u002F The API key is missing or invalid\n",[74,1566,1567,1569,1571,1573,1575,1577,1579,1582,1584],{"class":76,"line":1166},[74,1568,1514],{"class":116},[74,1570,1517],{"class":112},[74,1572,1520],{"class":112},[74,1574,782],{"class":814},[74,1576,1480],{"class":120},[74,1578,1496],{"class":162},[74,1580,1581],{"class":1499}," ValidationError",[74,1583,206],{"class":814},[74,1585,801],{"class":116},[74,1587,1589],{"class":76,"line":1588},19,[74,1590,1591],{"class":299},"    \u002F\u002F The input was malformed\n",[74,1593,1595],{"class":76,"line":1594},20,[74,1596,1597],{"class":116},"  }\n",[74,1599,1601],{"class":76,"line":1600},21,[74,1602,847],{"class":116},[11,1604,1605,1606,1609,1610,1613,1614,1616,1617,1620,1621,1624],{},"Five error classes cover the full API surface: ",[15,1607,1608],{},"AuthenticationError"," (401), ",[15,1611,1612],{},"ValidationError"," (400), ",[15,1615,571],{}," (404), ",[15,1618,1619],{},"RateLimitError"," (429), and ",[15,1622,1623],{},"PayloadTooLargeError"," (413). Each carries the HTTP status code and the response message, so you can log the specifics without parsing a response body.",[53,1626,1628],{"id":1627},"what-you-can-build-from-here","What you can build from here",[11,1630,1631,1632,409,1634,409,1636,409,1638,409,1641,1644],{},"With ",[15,1633,43],{},[15,1635,47],{},[15,1637,1200],{},[15,1639,1640],{},"reverse()",[15,1642,1643],{},"autocomplete()",", and typed error handling, the SDK covers the full Township Canada API surface. A few directions this opens up:",[11,1646,1647,1651,1652,1654,1655,1659],{},[1648,1649,1650],"strong",{},"Type-ahead search in your UI."," The ",[15,1653,1643],{}," 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\u002FNTS\u002FFPS grid. See the ",[27,1656,1658],{"href":1657},"\u002Fguides\u002Fautocomplete-api-guide","Autocomplete API guide"," for the endpoint details.",[11,1661,1662,1651,1665,1667],{},[1648,1663,1664],{},"Reverse geocoding.",[15,1666,1640],{}," 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.",[11,1669,1670,1673,1674,1676,1677,409,1681,409,1685,423,1689,189],{},[1648,1671,1672],{},"Map overlays."," Combine ",[15,1675,1200],{}," with a mapping library to draw parcel outlines on a map. There are integration guides for ",[27,1678,1680],{"href":1679},"\u002Fguides\u002Fgoogle-maps-integration","Google Maps",[27,1682,1684],{"href":1683},"\u002Fguides\u002Fmapbox-integration","Mapbox GL JS",[27,1686,1688],{"href":1687},"\u002Fguides\u002Fleaflet-integration","Leaflet",[27,1690,1692],{"href":1691},"\u002Fguides\u002Fopenlayers-integration","OpenLayers",[11,1694,1695,1696,1700,1701,1705],{},"For a broader look at who builds with the API and the common industry use cases, see ",[27,1697,1699],{"href":1698},"\u002Fblog\u002Fbuilding-with-canadian-land-data-api","Building with Canadian Land Data",". For data warehouse teams, ",[27,1702,1704],{"href":1703},"\u002Fblog\u002Fconvert-legal-land-descriptions-snowflake-sql","converting legal land descriptions in Snowflake SQL"," uses the same underlying API as an external function.",[11,1707,1708,1709,1712,1713,1716],{},"The SDK is MIT licensed and available on ",[27,1710,81],{"href":29,"rel":1711},[31],". API plans start with the Search Build tier. See the ",[27,1714,1715],{"href":38},"API pricing page"," for current rates.",[1718,1719,1720],"style",{},"html pre.shiki code .sYn-s, html code.shiki .sYn-s{--shiki-light:#E2931D;--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .sTbE_, html code.shiki .sTbE_{--shiki-light:#91B859;--shiki-default:#B56959;--shiki-dark:#C98A7D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .siDh9, html code.shiki .siDh9{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#1E754F;--shiki-default-font-style:inherit;--shiki-dark:#4D9375;--shiki-dark-font-style:inherit}html pre.shiki code .soVBu, html code.shiki .soVBu{--shiki-light:#39ADB5;--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sSC40, html code.shiki .sSC40{--shiki-light:#90A4AE;--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .sbYkP, html code.shiki .sbYkP{--shiki-light:#39ADB5;--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .s5Kfy, html code.shiki .s5Kfy{--shiki-light:#9C3EDA;--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .sVsLi, html code.shiki .sVsLi{--shiki-light:#39ADB5;--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .sljsM, html code.shiki .sljsM{--shiki-light:#6182B8;--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .snCua, html code.shiki .snCua{--shiki-light:#90A4AE;--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .suXOh, html code.shiki .suXOh{--shiki-light:#E53935;--shiki-default:#998418;--shiki-dark:#B8A965}html pre.shiki code .s9Tkl, html code.shiki .s9Tkl{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#A0ADA0;--shiki-default-font-style:inherit;--shiki-dark:#758575DD;--shiki-dark-font-style:inherit}html pre.shiki code .sLdnO, html code.shiki .sLdnO{--shiki-light:#E53935;--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sqOPj, html code.shiki .sqOPj{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#B07D48;--shiki-default-font-style:inherit;--shiki-dark:#BD976A;--shiki-dark-font-style:inherit}html pre.shiki code .sIOqK, html code.shiki .sIOqK{--shiki-light:#9C3EDA;--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .s7CZa, html code.shiki .s7CZa{--shiki-light:#F76D47;--shiki-default:#2F798A;--shiki-dark:#4C9A91}html pre.shiki code .s8XtY, html code.shiki .s8XtY{--shiki-light:#39ADB5;--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .su_V2, html code.shiki .su_V2{--shiki-light:#90A4AE;--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .sD-vU, html code.shiki .sD-vU{--shiki-light:#E2931D;--shiki-default:#2E8F82;--shiki-dark:#5DA994}",{"title":70,"searchDepth":142,"depth":142,"links":1722},[1723,1724,1725,1728,1729,1730],{"id":55,"depth":142,"text":56},{"id":225,"depth":142,"text":226},{"id":575,"depth":142,"text":576,"children":1726},[1727],{"id":872,"depth":149,"text":873},{"id":1193,"depth":142,"text":1194},{"id":1366,"depth":142,"text":1367},{"id":1627,"depth":142,"text":1628},"guides",null,"2026-08-17","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.","md",{},"\u002Fblog\u002Fadding-legal-land-description-search-nodejs-sdk-walkthrough",{"title":5,"description":1734},"blog\u002Fadding-legal-land-description-search-nodejs-sdk-walkthrough",[1741,1742,1743,1744,1745],"Developer","API","TypeScript","SDK","Node.js","2xO20mLh9Oa0a1RCxJHvOMlgKucHqimh9rLR7y1vMVY"]