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 link here. You never set scopes on a request — they are derived from your credential, a minted API key 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 wildcarditems:* 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 for the principal model and 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}.

ActionMeaningExample operations
readList, get, search, render, download, validate — anything non-mutatingitems.list, orders.get, catalog.search
writeCreate, update, delete, place, mutate stateitems.create, orders.place, collections.update
processLaunch compute — analytics, processing jobs, clips from itemsprocessing.create, analytics.execute, clip.create_from_item
destroyHard-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.

Imagery & catalog

ScopeOpsRepresentative operations
items:read22items.get, items.list, items.search, items.search_org, items.tile.render, items.list_assets, stac.search, orders.estimate
items:write10items.create, items.update, items.delete, items.duplicate, assets.delete, visualization.profile.create
catalog:read14catalog.search, catalog.federated_search, catalog.search_stream, catalog.sources.list, glossary.collections.list, glossary.providers.get
tiles:read16cog.tile.render, cog.statistics, cog.thumbnail, catalog.tile.render, catalog.viz.landsat, visualization.list_for
collections:read2collections.get, collections.list
collections:write3collections.create, collections.update, collections.delete
band_formulas:read2band_formulas.get, band_formulas.list
band_formulas:write3band_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

ScopeOpsRepresentative operations
orders:read14orders.get, orders.list, orders.archive.estimate, orders.coverage.get, orders.tasking.feasibility_check, orders.tasking.quotations.list, organizations.commitments.list
orders:write9orders.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

ScopeOpsRepresentative operations
processing:read9processing.job.get, processing.jobs.list, processing.catalog.list, processing.catalog.estimate, processing.job_types.list
processing:process6processing.create, processing.create_and_dispatch, processing.dispatch, processing.execute, processing.job.register, clip.create_from_item
analytics:read6analytics.indices.list, analytics.index.get, analytics.sensors.list, analytics.validate_formula, analytics.operations.list
analytics:process3analytics.execute, analytics.band_math, analytics.calculate_index
clip:read9clip.jobs.list, clip.job.get, clip.job.download, clip.area.metadata, clip.areas.list, processing.job.tile_info
clip:write1clip.create_from_area
clip:destroy2clip.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

ScopeOpsRepresentative operations
uploads:write5uploads.initiate, uploads.signed_url, uploads.complete, uploads.progress, uploads.fail
provenance:read1provenance.get
reports:read1reports.generate
usage:read2usage.current, usage.history
usage:write1usage.recalculate_storage

Projects & organizations

ScopeOpsRepresentative operations
projects:write7projects.create, projects.update, projects.archive, projects.delete, projects.add_member, projects.remove_member, projects.update_member
organizations:write1organizations.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

ScopeOpsRepresentative operations
shares:read3share.link.validate, share.tile.render, share.tilejson
shares:write2share.link.create, share.link.revoke
alerts:read4alerts.rules.list, alerts.rule.get, alerts.events.list, alerts.test_rule
alerts:write4alerts.create_rule, alerts.update_rule, alerts.delete_rule, alerts.acknowledge_event
event_subscriptions:read2event_subscriptions.get, event_subscriptions.list
event_subscriptions:write4event_subscriptions.create, event_subscriptions.update, event_subscriptions.delete, event_subscriptions.test
notifications:read2notifications.list, notifications.unread_count
notifications:write3notifications.mark_read, notifications.mark_all_read, notifications.dismiss

Billing, EULAs & API keys

ScopeOpsRepresentative operations
billing:read11billing.credits.balance, billing.invoices.list, billing.plans.list, billing.status.get, payment_methods.list, organizations.commitments.statement
billing:write10billing.topup, billing.set_auto_topup, billing.approvals.request, payment_methods.attach, payment_methods.set_default, payment_methods.detach
eulas:read4eulas.list, eulas.get, eulas.documents.list, eulas.document.get
eulas:write1eulas.document.accept
api_keys:read1api_keys.list
api_keys:write2api_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.

ScopeOpsRepresentative operations
clip:read9(designated clip user only)
clip:write1clip.create_from_area
clip:destroy2clip.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 (prefix gpra_) carries up to three capability flags, set when the key is created:

CapabilityGrants action
can_readread across every non-privileged resource
can_writewrite across every non-privileged resource
can_processprocess 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 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 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 — bearer tokens, the gpra_ API-key prefix, and minting keys with capabilities.
  • Concepts — the principal model and the operation-name convention.
  • Errors — the full problem+json taxonomy, including the two 403 causes.
  • Operations — the complete operation index, each linking back to its scope here.