Skip to main content

Product permissions reference

Every write action on a product is guarded by two independent layers. This page is the lookup for both: the granular permission keys (products.create, products.update, …) and the scope.rw:products API scope that sits in front of them.

Use it to answer "which permission does this button need?" and "why was this token refused?".

The two gate layers

Every product request passes through these in order. It must clear both to succeed.

LayerGateWhat it checksApplies to
1 — Scopescope.rw:productsEvery product request requires the products scope. Reads resolve to products:read; anything that changes data resolves to products:write.All product requests
2 — Permissionproducts.{action}The granular capability for the specific write action (create, update, archive, delete, import, export, manage_variants).Write actions only

Layer 1 is direction-aware: one scope covers the whole product surface, splitting read from write automatically. Read actions (search, lookups, inventory panels, attribute reads) only ever need products:read and carry no permission key — so a user who can view a product may still be blocked from editing it by layer 2.

Who is exempt from the scope gate

The scope check (layer 1) only applies to real, persisted Personal Access Tokens (PATs) — see Create a personal access token and the API scopes reference. It's bypassed for:

  • Signed-in browser sessions — working in SKU.io directly passes straight through.
  • Integration-instance tokens — 3PL, channel partner, and MCP tokens authenticate as an integration instance and skip scope checks.

A PAT that lacks the required scope is refused, and the refusal names the scope it needed — for example "Token is missing the required scope: products:write".

A read-only token (holds products:read but not products:write) can list and view products but is blocked at layer 1 on every write — before the permission key is ever considered.

Permission keys

These are the granular capability keys checked by layer 2. Assign them to users through Settings → Users & permissions.

Permission keyGrantsNotes
products.createCreate a new productGates the Add product button and the save behind it.
products.updateEdit product details, tags, pricing, taxonomy, shipping, blemished settings, and set the default financial line typeThe broadest key — most inline edit controls on the product detail page check it. Also required to create a blemished product.
products.archiveArchive and unarchive a product (single or bulk)One key covers both directions — see the edge case below.
products.deletePermanently delete a product (single or bulk)Distinct from products.archive.
products.importImport products from a spreadsheet (preview, validate, and run)Gates the whole import wizard, including the template download and field list.
products.exportExport the products list to a spreadsheetGates both the Export action and the download that follows.
products.manage_variantsEdit a product's attributes/variations and a kit's or bundle's componentsSeparate from products.update — a user can edit details but not restructure variants/components, or vice-versa.
inventory.adjustAdjust on-hand inventory and manage holds from the product pageAn inventory key, gated by scope.rw:inventory (not scope.rw:products). Surfaced on the product page but it authorizes inventory writes.

Action → permission map

The authoritative mapping of each product write action to the permission key that guards it (layer 2), on top of the scope.rw:products scope gate (layer 1). Where an action lives under the inventory scope, that's called out.

ActionPermission keyScope gate
Create a productproducts.createscope.rw:products
Update a product's detailsproducts.updatescope.rw:products
Set default financial line typeproducts.updatescope.rw:products
Bulk-edit productsproducts.updatescope.rw:products
Archive a productproducts.archivescope.rw:products
Unarchive a productproducts.archivescope.rw:products
Bulk archive / unarchiveproducts.archivescope.rw:products
Delete a productproducts.deletescope.rw:products
Bulk delete productsproducts.deletescope.rw:products
Import products (preview / validate / run)products.importscope.rw:products
Export products (export / download)products.exportscope.rw:products
Update product attributes / variationsproducts.manage_variantsscope.rw:products
Delete product attributesproducts.manage_variantsscope.rw:products
Edit kit / bundle componentsproducts.manage_variantsscope.rw:products
Adjust inventory from the product pageinventory.adjustscope.rw:inventory
Manage inventory holds from the product pageinventory.adjustscope.rw:inventory

Read actions — search, barcode lookup, SKU lookup, inventory panels, supplier/component/bundle/attribute views, and deletability checks — carry no permission key. They require only products:read, which scope.rw:products satisfies.

Edge cases and gotchas

These are the behaviors that surprise people. Read them before assigning roles.

  • Archive and unarchive share one key. Both directions require products.archive — there is no separate "unarchive" permission. Grant products.archive and a user can both archive and restore products. See Archive, unarchive & delete a product.
  • Delete is separate from archive. products.delete is a distinct key. A common role is "can archive but can't permanently delete": grant products.archive, withhold products.delete. Deletability is checked independently of permissions too — a product with movement history can be archived but not deleted, no matter who's asking. See Archive, unarchive & delete a product.
  • Editing details ≠ managing variants. products.update covers detail fields, tags, pricing, taxonomy, and shipping; products.manage_variants covers the attribute/variation matrix and kit/bundle component structure. They're independent, so you can let a user correct a description without letting them re-shape a variant matrix. See Set a product's categories & attributes and Build bundles & kits in the Workshop.
  • inventory.adjust is an inventory permission, not a products one. The Adjust Inventory action and the Holds tab appear on the product page, but they write inventory and are gated by scope.rw:inventory + inventory.adjust. Granting product permissions alone doesn't let a user adjust stock — see the Inventory guide for the inventory permission set.
  • Permission keys are the only gate. Products have no second, product-specific rules layer on top of the permission keys listed above — no per-product owner, no per-product override. If a user holds the key, they can perform the action on every product.
  • Creating a blemished product needs products.update. The Create Blemished Product action is gated on products.update (plus the product being a Standard or Kit), not on a dedicated key. See Create a blemished product.

How the UI reflects permissions

SKU.io hides or disables controls the current user can't use, but that's cosmetic — the permission is checked again when the action runs. A user who reaches a restricted action another way is still refused.

Each check resolves in this order:

  1. If granular permissions are turned off for your account, every check passes — nothing is permission-gated, matching how SKU.io behaved before the feature existed.
  2. If the user is an admin, every check passes.
  3. Otherwise, the check passes only if the user has that exact permission key.

Given that, the UI behaves as follows:

  • Buttons and menu items are conditionally hidden or disabled per permission. For example, the products list only shows Import with products.import, Export with products.export, Add product with products.create, and the bulk Delete action with products.delete.
  • The Actions menu on the product detail page only appears if at least one of its actions is permitted — it renders when the user can adjust inventory (and the product carries inventory), or can create a blemished product (products.update), or the product is a kit (assemble/disassemble links). If none apply, the menu isn't shown at all.
  • The Adjust Inventory item requires inventory.adjust to appear at all. When the user has that permission but the product's type doesn't carry inventory (only Standard, Kit, Blemished, and Manufactured do), the item still appears but is disabled, with a tooltip reading "<type> products don't carry inventory". So the permission controls whether the item shows, and the product type controls whether it's clickable.

Before you begin (assigning permissions)

  • Product permissions are managed under Settings → Users & permissions. Assign the keys above to roles or users.
  • If your account has the granular_permissions feature off, every user effectively has all product permissions — turning the feature on is what starts enforcing them.
  • Admin users bypass all product permission checks in the UI.
  • For API access via a Personal Access Token, the token must additionally carry the products:read and/or products:write scope — a token without the right scope is rejected at layer 1 regardless of the user's permissions. The API scopes reference lists every scope and what a refusal looks like.

Next steps

Last verified: