Permissions Reference
Permissions Reference
Permissions (scopes) control what data your app can access through the API. They are requested during app registration and granted when a merchant installs your app.
API Scopes
These scopes are used in the OAuth flow and when creating Custom Apps.
Data Access Scopes
| Scope | Grants Access To |
|---|---|
read:orders | View orders, order details, line items, and order history |
write:orders | Create orders, update order status, manage fulfillment |
read:products | View products, variants, categories, and pricing |
write:products | Create, update, and delete products and variants |
read:customers | View customer profiles, contact info, and purchase history |
write:customers | Create and update customer records |
read:inventory | View stock levels and inventory locations |
write:inventory | Adjust stock quantities and manage inventory |
read:store | View store settings, domains, currency, and configuration |
write:store | Update store settings |
read:shipping | View shipping zones, rates, and 3PL configurations |
write:shipping | Update shipping settings and manage fulfillment providers |
read:discounts | View discount codes, promotions, and rules |
write:discounts | Create, update, and delete discount codes |
read:analytics | View store analytics, traffic data, and reports |
Special Scopes
| Scope | Grants Access To |
|---|---|
billing | Create charges, subscriptions, wallet operations, and manage billing for your app |
read:billing | View billing history and charge status (read-only) |
manage:messaging | Send SMS, email, and push notifications to customers on behalf of the merchant |
Scope Enforcement
Every API endpoint requires a specific scope. If your app doesn't have the required scope, the API returns:
{
"message": "Insufficient scope. Required: read:orders",
"code": "insufficient_scope",
"required_scope": "read:orders",
"status": 403
}Scope-to-Endpoint Mapping
Orders
| Endpoint | Required Scope |
|---|---|
GET /api/apps/v1/orders | read:orders |
GET /api/apps/v1/orders/:order_id | read:orders |
POST /api/apps/v1/orders | write:orders |
PUT /api/apps/v1/orders/:order_id | write:orders |
PUT /api/apps/v1/orders/:order_id/status | write:orders |
Products
| Endpoint | Required Scope |
|---|---|
GET /api/apps/v1/products | read:products |
GET /api/apps/v1/products/:product_id | read:products |
POST /api/apps/v1/products | write:products |
PUT /api/apps/v1/products/:product_id | write:products |
DELETE /api/apps/v1/products/:product_id | write:products |
POST /api/apps/v1/products/:product_id/variants | write:products |
PUT /api/apps/v1/products/:product_id/variants/:sku_id | write:products |
DELETE /api/apps/v1/products/:product_id/variants/:sku_id | write:products |
POST /api/apps/v1/products/:product_id/images | write:products |
Categories
| Endpoint | Required Scope |
|---|---|
GET /api/apps/v1/categories | read:products |
GET /api/apps/v1/categories/:category_id | read:products |
POST /api/apps/v1/categories | write:products |
PUT /api/apps/v1/categories/:category_id | write:products |
DELETE /api/apps/v1/categories/:category_id | write:products |
Customers
| Endpoint | Required Scope |
|---|---|
GET /api/apps/v1/customers | read:customers |
GET /api/apps/v1/customers/:user_id | read:customers |
POST /api/apps/v1/customers | write:customers |
PUT /api/apps/v1/customers/:user_id | write:customers |
DELETE /api/apps/v1/customers/:user_id | write:customers |
GET /api/apps/v1/customers/:user_id/orders | read:customers |
Discounts
| Endpoint | Required Scope |
|---|---|
GET /api/apps/v1/discounts | read:discounts |
GET /api/apps/v1/discounts/:discount_id | read:discounts |
GET /api/apps/v1/discounts/validate/:code | read:discounts |
POST /api/apps/v1/discounts | write:discounts |
PUT /api/apps/v1/discounts/:discount_id | write:discounts |
DELETE /api/apps/v1/discounts/:discount_id | write:discounts |
Inventory
| Endpoint | Required Scope |
|---|---|
GET /api/apps/v1/inventory | read:inventory |
GET /api/apps/v1/inventory/:product_id | read:inventory |
PUT /api/apps/v1/inventory/:product_id | write:inventory |
POST /api/apps/v1/inventory/:product_id/adjust | write:inventory |
Store
| Endpoint | Required Scope |
|---|---|
GET /api/apps/v1/store | read:store |
Billing
| Endpoint | Required Scope |
|---|---|
POST /api/apps/v1/billing/charges | billing |
POST /api/apps/v1/billing/subscriptions | billing |
POST /api/apps/v1/billing/wallet/topup | billing |
GET /api/apps/v1/billing/charges/:charge_id | billing |
GET /api/apps/v1/billing/charges/active | billing |
POST /api/apps/v1/billing/usage-charges | billing |
GET /api/apps/v1/billing/usage-charges | billing |
GET /api/apps/v1/billing/wallet | billing |
POST /api/apps/v1/billing/wallet/debit | billing |
GET /api/apps/v1/billing/wallet/transactions | billing |
Webhooks
| Endpoint | Required Scope |
|---|---|
GET /api/apps/v1/webhooks | No scope required (app-level access) |
POST /api/apps/v1/webhooks | No scope required (app-level access) |
DELETE /api/apps/v1/webhooks/:subscription_id | No scope required (app-level access) |
Webhook Event Topics by Scope
The event topics your app can subscribe to depend on the scopes your app has been granted. The platform does not enforce topic-to-scope restrictions at the subscription level, but the payload will be filtered based on your permissions (see Webhook Payload Filtering below).
| Event Topic | Related Scope | Description |
|---|---|---|
order.created | read:orders | New order placed |
order.updated | read:orders | Order details modified |
order.status_changed | read:orders | Order status transition |
product.created | read:products | New product added |
product.updated | read:products | Product details modified |
product.deleted | read:products | Product removed |
customer.created | read:customers | New customer account |
customer.updated | read:customers | Customer record modified |
inventory.updated | read:inventory | Stock levels changed |
app.installed | -- | App installed on a store |
app.uninstalled | -- | App removed from a store |
charge.created | billing | New billing charge created |
charge.activated | billing | Charge approved and paid |
charge.declined | billing | Charge declined by merchant |
charge.cancelled | billing | Charge cancelled |
charge.expired | billing | Charge expired (48h timeout) |
charge.payment_failed | billing | Payment gateway failure |
subscription.renewal_pending | billing | Recurring renewal upcoming |
App Lifecycle Events
app.installed and app.uninstalled events do not require any specific scope. All apps receive these events automatically.
Requesting Scopes
Platform Apps
Specify scopes in your app configuration's requested_scopes field. When a merchant installs your app, they see the requested scopes and choose to grant or deny them.
Only request scopes your app actually needs. Requesting unnecessary scopes reduces merchant trust and install rates.
Custom Apps
Select scopes during app creation in the dashboard wizard. You can update scopes later from Settings > Apps > Manage.
Webhook Payload Filtering
Merchant-created webhooks (from the dashboard Webhooks tab) support permission-based payload filtering. This controls which data fields are included in webhook payloads.
App vs Merchant webhooks
App-created webhooks (auto-created on install) receive unfiltered payloads — they have full access to all fields. Payload filtering only applies to merchant-created webhook subscriptions.
Webhook Permissions
When a merchant creates a webhook subscription from the dashboard, they can select these payload permissions:
| Permission | What it includes | Fields removed if missing |
|---|---|---|
read_orders | Order totals, tracking, address, line items | total, grand_total, tracking_id, tracking_code, address, items |
read_products | Product details and pricing | product_title, product_price, variants, sku |
read_customers | Customer contact information | customer_name, customer_phone, customer_email, email, phone |
read_inventory | Stock and quantity data | quantity, stock, inventory_quantity |
Example
A webhook subscription with only read_orders permission receiving an order.status_changed event:
Full payload (app webhook):
{
"data": {
"order_id": 1045,
"status": "confirmed",
"total": 1460.00,
"customer_name": "Rahim Ahmed",
"customer_phone": "+8801712345678"
}
}Filtered payload (merchant webhook with only read_orders):
{
"data": {
"order_id": 1045,
"status": "confirmed",
"total": 1460.00
}
}Customer fields are removed because the read_customers permission was not granted.
Extension Scopes
Extensions inherit their parent app's OAuth scopes. When a sandbox extension calls selorax.api.get('/apps/v1/orders'), the API proxy checks that the app has read:orders. No additional scopes are needed specifically for extensions — the app's existing scopes apply.
| Extension Feature | Required Scope |
|---|---|
| API proxy requests (read) | Matching read:* scope for the resource |
| API proxy requests (write) | Matching write:* scope for the resource |
| Metafield read/write | No special scope (app-scoped by default) |
| Billing from extension | billing |
| Webhook management | No scope required (app-level access) |
Extension deployment and management use client credentials (X-Client-Id + X-Client-Secret) and do not require OAuth scopes. See Extensions for details.
Best Practices
- Least privilege — Request only the scopes your app needs. You can always add more scopes later (merchants will need to re-authorize).
- Check scopes at runtime — The token exchange response includes a
scopefield listing what was actually granted. Verify it contains what your app needs. - Handle scope changes — If a merchant re-installs with different scopes, your stored scopes may be outdated. Always check the latest scope from the installation data.
- Document your scope usage — In your app's description, explain why each scope is needed. Merchants are more likely to install apps that justify their permissions.