Guide¶
Pull everything you own¶
The loop this SDK exists for: discover what the organization licences, then pull each dataset's newest snapshot. No hard-coded slugs.
for ds in tl.datasets.iter_owned():
tl.dataset(ds.table).archive(f"{ds.table}.zip", month="latest", format="geojson")
owned() is filtered by the same licence check the download routes enforce, so
everything it returns is downloadable. One page at a time:
page = tl.datasets.owned(limit=50, offset=0)
print(page.total) # all licensed datasets, not the page size
for d in page.items:
print(d.table, d.recordCount, d.latestArchiveMonth)
print(d.links.current.format(format="csv")) # links carry a {format} placeholder
Archives¶
ds = tl.dataset("nl-domino-poi")
for a in ds.archives(): # newest month first
print(a.month, a.formats)
ds.archive("nl-domino-poi.zip", month="2026-07", format="geojson")
month takes three forms:
| Value | Meaning |
|---|---|
latest |
Newest archive inside your plan's retention window |
YYYY-MM |
That month |
YYYY-MM-DD |
The month containing that date |
A malformed or impossible month (2026-13, 2026-07-99, 2026-02-29) raises
ValueError before the request is sent; the server answers the same case with a
400. A 404 means no archive is available — which also covers months
outside your retention window and months that have not started, deliberately
indistinguishable so the response never reveals an archive you cannot access.
Team plans see a trailing 12 months of archives; Enterprise and full-history
add-ons see everything. archives() already reflects your window, so it never
lists a month that would 404.
Coordinates¶
Rows arrive as a bare array and the paging facts arrive as response headers; the SDK folds both into one object.
page = tl.dataset("nl-domino-poi").coordinates(limit=1000, offset=0)
print(page.total, page.returned, page.offset)
row = page.rows[0]
print(row.latitude, row.longitude) # strings, kept exactly as returned
print(row.location, row.metadata)
limit caps at 50000. Omit both parameters to take the whole dataset in one
response, up to that cap.
latitude and longitude are fixed-scale decimal strings ("51.49638600") and
are never parsed as floats — that would drop the trailing zeros and risk
representation drift. Convert them yourself when you need numbers.
SQL (Enterprise)¶
res = tl.sql("SELECT city, count(*) AS n FROM nl_domino_poi GROUP BY 1", max_rows=100)
print(res.columns, res.rowCount, res.truncated, res.elapsedMs)
for r in res.rows:
print(r["city"], r["n"])
One read-only SELECT (or WITH) per call, running against the datasets you
licence. It requires the sql-access entitlement, which is part of the
Enterprise plan and is not sold separately; without it the call raises
AddonRequiredError. A query that exceeds the server statement timeout raises
QueryTimeoutError.
Browse the catalog¶
Dataset metadata and samples¶
ds = tl.dataset("nl-domino-poi")
meta = ds.metadata() # DatasetSummary
sample = ds.sample("geojson") # free preview rows (csv/json/geojson/kml)
Query features in an area (spatial, paged)¶
items() addresses the collection by slug directly — the OGC collectionId
is the dataset slug, so there is no metadata round-trip.
Stream every feature, paging transparently:
Pull a whole dataset (bulk)¶
fc = tl.dataset("nl-domino-poi").to_geojson() # dict (FeatureCollection)
tl.dataset("nl-domino-poi").download("dominos-nl.geojson", format="geojson")
download() streams to a temp file and atomically renames, so an interrupted
transfer never leaves a truncated file at the destination.
GeoPandas¶
With the geo extra installed:
Async¶
AsyncClient mirrors the sync surface — metadata, sample, to_geojson,
download, to_geodataframe, items, iter_items, archives, archive,
coordinates, datasets.owned, datasets.iter_owned and sql are all
awaitable (iter_items and iter_owned are async iterators):