Bulk record reads
Totals independent of paging, server-side filtering and trimming, and a streamed export of a whole type.
Pagination is the right tool for a screen of records and the wrong one for a data consumer that wants everything. Three things cover that case.
Totals
GET /objects/{object_name}/stats answers whole-type questions in one call, independent of paging:
r = requests.get(f"{BASE}/objects/assets/stats", headers=HEADERS)
r.raise_for_status()
print(r.json())
# {"total": 3, "sums": {"capacity_kw": 212800.0}, "type_counts": {"wind_turbine": 3}}total is the record count. sums totals the type's numeric fields: capacity_kw for the built-in assets, every number attribute for a custom type. type_counts breaks the count down by subtype where the type has one (assets by values.type); it is empty otherwise.
A filtered, trimmed page
Filtering and trimming happen server-side, so the client does not download what it will discard:
r = requests.get(f"{BASE}/objects/plants", headers=HEADERS, params={
"filter[fuel]": "wind",
"fields": "name,capacity_mw,location",
"limit": 200,
})
r.raise_for_status()filter[<attribute>]=valuekeeps records whose attribute has that value. Repeat the parameter for several values of one attribute (OR-ed) or for several attributes (AND-ed). Filters work on custom types; on the built-ins they are a400. Link attributes filter by id, see Linking records.fields=a,btrimsvaluesto the named attributes. The envelope always rides along.
A whole type, streamed
GET /objects/{object_name}/export walks a server-side cursor and streams the result, so memory stays flat however many records there are. format=ndjson (the default) gives one JSON record per line:
import json
with requests.get(f"{BASE}/objects/plants/export", headers=HEADERS,
params={"fields": "name,capacity_mw,location"}, stream=True) as r:
r.raise_for_status()
print(r.headers["X-Total-Count"], "records; ETag", r.headers["ETag"])
etag = r.headers["ETag"]
for line in r.iter_lines():
if line:
record = json.loads(line)?filter[...] and ?fields= apply to the export exactly as to the list.
Cheap re-exports
The ETag is derived from the type's size and newest updated_at, so a repeat export of unchanged data costs one round trip and no body:
r = requests.get(f"{BASE}/objects/plants/export",
headers={**HEADERS, "If-None-Match": etag})
print(r.status_code) # 304 when nothing changedGeoJSON
format=geojson returns a FeatureCollection built from the type's geometry attribute; the remaining fields become each feature's properties. When a type has several geometry attributes, ?geometry_field= picks the one to use.
r = requests.get(f"{BASE}/objects/zones/export", headers=HEADERS,
params={"format": "geojson", "fields": "name,country"})
r.raise_for_status()
zones = r.json()
print(zones["type"], len(zones["features"])) # FeatureCollection 12This is the file a map draws areas from. For the series behind each area, see Many series at once.