Orbit Commerce
Plugin guides

OAuth scopes

Scopes are the permissions a plugin requests and a merchant grants. They define exactly what your plugin can read and write on a store.

What a scope is

A scope is a single permission string of the form <resource>:<action>. Each scope authorises one kind of operation on one kind of resource — for example, product:read lets your plugin read products, and order:create lets it create orders. Your plugin holds a set of scopes, and every API endpoint requires specific scopes to call it.

Scopes flow through four stages, from declaration to enforcement.

  1. Request — You declare the scopes your plugin needs in its manifest under oauth.scopes. This is part of the plugin definition you create in the partner dashboard.
  2. Consent — When a merchant installs your plugin, they see the requested scopes and consent to them. Installation is how the grant happens.
  3. Grant — The consented set becomes the scopes claim inside every token issued for that install (both session and access tokens carry it).
  4. Enforce — Each API endpoint checks the token's scopes claim. If the required scope is present, the call proceeds; if not, it is rejected.
{
  "oauth": {
    "scopes": ["product:list", "product:read", "order:read", "webhook:create"]
  }
}

The token your plugin uses carries exactly this granted set:

{
  "sub": "<StorePlugin id>",
  "storeId": "<store uuid>",
  "pluginId": "your-plugin-id",
  "scopes": ["product:list", "product:read", "order:read", "webhook:create"],
  "type": "plugin_access"
}

Naming convention

Every scope follows one rule:

<resource>:<action>
  • resource is singular and kebab-caseproduct, order, customer, taxonomy, tracking-script, webhook. Never plural.
  • action is one of exactly five verbs:
ActionMeaning
listEnumerate many records
readRead a single record
createCreate a record
updateModify a record
deleteRemove a record

So product:list, customer:read, and order:create are all valid.

Common mistakes

The two most frequent errors are pluralising the resource and inventing an action verb.

WrongRightWhy
products:readproduct:readResource is singular, not plural
orders:listorder:listResource is singular, not plural
order:writeorder:create / order:updatewrite is not an action — use create or update
product:readonlyproduct:readreadonly is not an action verb

If a scope string does not parse to a known <resource>:<action> pair, it will not grant any access.

Common scopes by resource

The following are frequently used scopes. The complete, authoritative list is the Scope Catalog.

ResourceScopes
productproduct:list, product:read, product:create, product:update
orderorder:list, order:read, order:create
customercustomer:list, customer:read
taxonomytaxonomy:list, taxonomy:read
webhookwebhook:create, webhook:list, webhook:read, webhook:delete

Webhook subscriptions require both a webhook scope and the topic's own scope. To subscribe to product.* topics you need webhook:create plus product:read. See the webhooks guide for the full topic-to-scope mapping.

Request the minimum

Request only the scopes your plugin actually uses. A smaller scope set is easier for merchants to approve and reduces what is at risk if a token leaks.

  • If you only read products, request product:read (and product:list if you enumerate them) — not product:update.
  • Don't request write scopes (create / update / delete) when your plugin only reads.
  • Add scopes as your plugin grows rather than requesting broad access up front. Note that expanding the requested set means merchants must re-consent on install.

Check granted scopes at runtime

A merchant may not grant every scope you requested, so check what you actually hold before calling a guarded endpoint. The granted set lives in your token's scopes claim.

On your backend, read it from the verified token:

import { OrbitClient } from '@orbitcommerce/sdk'

const { scopes } = await OrbitClient.verifyToken(token)

if (scopes.includes('order:read')) {
  // safe to read orders
}

In the iframe, the claim travels inside the session JWT returned by orbit.getToken(). Decode its payload to inspect scopes — it is a standard JWT, so any base64url/JWT decoder works:

const orbit = new OrbitClient()
await orbit.ready()

const [, payload] = orbit.getToken().split('.')
const { scopes } = JSON.parse(atob(payload)) as { scopes: string[] }

if (scopes.includes('order:read')) {
  const orders = await orbit.query(/* ... */)
} else {
  orbit.toast({ message: 'Order access not granted', type: 'warning' })
}

Use these checks to hide or disable features the merchant has not authorised, rather than letting a call fail at the API.

Enforcement errors

When a guarded endpoint rejects a call, the status code tells you which stage failed.

StatusMeaningWhat to do
401The token is missing, malformed, or expiredRe-authenticate. In the iframe, ensure await orbit.ready() has resolved; on the backend, refresh the access token. See authentication.
403The token is valid but lacks the required scopeAdd the scope to your manifest's oauth.scopes and have the merchant re-install/re-consent.

A 401 is an authentication problem; a 403 is a scope problem. Distinguishing them tells you whether to refresh a token or request a scope.

Reference

  • Scope Catalog — the complete list of available scopes.
  • API Reference — the exact scope required by each endpoint.
  • Authentication — how tokens are issued, refreshed, and verified.
  • Webhooks — topic subscriptions and their required scopes.