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

OperationSide-effectScopeUse
clip.create_from_itemspendprocessing:processClip a source item to an AOI
processing.createspendprocessing:processCreate a job (dispatch later)
processing.create_and_dispatchspendprocessing:processCreate + dispatch atomically
processing.dispatchspendprocessing:processDispatch a pending job
processing.jobs.list / processing.jobs.getreadprocessing:readTrack 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.) 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:

MethodWhat it does
zonal_statsSummary statistics (min/max/mean/percentiles) of a band or index over an AOI
time_seriesA value (e.g. an index) sampled across an item’s time dimension
change_detectionThe 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.