The official TypeScript client for the Topolab dataset and geospatial API.
Lightweight, GeoJSON-first, runs in Node and the browser.
π Docs: topolab-bv.github.io/topolab-js Β· full platform docs at docs.topolab.nl
npm install @topolab/sdkPre-release: until the first version is published to npm, install from Git:
npm install github:topolab-bv/topolab-js
Ships ES modules + CommonJS and TypeScript types. Uses the platform fetch, so
Node 18+ or any modern browser / edge runtime works with no polyfill.
// Page Domino's locations within an Amsterdam bounding box
import { Client } from "@topolab/sdk";
const tl = new Client({ apiKey: "tlb_prod_..." });
const fc = await tl.dataset("nl-domino-poi").items({ limit: 100, bbox: [4.7, 52.2, 5.1, 52.5] });
console.log(`${fc.features.length} locations`);Your API key carries your scope and add-ons β spatial queries need GIS_ACCESS,
downloads need API_ACCESS, and data routes require an organization-scoped key.
Pass apiKey or set TOPOLAB_API_KEY (avoid embedding keys in client bundles):
const tl = new Client({ apiKey: process.env.TOPOLAB_API_KEY });The client targets production (https://api.topolab.nl) by default. Point it
at staging with the environment option:
const tl = new Client({ apiKey: "tlb_staging_...", environment: "staging" }); // https://api-staging.topolab.nlOr set TOPOLAB_ENV=staging. An explicit baseUrl always wins (self-hosting /
tests). Precedence: baseUrl β environment β TOPOLAB_BASE_URL β
TOPOLAB_ENV β production.
The loop this SDK is built for β discover what your organization licences, then pull each dataset's newest snapshot. No hard-coded slugs:
import { Client } from "@topolab/sdk";
import { downloadArchive } from "@topolab/sdk/node";
const tl = new Client();
for await (const ds of tl.datasets.iterOwned()) {
await downloadArchive(tl.dataset(ds.table), `${ds.table}.zip`, { month: "latest", format: "geojson" });
}iterOwned() is filtered by the same licence check the download routes enforce,
so everything it yields is downloadable.
const page = await tl.datasets.list({ country: "NL", limit: 10 });const fc = await tl.dataset("nl-domino-poi").items({ limit: 100, bbox: [4.7, 52.2, 5.1, 52.5] });
// or stream every feature, paging transparently:
for await (const feature of tl.dataset("nl-domino-poi").iterItems({ pageSize: 500 })) {
// ...
}The async iterator cancels the in-flight request if you break early.
const fc = await tl.dataset("nl-domino-poi").toGeoJSON(); // FeatureCollectionimport { Client } from "@topolab/sdk";
import { download } from "@topolab/sdk/node";
const tl = new Client();
await download(tl.dataset("nl-domino-poi"), "dominos-nl.geojson", { format: "geojson" });download lives in the @topolab/sdk/node subpath because it writes to the
filesystem β the core entry point stays browser-safe. downloadArchive lives
there for the same reason.
const page = await tl.datasets.owned({ limit: 50 }); // page.total = all licensed datasets
for await (const ds of tl.datasets.iterOwned()) { /* every one, paged for you */ }const ds = tl.dataset("business_professional_services_autocrew");
const months = await ds.archives(); // newest first; free, no credits
import { downloadArchive } from "@topolab/sdk/node";
await downloadArchive(ds, "autocrew.zip", { month: "2026-07", format: "geojson" });month is "latest", "YYYY-MM" or "YYYY-MM-DD", validated as a real
calendar value before the request goes out. Team plans see a trailing 12 months;
Enterprise sees everything.
const page = await ds.coordinates({ limit: 1000 });
page.total; // from X-Total-Count β the whole dataset, regardless of paging
page.rows[0].latitude; // "51.49638600" β a decimal string, kept as oneconst res = await tl.sql("SELECT city, count(*) AS n FROM autocrew GROUP BY 1", { maxRows: 100 });One read-only SELECT, restricted to datasets you hold an active licence for.
Requires the sql-access entitlement, part of the Enterprise plan.
Every failure throws a subclass of TopolabError, so you never parse raw JSON:
| Error | When |
|---|---|
AuthenticationError |
missing or invalid API key (401) |
AddonRequiredError |
key lacks the add-on β .addon names it (403) |
AccessDeniedError |
dataset not accessible to your organization (403) |
InsufficientCreditsError |
not enough credits β .required / .available (402) |
NotFoundError |
unknown dataset, or no archive available for that month (404) |
QueryTimeoutError |
a SQL query exceeded the server statement timeout (408) |
RateLimitError |
rate limited β .retryAfter, retried automatically (429) |
import { Client, AddonRequiredError } from "@topolab/sdk";
try {
await tl.dataset("nl-domino-poi").toGeoJSON();
} catch (e) {
if (e instanceof AddonRequiredError) console.log("Your key needs:", e.addon);
}- Full docs: docs.topolab.nl
- The bulk vs. spatial access patterns, credits, and add-ons are described in the
SDK conventions and the
topolab-sdk-specrepository. - A runnable example lives in
examples/quickstart.ts.
Issues and pull requests are welcome. See CONTRIBUTING.md.
Run npm test, type-check with npm run typecheck, build with npm run build.
MIT Β© Topolab B.V.
