Every key carries a subset of scopes. A request to a route missing its required scope returns 403 FORBIDDEN_SCOPE. Grant the minimum a key needs.

Catalog

Commerce resources

The resource APIs use Shopify-parity scope names. catalog:read and catalog:write are the older names for read_products and write_products. They remain valid and are treated as equivalent, so keys issued before the rename keep working and either name authorizes either surface. New keys should use the *_products names. GET /whoami reports both: scopes is the grant exactly as issued on the key — the audit answer — and effective_scopes is that set expanded through the aliases, which is what authorization checks. A key created with catalog:read reports ["catalog:read"] and ["catalog:read", "read_products"] respectively.

Fulfillment

See Suppliers & 3PL.

agent:admin behaves differently on the two surfaces

Known inconsistency, not intended design. agent:admin and * are treated as super-scopes by the commerce resource APIs — holding either satisfies any scope check on products, inventory, discounts, gift cards, draft orders, metafields and returns. The Agent Gateway (/api/v1/agent/v1) gives them no special meaning at all: there, agent:admin grants only the key-and-webhook administration routes, and a scope you did not explicitly grant is denied.The two surfaces therefore disagree in opposite directions about the same credential, and which answer you get depends only on which one serves the path. A key holding ['agent:admin', 'orders:read'] is strictly orders-only on the Agent Gateway, yet reaches every commerce resource API — so the narrower-looking grant is the more powerful one exactly where you would least expect it. * is not a grantable scope and should not be used.Do not rely on this. Grant the scopes a key actually needs and treat agent:admin as administration only — that is the behaviour both surfaces will converge on. Until they do, audit agent:admin keys as though they were unscoped.

Spend scopes

store:publish, ads:write, marketing:send, checkout:recover, write_discounts and write_gift_cards move money, mint redeemable value, or contact real customers. A key created with requires_approval: true parks these actions in pending_approval until a human resolves them — see Approvals. A gift card and a discount code are bearer instruments: whoever holds one can redeem it. Treat write_gift_cards and write_discounts like ads:write — grant them to a dedicated key with approval on, never to a general-purpose one. offers:approve also changes live pricing, but it is not a spend scope: offer optimization carries its own in-band confirmation, where the two gated calls require approval_context: { "merchant_confirmed": true } in the body and otherwise return 200 with data.status: "pending_approval". It is gated at the call, not at the key.
Issue separate keys per agent role: a catalog:write “merchandiser”, an analytics:read “reporter”, an ads:write + marketing:send “growth” key with requires_approval on. Revoke independently.