SHOP DESKDevelopers
shopdeskbd.com

Authentication

Key format, store vs company keys, rotation, revocation.

Every request carries an API key in an Authorization header. There is no token exchange, no refresh, and nothing expires unless you ask it to.

Authorization: Bearer shopdesk_sk_7f3kq9p2xR4mVnB8tLcYwZ1sD6hJ0gKeQaUiOpNm

Key format

A key is the literal prefix shopdesk_sk_ followed by 40 random base62 characters — around 238 bits of entropy.

The prefix is not decoration. It earns its place three times: secret scanners (GitHub and GitLab push protection among them) match on exactly this shape, so a key committed by accident gets caught; it tells you at a glance which of the tokens in a config file is ours; and it makes a Shopdesk key structurally impossible to confuse with a JWT. Anything that does not start with shopdesk_sk_ is rejected before we even look in the database, so a stolen dashboard session token can never be replayed here.

In the dashboard and in our logs a key appears masked, as its prefix sample and last four characters:

shopdesk_sk_7f3kq9p2…OpNm

Store keys and company keys

The key type is chosen at creation and can never change. It decides how wide the tenant fence is — never how the fence works.

Store keyCompany key
SeesOne storeEvery live store in the company
Created byAnyone with developer.manageCompany owners only
store_id on rowsThe key's own storeVaries per row — partition on it
?store_id= filterMust match the key's own storeNarrows to one store in the company
Best forA single storefront, a POS bridge, one shop's ERPA head office reading across branches

Start with a store key. A company key is a wider blast radius if it leaks, and most integrations only ever touch one store. Reach for a company key when you genuinely need one credential to read several stores.

export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"

curl -sS "https://api.shopdeskbd.com/ext/v1/locations" \
  -H "Authorization: Bearer $SHOPDESK_API_KEY"

Narrowing a company key

Pass store_id to restrict a read to one store. The value is validated against your company before any query runs — an id belonging to someone else's company returns 404, not 403, so the parameter can never be used to probe for which store ids exist.

export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"

curl -sS "https://api.shopdeskbd.com/ext/v1/orders?store_id=REPLACE_WITH_STORE_ID&limit=50" \
  -H "Authorization: Bearer $SHOPDESK_API_KEY"

Scopes

A key carries a set of scopes, fixed when it is created. Scopes are named resource:action and there are only two verbs — read and write.

ScopeCoversEndpoints today
products:readProducts, variants, categories, brands, attributesYes
orders:readOrders, their items and payment stateYes
customers:readCustomers and their addressesYes
inventory:readStock levels and the movement ledgerYes
locations:readWarehouses and stock locationsYes
channels:readSales channelsYes
store:readThe store profile: name, domain, currency, timezone, contact details, advance-payment plansYes
reports:readSales summary; comparisons and top products on plans with Advanced ReportsYes
payments:readPayments recorded against an orderYes
products:writeCreate, update and archive products and their variants; add product images by upload or by link, and manage themYes
catalog:writeCreate and update categories, brands and attributes with their values and images. Granted only together with products:readYes
videos:writeUpload product videos or import them from a link, set their covers, place them on products and list or delete this key's own uploads, within the plan's video storageYes
orders:writeCreate orders, change status, cancel, edit the note and follow-upYes
customers:writeCreate and update customersYes
inventory:writeStock adjustmentsYes
payments:writeRecord a payment against an orderYes
webhooks:manageManage webhook endpoints via the APIDeclared, not grantable

Five rules govern them:

  • Write implies read of the same resource. A key with orders:write also satisfies orders:read. The reverse is never true, which is the direction that actually matters.
  • catalog:write comes with products:read. Categories, brands and attributes are read through products:read endpoints, so a key that can create them must be able to list them. Creating a key with catalog:write but neither products:read nor products:write is refused, and the dashboard ticks products:read for you.
  • videos:write stands alone. It is its own opt-in scope, not part of products:write, because a video uses the plan's video storage and can be up to 200 MB. It reads only the key's own uploads (GET /videos); a product's ready videos are read with the product, under products:read. See Upload a product video.
  • Scopes are immutable. No endpoint changes a key's scopes, and the dashboard does not offer it either. Need different scopes? Create a new key and revoke the old one.
  • You can only grant what you hold. A staff user can only tick scopes their own role can back. A cashier who cannot read orders in the dashboard cannot mint a key that reads them over the API.

Every operation in the reference shows the scope it requires. A key missing it gets 403 FORBIDDEN_SCOPE — the endpoint is never silently reduced to an empty result. The one exception is GET /me, which any valid key may call to describe itself: its scopes, the stores it reaches and its rate limits.

export SHOPDESK_API_KEY="shopdesk_sk_YOUR_API_KEY"

curl -sS "https://api.shopdeskbd.com/ext/v1/me" \
  -H "Authorization: Bearer $SHOPDESK_API_KEY"

Rotation

Rotating replaces a key without an outage. Shopdesk issues a new key and sets an expiry on the old one instead of killing it, so both work during the overlap:

  1. Rotate in the dashboard and copy the new secret.
  2. Both keys are valid for the grace window — 24 hours by default.
  3. Deploy the new key wherever the old one lives.
  4. The old key stops working when the window closes. Nothing to remember, no second visit to the dashboard.

If you rotate because a key leaked, choose the immediate option instead: the old key dies at once and the overlap is skipped. A brief outage is the correct trade when the alternative is a live credential in someone else's hands.

Revocation

Revoking is permanent — a revoked key never comes back, and there is no "delete" at all, because the row is audit history.

Revocation takes effect immediately. Authentication is cached for speed, but every revocation and rotation explicitly evicts that cache rather than waiting for it to expire, so there is no window in which a killed key still works.

Why a request was rejected

All authentication failures return 401 and are told apart by their code:

CodeMeaning
UNAUTHORIZEDThe header is missing or malformed, the key is unknown, your plan does not include API access, or the store or company is not live.
KEY_REVOKEDThe key existed and was deliberately killed.
KEY_EXPIREDThe key's clock ran out — either its own expiry date, or the end of a rotation grace window.

Two more you will see:

  • 403 FORBIDDEN_SCOPE — the key is fine, but it was not granted this scope.
  • 404 NOT_FOUND — for an id in another tenant. Never 403; a 403 would confirm the record exists.

Keeping keys safe

  • Server-side only. A key grants everything its scopes allow across a whole store. It has no business in a browser, a mobile app, or anything else a customer can open.
  • Environment variables, not source. Every sample in these docs reads SHOPDESK_API_KEY from the environment for exactly this reason.
  • One key per integration. Separate keys mean you can revoke the one that leaked without taking down the other four, and last_used_at in the dashboard tells you which is which.
  • Only the scopes it needs. A reporting tool that reads orders should not hold customers:read as well.