OAuth Scope Catalog
The scope families a plugin or store API key works with day to day on the Orbit Commerce public API (api.myorbitcommerce.net/v1/*, plus the /oauth/* token handshake). Most scopes are requestable by a third-party plugin; the remainder are labelled with the credential that can hold them. When a merchant installs your plugin they consent to the scopes you request; the granted set becomes the scopes claim on every token your plugin receives, and each endpoint checks for the scope it needs.
The same scope strings govern store API keys: the merchant picks the key's scopes at creation instead of consenting at install. Every resource scope below is grantable to a key, with three exceptions, each labelled where it appears:
- Plugin-only (
billing:*,email:send,settings:*): these only make sense for an installed plugin and are rejected for keys. - Assistant-only (
page:*,store-settings:*): granted exclusively through the consent flow a merchant completes when connecting an AI assistant to their store; not offered in the key scope picker, not requestable by plugins. See Assistant-granted scopes. - Reserved (
import:*): named in the catalog but grantable to no credential today. See Reserved scopes.
This page is the public contract for the families it covers: every scope string here is exactly the string the API enforces, so copy them verbatim. It is not yet exhaustive — /v1 enforces further families (inventory, shipping, discounts, tags, gift cards, returns, quotes, suppliers and more) that this page has still to document. For how scopes are requested, consented to, and checked at runtime, see the Scopes guide; for the endpoint-by-endpoint detail — including the families not yet listed here — see the API Reference.
Naming convention. Scopes are
<resource>:<action>. The resource is singular and kebab-case (product,customer,tracking-script— neverproductsortrackingScripts). The action is usually one oflist,read,create,update,delete; a small number of scopes use a purpose verb where a CRUD action would be misleading (order:fulfill,email:send,cart:adjust), and content resources with a draft/live split addpublish(page:publish,blog-post:publish). Soproduct:writeis invalid (writeis not an action — useupdate), andproducts:readis invalid (the resource must be singular).
A call made without a token — or with an invalid or expired one — returns 401 Unauthorized. A call made with a valid token that is missing the required scope also returns 401 Unauthorized; the error body names the missing scope (Missing required scopes: …). The one exception is subscribing to a webhook topic without the topic's scope, which returns 403 Forbidden.
Products — product:*
The product catalogue: variants, prices, inventory, images, and taxonomy associations. The most-used scope set for sync plugins (marketplaces, feed generators, import/export, reviews, search).
| Scope | Grants |
|---|---|
product:list | GET /v1/products (filtered, paginated), GET /v1/products/sync (lightweight sync variant). Required for any plugin that iterates the catalogue. |
product:read | GET /v1/products/{id}, POST /v1/products/batch-lookup. Pair with product:list for plugins that fetch detail after listing. |
product:create | POST /v1/products. Used by import plugins and marketplace inbound (Amazon → Orbit, eBay → Orbit). |
product:update | PATCH /v1/products/{id}, PATCH /v1/products/bulk-update. Used by sync plugins to push price, inventory, and SEO updates from external sources. |
A plugin that only listens to product events through webhooks (product.created, product.updated) needs webhook:create to subscribe plus product:read for the topic. The webhook payload includes whatever your read scopes allow you to see.
Orders — order:*
Reads, create-on-behalf-of and status sync for marketplace plugins, plus fulfilment with tracking for shipping/3PL plugins.
| Scope | Grants |
|---|---|
order:list | GET /v1/orders. Iteration for sync and reporting plugins, with filters for status, payment status, fulfilment state, created-date window, and updatedFrom for incremental sync. |
order:read | GET /v1/orders/{id}, GET /v1/orders/{id}/fulfillments. Order and fulfilment detail. |
order:create | POST /v1/orders. Used by marketplace plugins that import orders from an external platform as native Orbit orders. |
order:update | PATCH /v1/orders/{id} (sync an order's status, paymentStatus, or notes; set custom metadata — merged key-by-key, null removes a key, source/externalId are protected; toggle archived, idempotently), POST /v1/orders/{id}/tags (find-or-create tags by name and attach them; overwrite: true with an empty list removes every tag). |
order:fulfill | POST /v1/orders/{id}/fulfillments (fulfil an order fully or partially with tracking number/URL/carrier; omit items to fulfil every remaining quantity), PATCH /v1/orders/{id}/fulfillments/{fulfillmentId} (correct tracking on a fulfilled order, mark delivered, or cancel/void a shipment). |
order:update is deliberately narrow: pricing, line items, currency, and addresses are not editable through it (and a paid + fulfilled order seals those fields). order:fulfill is separate from order:update because fulfilling has customer-facing side effects — creating a fulfilment with tracking sends the store's Order Shipped email/SMS unless you pass notifyCustomer: false (do that when your plugin sends its own notification). Refunds and cancellation workflows stay owned by the storefront and dashboard.
Customers — customer:*
Read and write customer records — list and detail, plus create, update, and delete on behalf of a merchant (for example, mirroring a CRM into Orbit, or resolving and enriching buyer identity during order import).
| Scope | Grants |
|---|---|
customer:list | GET /v1/customers. List the customer base — used by marketing/CRM plugins. |
customer:read | GET /v1/customers/{id}, GET /v1/customers/by-email/{email}. Customer detail (including tags), and buyer-identity lookup by email (e.g. resolving a customer during order import, or reviews — "which customer left this review?"). Also the topic scope for the customer.* webhook topics. |
customer:create | POST /v1/customers. Create a customer — e.g. mirroring a CRM contact into Orbit, or registering a buyer imported from a marketplace. |
customer:update | PATCH /v1/customers/{id} (update a customer's details), POST /v1/customers/{id}/tags (find-or-create tags by name and attach them — mirror of the order tags endpoint, with the same overwrite semantics). |
customer:delete | DELETE /v1/customers/{id}. Remove a customer. |
Taxonomies — taxonomy:*
The product classification hierarchy (Orbit calls this "taxonomy"; some platforms call it a "category tree"). Read-only on the public API — taxonomy management happens in the dashboard.
| Scope | Grants |
|---|---|
taxonomy:list | GET /v1/taxonomies. Hierarchy walk for feed plugins (category mapping). |
taxonomy:read | GET /v1/taxonomies/{id}, GET /v1/taxonomies/by-handle/{handle}. |
In Orbit's vocabulary, a taxonomy is the classification tree. What other platforms call a "category" (a curated product collection) is a separate resource with its own scope family: see Categories below.
Categories — category:*
Curated product collections (the dashboard's Categories): manual or smart, each a browsable storefront destination. Distinct from taxonomies, the classification tree.
| Scope | Grants |
|---|---|
category:list | GET /v1/categories (paginated). |
category:read | GET /v1/categories/{id}, with its linked products. |
category:create | POST /v1/categories. |
category:update | PATCH /v1/categories/{id}, plus membership edits on a manual category: POST /v1/categories/{id}/products (assign products) and DELETE /v1/categories/{id}/products/{productId} (remove one). Membership edits are updates of the category, so both run under this scope rather than category:delete. |
category:delete | DELETE /v1/categories/{id}. Deliberately its own scope: a token granted category:update cannot destroy a category. |
A category has no separate publish step: status is writable on create and update, so a category created active is browsable by shoppers on the same request. Hold your own drafts back by creating them inactive.
Brands — brand:*
The merchant's own brands. A brand is a storefront destination, not just a label: /brands/{handle} is a real route, the brand's description, logo, and SEO fields render on it, and the brand's name appears in the storefront product filter rail. (This is unrelated to the supplier-side brand catalogue, which is not on the public API.)
| Scope | Grants |
|---|---|
brand:list | GET /v1/brands (paginated, always ordered by name A-Z; filter with keyword and status). |
brand:read | GET /v1/brands/{id}. Includes productCount, the number of products currently carrying the brand: the number that matters before deleting it. |
brand:create | POST /v1/brands. A new brand's page is reachable by shoppers as soon as the request returns; there is no draft state. |
brand:update | PATCH /v1/brands/{id}. Every writable field is shopper-visible, and renaming the handle moves the brand's public URL (a redirect from the old one is created). |
brand:delete | DELETE /v1/brands/{id} (soft delete). Further-reaching than it looks: every product carrying the brand goes unbranded on the storefront in the same request, the brand leaves the filter rail, and /brands/{handle} stops resolving. There is no restore route. |
Carts — cart:*
On-demand read access to a live cart, plus the ability to own adjustment lines pushed onto one through the synchronous function.cart.adjustments extension point (conditional fees, surcharges — for example, a customs-duty or payment-surcharge plugin).
| Scope | Grants |
|---|---|
cart:read | GET /v1/carts/{cartId} (store-tenanted; the response shape mirrors the function.cart.adjustments callout payload, so you learn one cart model across pull and push). Also the topic scope for the cart.updated webhook topic, whose payload carries ids only — pair the two so a plugin is notified, then fetches full detail. |
cart:adjust | Not a callable endpoint by itself. Gates whether a plugin receives the function.cart.adjustments callout and may add its own adjustment lines back onto the cart; core enforces caps and ownership, so a plugin can only ever touch the lines it added. |
There is deliberately no list endpoint — enumerating a store's live carts is a privacy surface Orbit has not opened.
Custom fields — custom-field:read / custom-field:update
Custom field values on the store's own resources: the columns an import/export or sync plugin needs beyond the built-in fields. Values only, deliberately: definitions (the store's schema) are merchant-managed in the dashboard, and there is no public create/update/delete for them, so a plugin can never mutate a store's schema.
| Scope | Grants |
|---|---|
custom-field:read | GET /v1/custom-field-definitions (paginated; filter by ownerType and namespace), which lists the store's definitions so you can build your column catalogue, and GET /v1/custom-fields/batch, which returns values for up to 100 owners per request (ownerIds comma-separated, plus ownerType; optional namespace filter). |
custom-field:update | POST /v1/custom-fields/set. Batch-upserts up to 25 values per call, keyed on (owner, namespace, key); each value is validated against the matching definition where one exists. |
Owner types span the platform: PRODUCT, PRODUCT_VARIANT, CATEGORY, BRAND, ORDER, DRAFT_ORDER, CUSTOMER, PAGE, BLOG, BLOG_POST, and CRM_DEAL. The store is always resolved from your token; a storeId in the query or body is ignored.
Tracking scripts — tracking-script:*
Per-store conversion-tracking script management (Snapchat Pixel + CAPI, TikTok Pixel, Meta/Facebook Pixel, and similar). These scopes are store-scoped, which the :store suffix denotes.
| Scope | Grants |
|---|---|
tracking-script:read:store | GET /v1/tracking-scripts/{provider}. Reads the active script for one provider on the current store. |
tracking-script:update:store | PUT /v1/tracking-scripts/{provider} (upsert; clear a script by writing an empty value). |
A plugin that manages several providers (for example, a unified ad-pixel plugin) requests both scopes once.
Settings — settings:read:store / settings:update:store
Your plugin's own per-store configuration, as defined by its settingsSchema. Store-scoped (the :store suffix, as with tracking scripts).
| Scope | Grants |
|---|---|
settings:read:store | GET /v1/settings. Returns your plugin's settings merged with the schema defaults. |
settings:update:store | PUT /v1/settings. Partial-merge update; values are validated against your settingsSchema. |
The SDK wraps these as orbit.settings.get(), orbit.settings.getValue(key), and orbit.settings.update({ … }) — see the SDK guide.
Billing — billing:read / billing:update
Plugin monetisation, for plugins that charge merchants. Use the SDK's billing module (orbit.billing.*); these scopes gate what it can do.
| Scope | Grants |
|---|---|
billing:read | Read-only billing: plans, subscription status, and the merchant's saved payment methods (orbit.billing.getPlans(), getSubscriptionStatus(), getPaymentMethods()). |
billing:update | Create charges and manage subscriptions: subscribe, change plan, cancel, restore (orbit.billing.requestPurchase(), subscribeToPlan(), cancelSubscription(), restoreSubscription()). |
A plugin that only displays pricing without taking payment needs billing:read only. See the billing section of the SDK guide for the full flow.
Email — email:send
Send transactional email through the platform's email service, so your plugin's mail goes out through the store's verified sender identity (DKIM), suppression list, and warm-up handling instead of hand-rolled SMTP.
| Scope | Grants |
|---|---|
email:send | POST /v1/emails/send (to, subject, html, optional replyTo). Sends from the store's verified sender when one is configured, falling back to the platform default address. |
Use it for merchant-facing notifications (sync reports, alerts) and store-branded customer messages your plugin owns. It is not a bulk-campaign API — sends inherit the platform's suppression and rate handling.
Outbound webhooks — webhook:*
Subscribe to store events for delivery to your plugin's own HTTPS endpoint. Most plugins that subscribe at runtime request all four scopes so they can manage their own subscriptions through the API. To subscribe to a topic you also need that topic's resource scope (for example, product:read for product.*) — see the Webhooks guide.
| Scope | Grants |
|---|---|
webhook:list | GET /v1/webhooks. List your subscriptions. |
webhook:read | GET /v1/webhooks/{id}/deliveries. Recent delivery history for a subscription (useful for debugging). |
webhook:create | POST /v1/webhooks. Subscribe to a topic at runtime. |
webhook:delete | DELETE /v1/webhooks/{id}. Unsubscribe. |
Navigation — navigation:*
Storefront menus and the links inside them. One family covers the menu and its items: an item cannot exist without its menu, so there is no separate navigation-item:*. Menus have no draft state and the storefront resolves them by handle, which makes every write here live: a menu edit is read by shoppers immediately.
| Scope | Grants |
|---|---|
navigation:list | GET /v1/navigations (paginated, ordered by title A-Z; filter with keyword). Menu items are not included; read one menu to get its tree. |
navigation:read | GET /v1/navigations/{id}: one menu with its full nested item tree. |
navigation:create | POST /v1/navigations. The body may carry the whole nested items tree, so a menu can arrive fully populated. If its handle is one the theme asks for (say main-menu), it fills the storefront header on the creating request. |
navigation:update | PATCH /v1/navigations/{id} (retitle, or rename the handle, which is what a theme looks a menu up by), POST /v1/navigations/{id}/items (add a link), PATCH /v1/navigations/{id}/items/{itemId} (edit one), POST /v1/navigations/{id}/items/{itemId}/move (reorder/reparent). |
navigation:delete | DELETE /v1/navigations/{id} and DELETE /v1/navigations/{id}/items/{itemId}. Hard deletes: there is no soft delete or restore path for menus, deleting a menu takes every item with it, and deleting a single item takes its entire submenu. The item delete deliberately requires this scope, not navigation:update: "edit my menus" must not include "destroy a submenu". |
Blogs — blog:*
The containers a store's articles are filed under. Most stores have one ("News", seeded at store creation) and only need more for genuinely separate publications. A blog is not itself a storefront page (there is no /blogs/{handle} route), but a store's blogs are listed on shopper-reachable surfaces, so creating one is visible without any post inside it. Posts are a separate scope family (blog posts below), so granting "write my articles" does not grant "delete the container they live in".
| Scope | Grants |
|---|---|
blog:list | GET /v1/blogs (paginated, ordered by title A-Z; filter with keyword). |
blog:read | GET /v1/blogs/{id}. Includes postCount, the number of posts currently filed under the blog: the number that matters before deleting it. |
blog:create | POST /v1/blogs. |
blog:update | PATCH /v1/blogs/{id}. Renaming a blog changes what shoppers read: its title is printed on the card of every post filed under it in the storefront blog index. |
blog:delete | DELETE /v1/blogs/{id} (soft delete). Deleting a blog silently takes every published post inside it off the storefront; the posts survive and return if the blog is restored from the dashboard. There is no hard delete on the public API. |
Blog posts — blog-post:*
The articles themselves, with a full draft/publish lifecycle. A post is created as a draft, becomes shopper-visible only through the separately-scoped publish step, and (unlike a page) has no separate draft copy once published: editing a published post rewrites the live article.
| Scope | Grants |
|---|---|
blog-post:list | GET /v1/blog-posts (paginated, most recently edited first; filter with blogId, status, keyword). |
blog-post:read | GET /v1/blog-posts/{id}, including its body content. |
blog-post:create | POST /v1/blogs/{blogId}/posts. Always creates a draft: a status or publishedAt in the body is dropped, so a new post is on no page a shopper can reach until the publish step runs. |
blog-post:update | PATCH /v1/blog-posts/{id}. On a published post this edits the live article in the same request; renaming the handle moves its public URL (/blog/{handle}) and creates an automatic redirect. |
blog-post:publish | POST /v1/blog-posts/{id}/publish and POST /v1/blog-posts/{id}/unpublish: the only handlers on the posts controller that change what the storefront serves. |
blog-post:delete | DELETE /v1/blog-posts/{id} (soft delete, restorable from the dashboard). Deliberately not reachable through blog-post:update. |
Media — media:read / media:create
The store media library: the images and files the dashboard's media manager holds.
| Scope | Grants |
|---|---|
media:read | GET /v1/media (paginated, newest first; filter with keyword, matched against the filename), GET /v1/media/{id}. |
media:create | POST /v1/media. Multipart upload, one file per request under the field name file; the upload is validated by its content, not the Content-Type header, and accepts images, audio, video, Lottie animations, and glTF/USDZ models. The filename you send becomes the library display name and must carry a real extension. |
There is no delete on the public surface; removing files from the library stays a dashboard action.
SEO — seo:read
A read-only health overview of the store's search-engine surface.
| Scope | Grants |
|---|---|
seo:read | GET /v1/seo/overview: sitemap entry counts and robots status. |
Redirects are their own resource family (next section), not part of seo:read.
Redirects — redirect:*
URL redirects, the tool for moving or retiring storefront paths without stranding saved links. There is deliberately no redirect:update: a redirect is small enough that the correction path is delete and recreate.
| Scope | Grants |
|---|---|
redirect:list | GET /v1/redirects (newest first, capped; meta.hasMore tells you when the store has more redirects than one response returns). |
redirect:create | POST /v1/redirects. A redirect takes effect immediately; there is no draft or publish step. Paths are normalized before storage (lowercased, query string and fragment stripped, trailing slash removed) and chains are flattened: creating /a to /b while /b to /c exists stores /a to /c and repoints existing redirects that targeted /a. Read the response (the row as stored) rather than assuming your input was kept, and list first if anything depends on where existing redirects point. |
redirect:delete | DELETE /v1/redirects/{id}. Re-breaks whatever link the redirect was rescuing. |
A redirect takes precedence over a live page at the same path, and nothing checks for that collision (the dashboard does not either), so confirm the source path is genuinely retired before creating one.
Analytics — analytics:read
Read-only reporting, mirroring the dashboard's analytics payloads.
| Scope | Grants |
|---|---|
analytics:read | GET /v1/analytics/overview (sales overview metrics with the returning-customer rate merged in), GET /v1/analytics/sales-over-time (time series), GET /v1/analytics/top-products (by revenue; limit defaults to 10). All three take a required startDate/endDate range and an optional comparison (default previous_period). |
Companies — company:*
B2B buyer accounts: the company record itself, its users (buyers who sign in and place orders on the company's behalf), pending invitations, and locations (billing/shipping addresses, each with its own assigned catalogue and payment term). Company users, invitations, and locations have no independent tenancy — a contact or invitation cannot outlive its company — so they read and write under company:* rather than a scope family of their own, the same way navigation items live under navigation:*.
Every /v1/companies request also requires the store to hold the B2B feature. Without it, reads still work — an ERP that pulls after a downgrade keeps working — but writes are refused with 403 Forbidden and body { "code": "FEATURE_FROZEN", "feature": "b2b" }.
| Scope | Grants |
|---|---|
company:list | GET /v1/companies (paginated; filter by status, exact-match externalId, and updatedAtMin/updatedAtMax for incremental sync). |
company:read | GET /v1/companies/{id} — returns the company with its locations and contacts, including each contact's linked customer record, projected down to exactly id, firstName, lastName and email — the buyer's phone, notes, consent flags and unsubscribe token are never included. It is still a PII-bearing scope: a buyer's name and email are personal data. Grant it only to integrations that need contact detail. Also: GET /v1/companies/{companyId}/users, GET /v1/companies/{companyId}/invitations, GET /v1/companies/{companyId}/locations, GET /v1/companies/{companyId}/locations/{locationId}. |
company:create | POST /v1/companies. Paired with company:update on the upsert route below. |
company:update | PATCH /v1/companies/{id} (including status to suspend/activate, and mainContactId); PUT /v1/companies/by-external-id/{externalId} (with company:create, see below); and every write on a company's users, invitations, and locations — POST/PATCH/DELETE .../users/{contactId}, POST .../users/{contactId}/send-activation, POST .../invitations, POST .../invitations/{id}/resend, DELETE .../invitations/{id}, POST/PATCH/DELETE .../locations/{locationId}. The two assignment routes under a location are the exception: PATCH .../locations/{locationId}/catalog is gated on b2b-catalog:update alone and PATCH .../locations/{locationId}/payment-term on payment-term:update alone — neither also requires company:update. Removing a company user is a DELETE that still requires company:update, not a separate delete scope — the same subordinate-resource reasoning as company:read above. |
company:delete | DELETE /v1/companies/{id} (soft delete). |
PUT /v1/companies/by-external-id/{externalId} is an idempotent upsert keyed on your own ERP id: it requires both company:create and company:update, since it may do either. The path externalId always wins over one in the request body. It returns { "company": { … }, "created": true } on insert or "created": false on update — see the B2B/ERP integration guide for the full sync pattern.
Company users land pending-activation: POST .../users never accepts a password, and sendActivationEmail defaults to false so a bulk ERP import never surprise-emails a buyer. Set a role preset (admin, buyer, or viewer) for a quick grant, or send an explicit permissions object for the 11 individual flags — an explicit permissions always wins over role. Pass mainContact: true to also make the new user the company's main contact. To send (or resend) the set-password email later, once you're ready for the buyer to sign in, call POST .../users/{contactId}/send-activation.
Catalogues — b2b-catalog:*
Company-specific price books: per-product/variant price overrides, group pricing rules keyed on category, tag, brand, custom product type, or an individual product, and — for a restricted catalogue — the explicit set of products a company is allowed to see and buy.
Every /v1/catalogs request also requires the store to hold the B2B feature; the same read-always, write-refused-when-frozen split as company:* applies (403 Forbidden, { "code": "FEATURE_FROZEN", "feature": "b2b" }).
| Scope | Grants |
|---|---|
b2b-catalog:list | GET /v1/catalogs (paginated; filter by status, free-text keyword), GET /v1/catalogs/rule-targets/custom-types (the distinct merchant-defined product types a custom_type rule can target). |
b2b-catalog:read | GET /v1/catalogs/{id} (with its price overrides), GET /v1/catalogs/{id}/prices, GET /v1/catalogs/{id}/products (the visible set on a restricted catalogue), GET /v1/catalogs/{id}/rules. Also the topic scope for the b2b_catalog.created / b2b_catalog.updated / b2b_catalog.deleted webhook topics. |
b2b-catalog:create | POST /v1/catalogs. |
b2b-catalog:update | PATCH /v1/catalogs/{id} (name, currency, status); PUT /v1/catalogs/{id}/prices (create or replace one product/variant price override) and DELETE /v1/catalogs/{id}/prices/{priceId}; POST/DELETE /v1/catalogs/{id}/products/{productId} (restricted-catalogue visibility); POST /v1/catalogs/{id}/rules, PATCH /v1/catalogs/{id}/rules/{ruleId}, DELETE /v1/catalogs/{id}/rules/{ruleId} (group pricing rules); and PATCH /v1/companies/{companyId}/locations/{locationId}/catalog (assigning a catalogue to a location — this scope alone gates the route; company:update is not additionally required). |
b2b-catalog:delete | DELETE /v1/catalogs/{id}. |
isRestricted: true on create means the company sees and buys only the products explicitly added with POST /v1/catalogs/{id}/products; false (the default) means every store product is visible, at whatever price the catalogue's overrides and rules resolve to. The vendor dashboard's price-preview/price-check tooling is deliberately not exposed on the public surface.
Payment terms — payment-term:*
Net-terms definitions — Net 30, a fixed due date, and so on — that a company location can be assigned. The assignment drives the due date on every B2B invoice an order under that location generates.
Every /v1/payment-terms request also requires the store to hold the B2B feature; same read-always, write-refused-when-frozen split (403 Forbidden, { "code": "FEATURE_FROZEN", "feature": "b2b" }).
| Scope | Grants |
|---|---|
payment-term:list | GET /v1/payment-terms (paginated; filter by status, free-text keyword). |
payment-term:read | GET /v1/payment-terms/{id}. Also the topic scope for the payment_term.created / payment_term.updated / payment_term.deleted webhook topics. |
payment-term:create | POST /v1/payment-terms. type: "net" requires netDays; type: "fixed" requires dueDate. |
payment-term:update | PATCH /v1/payment-terms/{id}, and PATCH /v1/companies/{companyId}/locations/{locationId}/payment-term (assigning a term to a location — this scope alone gates the route; company:update is not additionally required). |
payment-term:delete | DELETE /v1/payment-terms/{id}. |
B2B invoices — b2b-invoice:list / b2b-invoice:read
Read-only accounts-receivable view of a store's net-terms invoices — generated automatically when an order is placed under a company location that carries a payment term. There is no public create, update, record-payment, or void route: an invoice is a Stripe-backed order artifact, so ERPs pull its state through this surface (or through the b2b_invoice.* webhook topics) rather than minting one, and recording a payment or voiding stays a vendor-dashboard action.
Reads work regardless of B2B entitlement, the same as every other company:*-family surface — there simply is no write route here for the entitlement guard to refuse.
| Scope | Grants |
|---|---|
b2b-invoice:list | GET /v1/b2b-invoices (paginated; filter by status, companyId, and updatedAtMin/updatedAtMax for incremental sync). |
b2b-invoice:read | GET /v1/b2b-invoices/{id}. Also the topic scope for the b2b_invoice.created / b2b_invoice.paid / b2b_invoice.voided webhook topics. |
The response never includes stripeInvoiceId — that id must never leave the server. It does include hostedInvoiceUrl, a link to the Stripe-hosted invoice page you can hand a buyer or file alongside your own AR record.
Assistant-granted scopes
The two families below gate live /v1 endpoints, but neither a plugin install nor a store API key can hold them. Their endpoints carry consent semantics (tiered read / safe-write / live-write approval) that only the flow a merchant completes when connecting an AI assistant to their store can grant, so the dashboard's key scope picker does not offer them, requesting one on a key is rejected with 400, and they are not accepted in a plugin's oauth.scopes. They are documented here so the catalog is complete and so no integration mints a colliding string.
Pages — page:*
Storefront pages built with the Orbit page builder: draft/publish lifecycle, revision history, and the element catalog the page content is validated against.
| Scope | Grants |
|---|---|
page:list | No endpoint enforces this scope today: GET /v1/pages requires page:read, so page:list currently gates nothing. The string is kept in the catalog for the day pages grow a separate list route; do not request it. |
page:read | GET /v1/pages (meta only, no content; filter with status/keyword), GET /v1/pages/{id} (with its draft content), GET /v1/pages/{id}/revisions, plus the element catalog: GET /v1/page-builder/elements (compact index) and GET /v1/page-builder/elements/{id} (full prop schema for one element). |
page:create | POST /v1/pages. Always creates a draft custom page: a status in the body is dropped, so the result is invisible to shoppers until the separately-scoped publish step runs. |
page:update | PATCH /v1/pages/{id} (title, handle, and SEO metadata; note this edits a published page's live metadata, and blanking a live page's handle would unroute it), PATCH /v1/pages/{id}/content (replace the draft content; validated against the element catalog), POST /v1/pages/{id}/revisions/{revId}/restore (restore a revision onto the draft). |
page:publish | POST /v1/pages/{id}/publish and POST /v1/pages/{id}/unpublish: the only handlers on the pages controller that change what the storefront serves. |
page:delete | DELETE /v1/pages/{id} (soft delete; the page and its revision history are restorable from the dashboard). A live page is deleted in one step and leaves the storefront immediately. Built-in template pages, the homepage, and the default page for a type answer 409. |
Store settings — store-settings:*
Store-level configuration: what the store is, as opposed to settings:*:store above, which is a plugin's own per-store settings surface.
| Scope | Grants |
|---|---|
store-settings:read | GET /v1/store-settings, one grouped read: general (order numbering, timezone, unit system), information (name, description, domain), branding (logo, favicon), and currency (primary plus enabled currencies). |
store-settings:update | PATCH /v1/store-settings. Deliberately narrower than the read: it may write information.name, information.description, and the general keys (order-number prefix/suffix/format, timezone, unit system, weight and dimension units). The domain, both branding fields, the whole currency group, and the next order number are refused with 400. |
Reserved scopes — import:*
Defined in the catalog but grantable to no credential: not plugins, not API keys, not assistant connections. The core API has no import surface (product import lives elsewhere), so no /v1 endpoint enforces these strings; they are reserved so a future import surface reuses them instead of minting new ones.
| Scope | Grants |
|---|---|
import:read | Nothing today. Reserved for reading import jobs on a future /v1 import surface. |
import:dry-run | Nothing today. Reserved for validate-only import runs. |
import:apply | Nothing today. Reserved for applying an import. |
Using this page
- Find the endpoint you need in the API Reference.
- Pick the matching scope from the tables above.
- Declare it in your plugin's
oauth.scopes(in the partner-dashboard plugin form, or via JSON import). - When a merchant installs your plugin, they see those scopes on the consent screen.
- After install, your token's
scopesclaim contains everything you declared, and the API enforces that set on every call.
Request only the scopes you actually use — a smaller set is easier for merchants to approve and limits exposure if a token leaks. If you need something that isn't listed here, it isn't available on the public API yet; reach the team at developers@orbitcommerce.net rather than guessing a string, since any scope not in this catalog will never be granted.
Versioning
These scope strings are version-independent — /v1 and any future version share the same catalog. The OAuth handshake (/oauth/*) is rooted at the host (deliberately not under /vN), so your token flow never changes when the data API version bumps. New scopes are only ever added alongside the endpoints they protect; plugins that don't request a new scope are unaffected.
Last updated: 2026-08-28.