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

# Scopes & authorization

Every one of the 227 operations declares exactly one scope — a `resource:action` string such as `items:read`, `orders:write`, or `processing:process` — and before an operation runs the API checks that your principal holds it, otherwise the call is rejected with `403 Forbidden` before any work happens.

## How scope checks work

Each operation declares the single scope it requires; this page is the authoritative source for that mapping, and every SDK and the [operation index](/api-reference/operations) link here. You never set scopes on a request — they are derived from your credential, a [minted API key](/api-reference/authentication) or a session token (obtained by signing in to Geopera), and checked automatically.

The API resolves a _principal_ from your `Authorization` bearer, computes the set of scopes that principal holds, and then, for the operation you invoked, runs a single check:

```python
if not principal.has_scope(op.required_scope):
    raise ScopeDenied(f"missing scope {op.required_scope!r} for {operation_id!r}")
```

`has_scope` grants a required scope by any of:

- an **exact match** — the principal holds `items:read` and the op requires `items:read`;
- a **full wildcard** — the principal holds `*` (service-role / internal only);
- a **resource wildcard** — `items:*` satisfies `items:read`, `items:write`, and any future `items:` action;
- an **action wildcard** — `*:read` satisfies `items:read`, `catalog:read`, and every other `:read`.

So the unit you are granted is usually a _wildcard_ (e.g. `items:*` or `*:write`), and the unit an operation _requires_ is always a concrete `resource:action`. The scope check is purely about capability — owning `items:read` does not grant you access to _another org's_ items. Per-resource ownership (does this item belong to your project / organization?) is enforced separately inside each operation and surfaces as its own `403`/`404`. See [Concepts](/api-reference/concepts) for the principal model and [Errors](/api-reference/errors) for the failure shapes.

## The action verbs

Across the whole surface there are four actions. The action is encoded in the operation name, never in an HTTP verb — every operation is a `POST /v1/op/{operation_id}`.

| Action    | Meaning                                                               | Example operations                                                |
| --------- | --------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `read`    | List, get, search, render, download, validate — anything non-mutating | `items.list`, `orders.get`, `catalog.search`                      |
| `write`   | Create, update, delete, place, mutate state                           | `items.create`, `orders.place`, `collections.update`              |
| `process` | Launch compute — analytics, processing jobs, clips from items         | `processing.create`, `analytics.execute`, `clip.create_from_item` |
| `destroy` | Hard-delete a compute artifact (distinct from `write`)                | `clip.job.delete`, `processing.job.delete`                        |

`process` and `destroy` are separated from `write` deliberately: launching billable compute and tearing down job artifacts are higher-consequence than ordinary mutations, so a key can be granted writes without being granted the ability to spend on processing or to destroy jobs.

## Scope catalog

Below is every scope, grouped by resource domain. The **Ops** column is the count of operations gated by that scope; representative operation IDs follow. Resource domains marked **privileged** are not part of the self-serve surface — see [Privileged scopes](#privileged-scopes).

### Imagery & catalog

| Scope                 | Ops | Representative operations                                                                                                                            |
| --------------------- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items:read`          | 22  | `items.get`, `items.list`, `items.search`, `items.search_org`, `items.tile.render`, `items.list_assets`, `stac.search`, `orders.estimate`            |
| `items:write`         | 10  | `items.create`, `items.update`, `items.delete`, `items.duplicate`, `assets.delete`, `visualization.profile.create`                                   |
| `catalog:read`        | 14  | `catalog.search`, `catalog.federated_search`, `catalog.search_stream`, `catalog.sources.list`, `glossary.collections.list`, `glossary.providers.get` |
| `tiles:read`          | 16  | `cog.tile.render`, `cog.statistics`, `cog.thumbnail`, `catalog.tile.render`, `catalog.viz.landsat`, `visualization.list_for`                         |
| `collections:read`    | 2   | `collections.get`, `collections.list`                                                                                                                |
| `collections:write`   | 3   | `collections.create`, `collections.update`, `collections.delete`                                                                                     |
| `band_formulas:read`  | 2   | `band_formulas.get`, `band_formulas.list`                                                                                                            |
| `band_formulas:write` | 3   | `band_formulas.create`, `band_formulas.update`, `band_formulas.delete`                                                                               |

Note that `stac.*` and the read-only `orders.estimate` / `orders.get_schema` / `orders.list_assets` operations live under `items:read`, and the `glossary.*` reference operations live under `catalog:read`. Visualization profile reads use `items:read`; profile writes use `items:write`; the cross-item `visualization.list_for` uses `tiles:read`.

### Orders & tasking

| Scope          | Ops | Representative operations                                                                                                                                                                                 |
| -------------- | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orders:read`  | 14  | `orders.get`, `orders.list`, `orders.archive.estimate`, `orders.coverage.get`, `orders.tasking.feasibility_check`, `orders.tasking.quotations.list`, `organizations.commitments.list`                     |
| `orders:write` | 9   | `orders.place`, `orders.archive.place`, `orders.tasking.place`, `orders.cancel`, `orders.update`, `orders.tasking.feasibility_decide`, `orders.tasking.quotation_decide`, `orders.tasking.templates.save` |

Every order operation — including reads like `orders.tasking.feasibility_check` — gates on `orders:read`, so a key that can place orders also needs read to walk the workflow. Write/process keys are granted read automatically (see below), so this is transparent for API keys.

### Processing, analytics & clips

| Scope                | Ops | Representative operations                                                                                                                              |
| -------------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `processing:read`    | 9   | `processing.job.get`, `processing.jobs.list`, `processing.catalog.list`, `processing.catalog.estimate`, `processing.job_types.list`                    |
| `processing:process` | 6   | `processing.create`, `processing.create_and_dispatch`, `processing.dispatch`, `processing.execute`, `processing.job.register`, `clip.create_from_item` |
| `analytics:read`     | 6   | `analytics.indices.list`, `analytics.index.get`, `analytics.sensors.list`, `analytics.validate_formula`, `analytics.operations.list`                   |
| `analytics:process`  | 3   | `analytics.execute`, `analytics.band_math`, `analytics.calculate_index`                                                                                |
| `clip:read`          | 9   | `clip.jobs.list`, `clip.job.get`, `clip.job.download`, `clip.area.metadata`, `clip.areas.list`, `processing.job.tile_info`                             |
| `clip:write`         | 1   | `clip.create_from_area`                                                                                                                                |
| `clip:destroy`       | 2   | `clip.job.delete`, `processing.job.delete`                                                                                                             |

`clip.create_from_item` sits under `processing:process` (it launches compute against an item), while `clip.create_from_area` sits under `clip:write`. The `clip:*` resource is **privileged** — see below.

### Uploads, provenance, reports & usage

| Scope             | Ops | Representative operations                                                                        |
| ----------------- | --- | ------------------------------------------------------------------------------------------------ |
| `uploads:write`   | 5   | `uploads.initiate`, `uploads.signed_url`, `uploads.complete`, `uploads.progress`, `uploads.fail` |
| `provenance:read` | 1   | `provenance.get`                                                                                 |
| `reports:read`    | 1   | `reports.generate`                                                                               |
| `usage:read`      | 2   | `usage.current`, `usage.history`                                                                 |
| `usage:write`     | 1   | `usage.recalculate_storage`                                                                      |

### Projects & organizations

| Scope                 | Ops | Representative operations                                                                                                                              |
| --------------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `projects:write`      | 7   | `projects.create`, `projects.update`, `projects.archive`, `projects.delete`, `projects.add_member`, `projects.remove_member`, `projects.update_member` |
| `organizations:write` | 1   | `organizations.create`                                                                                                                                 |

There is no `projects:read` scope: project reads are folded into the resources they return (items, orders, jobs). Membership in the project or organization is still enforced per-operation.

### Sharing, alerts, events & notifications

| Scope                       | Ops | Representative operations                                                                                            |
| --------------------------- | --- | -------------------------------------------------------------------------------------------------------------------- |
| `shares:read`               | 3   | `share.link.validate`, `share.tile.render`, `share.tilejson`                                                         |
| `shares:write`              | 2   | `share.link.create`, `share.link.revoke`                                                                             |
| `alerts:read`               | 4   | `alerts.rules.list`, `alerts.rule.get`, `alerts.events.list`, `alerts.test_rule`                                     |
| `alerts:write`              | 4   | `alerts.create_rule`, `alerts.update_rule`, `alerts.delete_rule`, `alerts.acknowledge_event`                         |
| `event_subscriptions:read`  | 2   | `event_subscriptions.get`, `event_subscriptions.list`                                                                |
| `event_subscriptions:write` | 4   | `event_subscriptions.create`, `event_subscriptions.update`, `event_subscriptions.delete`, `event_subscriptions.test` |
| `notifications:read`        | 2   | `notifications.list`, `notifications.unread_count`                                                                   |
| `notifications:write`       | 3   | `notifications.mark_read`, `notifications.mark_all_read`, `notifications.dismiss`                                    |

### Billing, EULAs & API keys

| Scope            | Ops | Representative operations                                                                                                                                     |
| ---------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `billing:read`   | 11  | `billing.credits.balance`, `billing.invoices.list`, `billing.plans.list`, `billing.status.get`, `payment_methods.list`, `organizations.commitments.statement` |
| `billing:write`  | 10  | `billing.topup`, `billing.set_auto_topup`, `billing.approvals.request`, `payment_methods.attach`, `payment_methods.set_default`, `payment_methods.detach`     |
| `eulas:read`     | 4   | `eulas.list`, `eulas.get`, `eulas.documents.list`, `eulas.document.get`                                                                                       |
| `eulas:write`    | 1   | `eulas.document.accept`                                                                                                                                       |
| `api_keys:read`  | 1   | `api_keys.list`                                                                                                                                               |
| `api_keys:write` | 2   | `api_keys.create`, `api_keys.revoke`                                                                                                                          |

### Privileged scopes

The `clip` resource is **not** part of the self-serve surface. `clip` scopes are granted only to the designated clip user — a normal API key never inherits them through a broad capability, and a member never gets them from ordinary membership.

| Scope          | Ops | Representative operations                  |
| -------------- | --- | ------------------------------------------ |
| `clip:read`    | 9   | (designated clip user only)                |
| `clip:write`   | 1   | `clip.create_from_area`                    |
| `clip:destroy` | 2   | `clip.job.delete`, `processing.job.delete` |

Geopera also runs administrative and internal operations (billing reconciliation, data retention, scheduled jobs, and the like). These require an `admin:*` scope that is never granted to customer API keys or ordinary members, so they are omitted from this reference; calling one with a customer credential returns `403`.

## How credentials map to scopes

Your scope set is derived from the _kind_ of principal behind your bearer token.

### API keys

A [minted API key](/api-reference/authentication) (prefix `gpra_`) carries up to three capability flags, set when the key is created:

| Capability    | Grants action                                  |
| ------------- | ---------------------------------------------- |
| `can_read`    | `read` across every non-privileged resource    |
| `can_write`   | `write` across every non-privileged resource   |
| `can_process` | `process` across every non-privileged resource |

These expand to **action-wildcard** scopes over the self-serve resources. A key with `can_read` holds the equivalent of `*:read`; a key with `can_write` holds `*:write`; a key with `can_process` holds `*:process` — but only over non-privileged resources. Critically, an API key can **never** reach `admin:*` or `clip:*` through these capability flags, no matter how broad. A privileged resource is reachable by a key only through an explicit _stored_ scope provisioned on the key itself.

Two convenience rules:

- A key with **no** capabilities still gets `read` (the floor), so every key can at least list and get.
- `can_write` or `can_process` **imply** `can_read`. A write/process key can also estimate, list, and walk read steps of a workflow, so a single key runs an end-to-end flow (for example, the whole order workflow gates on `orders:read` even for its write steps). You do not need to grant read separately.

```bash
# A key created with can_read + can_process holds, effectively:
#   *:read   (every non-privileged :read scope)
#   *:process (every non-privileged :process scope)
# It can search the catalog, launch processing, and list jobs —
# but it cannot orders.place (needs orders:write) and cannot
# touch any admin:* or clip:* operation.

curl -s https://api.geopera.com/v1/op/processing.create \
  -H "Authorization: Bearer gpra_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"project_id": "...", "job_type": "ndvi", "item_ids": ["..."]}'
```

### Session tokens (human users)

A human user authenticated with a session token (obtained by signing in to Geopera) receives the full **self-serve standard set**: one `{resource}:*` wildcard for every non-privileged resource. In practice a logged-in member can invoke any non-admin, non-clip operation their org and project membership allow — the scope check is satisfied, and access is then narrowed to _their_ resources by the per-operation ownership checks.

On top of that floor:

- The designated **clip user** (a configured account) additionally holds `clip:*`.
- A **system admin** additionally holds `admin:*` and `clip:*`.

Project and organization **roles** (`owner`, `admin`, `member`) do not change your _scope_ set — they govern per-resource ownership and the privileged role checks inside individual operations (for example, only an org `admin`/`owner` can run organization-admin operations). Scopes answer "can this principal perform this _class_ of action at all?"; roles and membership answer "on _this specific resource_?". Both must pass.

### Workers and service-role

Internal **worker** tokens hold exactly `processing:process` — enough to register their own job's outputs and nothing else. The **service-role** identity holds `*` (full trust) and is for internal/system use only. Neither is something you provision; they are listed for completeness.

## What a denied call returns

A missing scope produces an [RFC 9457 problem+json](/api-reference/errors) response with status `403`:

```http
POST /v1/op/orders.place HTTP/1.1
Host: api.geopera.com
Authorization: Bearer gpra_a_read_only_key
Content-Type: application/json

{ "project_id": "...", "items": ["..."] }
```

```http
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "type": "https://api.geopera.com/problems/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "missing scope 'orders:write' for 'orders.place'"
}
```

The `detail` names the exact scope you lacked and the operation you called, so you can read off precisely which capability or role is missing. The check runs **before** any side effect — a denied write never mutates state and is never billed.

Distinguish this from the _other_ `403` you can receive: an ownership failure. If you hold `items:read` but ask for an item in another organization's project, the scope check passes and the operation's own ownership check raises a `403` with a detail like `Access denied`. Same status, different cause — the `detail` tells them apart. See [Errors](/api-reference/errors) for the full taxonomy.

## Gotchas

- **One scope per operation.** Operations never require two scopes. If a call needs both reading and writing (e.g. an order workflow), each underlying operation has its own single scope; you hold the union across your credential.
- **The action lives in the name, not the verb.** `items.delete` is `items:write`, not a `DELETE`. Always `POST /v1/op/{operation_id}`. There are no GET/PUT/PATCH/DELETE operations.
- **`process` and `destroy` are not `write`.** A `can_write` key cannot launch processing (`processing:process`) or delete a job (`clip:destroy`) unless it also has `can_process` / an explicit destroy scope.
- **Capability flags can't reach privileged scopes.** No combination of `can_read`/`can_write`/`can_process` grants `admin:*` or `clip:*`. That is by design to prevent cross-tenant escalation.
- **Scope ≠ ownership.** Passing the scope check does not mean you can touch a given resource. Membership and role checks run per-operation and surface their own `403`/`404`.

## Related

- [Authentication](/api-reference/authentication) — bearer tokens, the `gpra_` API-key prefix, and minting keys with capabilities.
- [Concepts](/api-reference/concepts) — the principal model and the operation-name convention.
- [Errors](/api-reference/errors) — the full problem+json taxonomy, including the two `403` causes.
- [Operations](/api-reference/operations) — the complete operation index, each linking back to its scope here.
