<!-- Source: https://docs.geopera.com/api-reference/guides/processing · Markdown for LLMs -->

# Processing & analytics

Geopera turns imagery into answers two ways:

- **Processing jobs** produce new data — a clipped raster, a derived product — written
  back into your project as items + assets. These are `external_spend` operations
  (they reserve credits) and run on registered workers.
- **Analytics** computes results *over* existing items — statistics, indices, change
  detection — and returns the numbers (and any rendered output). Analytics is a single
  extensible operation, so new methods appear without changing the API surface.

All of the heavy geospatial computation runs in the backend. The client sends
parameters; the platform produces the correct, reproducible result — the same answer
whether the caller is the portal, an SDK, or an AI agent.

## Processing jobs

| Operation | Side-effect | Scope | Use |
|---|---|---|---|
| `clip.create_from_item` | spend | `processing:process` | Clip a source item to an AOI |
| `processing.create` | spend | `processing:process` | Create a job (dispatch later) |
| `processing.create_and_dispatch` | spend | `processing:process` | Create + dispatch atomically |
| `processing.dispatch` | spend | `processing:process` | Dispatch a pending job |
| `processing.jobs.list` / `processing.jobs.get` | read | `processing:read` | Track jobs |

### Clip an item to an AOI

The most common job: cut a source item down to your area of interest as a new item.

```bash
curl -s -X POST https://api.geopera.com/v1/op/clip.create_from_item \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: c0ff...-unique" \
  -d '{
    "source_item_id": "it_3a9c...",
    "project_id": "your-project-id",
    "aoi_geometry": { "type": "Polygon", "coordinates": [ ... ] },
    "asset_keys": ["data"],
    "output_name": "harbour_clip"
  }'
```

```json
{
  "job": { "id": "job_77a1...", "status": "pending", "...": "..." },
  "assets_to_clip": 1,
  "asset_keys": ["data"]
}
```

The job reserves credits on creation, runs on a worker, and registers its outputs back
into your project — each output item carries a provenance edge to the job and to the
source item it was clipped from.

### Create and dispatch a job

For arbitrary registered job types (`job_type_id`), create and dispatch in one atomic,
idempotent call:

```bash
curl -s -X POST https://api.geopera.com/v1/op/processing.create_and_dispatch \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: d15a...-unique" \
  -d '{
    "project_id": "your-project-id",
    "job_type_id": "bathymetry",
    "input_params": { "source_item_id": "it_3a9c...", "...": "job-specific params" },
    "target_collection_id": "optional-collection-id"
  }'
```

```json
{
  "job": { "id": "job_91bd...", "status": "dispatched", "...": "..." },
  "dispatch": { "accepted": true, "...": "worker dispatch result" }
}
```

Use `processing.create` instead when you want to create the job (reserving credits) but
dispatch it later with `processing.dispatch` — a deliberate two-step path for clients
that stage work.

### How a job runs (and reports back)

A dispatched job runs on a registered worker. The worker is handed a **scoped,
job-bound token** — a `worker` principal whose only authority is to register *its own*
job's outputs, nothing else. When it finishes, it calls back through the kernel to
register the produced items, and provenance is recorded at that write. (See the
[principal model](/api-reference/authentication#principals).) This is why a delivered
output always has a complete lineage chain — there is no privileged side door that
skips it.

### COG conventions

For outputs to render and analyse efficiently, processing writes Cloud-Optimized
GeoTIFFs with overviews and embedded statistics (ZSTD compression, internal tiling,
`STATISTICS=YES`). Uploads you bring in should follow the same convention — the
platform then auto-computes render rescaling from the embedded stats, so every client
gets correct visuals with no per-client logic.

## Analytics

Analytics runs through **one** operation:

```
POST /v1/op/analytics.execute
```

The body names the method and passes its parameters:

```json
{
  "operation": "<method name>",
  "params": { "...": "method-specific parameters" }
}
```

The parameters are validated against *that method's* own schema, so a bad request fails
with `422` carrying the expected schema. Discover the available methods and their
schemas with the read operations `analytics.operations.list` and
`analytics.operations.get`.

Built-in methods include:

| Method | What it does |
|---|---|
| `zonal_stats` | Summary statistics (min/max/mean/percentiles) of a band or index over an AOI |
| `time_series` | A value (e.g. an index) sampled across an item's time dimension |
| `change_detection` | The change in a spectral index between a *before* and *after* item |

### Change detection over two items

Change detection compares a spectral index between two acquisitions — the canonical
"what changed here between these dates" question:

```bash
curl -s -X POST https://api.geopera.com/v1/op/analytics.execute \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "change_detection",
    "params": {
      "before_item_id": "it_jan...",
      "after_item_id": "it_jun...",
      "index_name": "NDVI",
      "change_threshold": 0.2,
      "geometry": { "type": "Polygon", "coordinates": [ ... ] }
    }
  }'
```

`band_mapping` is auto-detected from the items' band names when omitted; pass it
explicitly (e.g. `{ "RED": 1, "NIR": 4 }`) to override. The response carries the change
statistics and a reference to the rendered change raster.

### Running over many items

To analyse a stack — many dates over the same AOI — call `analytics.execute` per pair
(or per item) and aggregate, or use `time_series` to sample one item's time dimension
in a single call. Because each call is independent and stateless, fanning out across
items is just concurrent requests; there is no special batch endpoint to learn.

## Extending analytics — adding a new "process"

Analytics methods are **pluggable**. A method is a small class that declares its `name`,
its typed `input_model`, and an `execute()` — for example `change_detection` and
`zonal_stats` are each one such class. Registering a new one makes it:

- callable immediately through the same `analytics.execute` operation (no new route),
- discoverable through `analytics.operations.list` / `.get` (its schema is published),
- available to **every client at once** — REST, the Python and TypeScript SDKs, and the
  MCP agent tools — because they're all projections of the same registry.

So adding a new process to apply over one or many assets (a new index, a new detector, a
classifier) is a localized change: write the method, declare its schema, and the entire
multi-client surface gains it with the same auth, validation, and result contract. This
is the practical payoff of the operation model — capability grows without growing the
API's shape.
