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:
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:readand the op requiresitems:read; - a full wildcard — the principal holds
*(service-role / internal only); - a resource wildcard —
items:*satisfiesitems:read,items:write, and any futureitems:action; - an action wildcard —
*:readsatisfiesitems: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}.
| 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.
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 (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_writeorcan_processimplycan_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 onorders:readeven for its write steps). You do not need to grant read separately.
# 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:*andclip:*.
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:
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/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.deleteisitems:write, not aDELETE. AlwaysPOST /v1/op/{operation_id}. There are no GET/PUT/PATCH/DELETE operations. processanddestroyare notwrite. Acan_writekey cannot launch processing (processing:process) or delete a job (clip:destroy) unless it also hascan_process/ an explicit destroy scope.- Capability flags can’t reach privileged scopes. No combination of
can_read/can_write/can_processgrantsadmin:*orclip:*. 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
403causes. - Operations — the complete operation index, each linking back to its scope here.