# Count and sum a cloud GeoParquet per zone

> Index of the TerraLab docs for agents: [llms.txt](https://terra-lab.ai/docs/llms.txt). Every docs page is also served as markdown: add .md to its URL.

A count, sum or average over a large public GeoParquet for a place or per zone (state, county, region, district), without downloading it.

| | |
| --- | --- |
| REST | `POST https://mcp.terra-lab.ai/v1/tools/aggregate_dataset` |
| MCP tool | `aggregate_dataset` on `https://mcp.terra-lab.ai/mcp` |
| Group | Data catalog |

## Request

```bash
curl -X POST https://mcp.terra-lab.ai/v1/tools/aggregate_dataset \
  -H "Authorization: Bearer $TERRALAB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dataset":"portolan:harmonized-field-data/glocab","zones":{"per":"state","units":["Arkansas"]}}'
```

```python
import os
import requests

response = requests.post(
    "https://mcp.terra-lab.ai/v1/tools/aggregate_dataset",
    headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"},
    json={
        "dataset": "portolan:harmonized-field-data/glocab",
        "zones": {
            "per": "state",
            "units": ["Arkansas"],
        },
    },
    timeout=120,
)
response.raise_for_status()
print(response.json()["structuredContent"])
```

```javascript
const response = await fetch('https://mcp.terra-lab.ai/v1/tools/aggregate_dataset', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "dataset": "portolan:harmonized-field-data/glocab",
    "zones": {
      "per": "state",
      "units": [
        "Arkansas"
      ]
    }
  }),
});
const data = await response.json();
console.log(data.structuredContent);
```

Other valid request bodies:

```json
{"dataset":"portolan:nasa-firms-active-fire/detections","zones":{"per":"county","within":"California"},"where":[{"column":"year","op":"eq","value":2024}]}
```

## Parameters

A JSON object in the request body; in an MCP client, the arguments of the tool call.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `dataset` | string | Yes | A portolan: id from find_datasets (open_registry), or the https URL of a public GeoParquet file, a hive glob such as https://.../year=*/data.parquet, an s3:// or gs:// address, or a STAC collection.json. Up to 400 characters. |
| `area` | object | No | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. |
| `area.bbox` | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. |
| `area.place` | string or object | No | A place name or address, or a point. |
| `area.radius_m` | number | No | Radius around the place in metres. From 1 to 100000. |
| `zones` | object | No | One row per zone instead of one area. |
| `zones.per` | string | Yes | The zone level. One of `"state"`, `"county"`, `"zip"`, `"place"`, `"country"`, `"region"`, `"locality"`, `"neighborhood"`, `"adm1"`, `"adm2"`, `"adm3"`, `"adm4"`. |
| `zones.within` | string | No | The place whose zones are read: "California", "Kenya", or a zone id (US-STATE-06). Up to 200 characters. |
| `zones.units` | array of string | No | Only these zones, by name or code, at the level per gives. 0 to 200 items. |
| `where` | array of object | No | Conditions that must all hold. A condition on a partition key (year) picks the files before any byte is read; without one, only the newest partition is read. 0 to 12 items. |
| `group_by` | array of string | No | Columns to group by, one row per value (per zone). 0 to 2 items. |
| `aggregates` | array of object | No | Values beside the count: sum, mean, min or max of a column; area_m2 of the geometries. 0 to 8 items. |
| `limit` | integer | No | Rows returned, largest count first; distinct_groups says how many there are. From 1 to 100. Default `50`. |
| `dry_run` | boolean | No | Only the plan: files, row groups and bytes the query would read. Default `false`. |

## Response

`structuredContent` holds the result, `content` the same result as text for a model, and `isError` is `true` when the tool refused the request or failed; then `structuredContent.code`, `message` and `hint` say what to change.

```json
{
  "structuredContent": {
    "sources": [{ "name": "...", "licence": "...", "url": "..." }],
    "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }]
  },
  "content": [{ "type": "text", "text": "..." }],
  "isError": false
}
```

Every result lists its `sources` with their licence. File results come as `downloads`: each link builds its file when opened.

## Errors

A missing key answers `401`, an empty balance `402`, too many calls `429`. A call that fails or finds nothing is not counted. See [Errors and rate limits](https://terra-lab.ai/docs/errors).

## Description for agents

The text the server gives an agent to decide when to call this tool:

> Use this when the user wants a count, sum or average over a large public GeoParquet for a place or per zone, without downloading it: "how many crop fields are in Arkansas?", "active fires per county in California in 2024", "total building footprint area in Berlin Mitte", "fields per crop type". Give dataset (a portolan: id from find_datasets, or a GeoParquet URL; inspect_dataset lists its columns and partitions), then the area: area (a named place is cut to its outline; a place with radius_m is a disk; a bbox), or zones for one row per zone (per: state, county, zip, place in the US; country, region, locality, neighborhood anywhere; adm1 to adm4 official units; within: the parent place, or units: the zones by name). Without either, the whole file. where filters rows (a condition on the partition key, such as year, picks the files; without one only the newest partition is read). group_by gives one row per value (up to 2 columns); aggregates adds sum, mean, min or max of a column, or area_m2 of the geometries (reads the geometry, heavier). A row counts in a zone when the centre of its bounding box lies inside. Only the row groups the zones touch are read (a state from a multi-GB file in seconds); a query over the caps gets too_large with the estimate. Returns the table (zone, group, count, values), totals, distinct_groups and what was read. No features: extract_dataset cuts them. Never use the user's current location unless they type it. Give the place as the user names it (place name, address or coordinates): this tool locates it, so find_places is not needed first.


Source: https://terra-lab.ai/docs/api/aggregate_dataset · Updated 2026-11-15
