Authentication
Bearer tokens, scopes, capabilities, and the Platform Kernel.
Every Partner API v1 request authenticates the same way:
Authorization: Bearer <access_token>There is no API-key mode and no query-string token mode — bearer header only. The access token identifies one installation (one app, installed on one store), never a whole developer org or a whole app across every store it's installed on.
Where the token comes from
Access tokens are minted by the OAuth token endpoint. There is no other way to obtain one — Partner API v1 has no separate API-key concept distinct from OAuth tokens.
Not the same as a CLI login token
prebit login's personal access token (prcli_...) authenticates you
(a developer) to the Developer Portal's own management routes
(submitting releases, reading health). It is a completely different
credential from the per-installation OAuth access_token your app uses
to call Partner API v1 on a merchant's behalf. See CLI.
What a request is authorized against
Every request is checked against two independent things, both drawn from your specific installation's granted state — never from what your app merely requested at manifest-submission time:
-
Scopes gate data access — e.g.
read_products,write_products,read_orders,read_customers,read_customer_pii,read_collections,write_collections,read_store. -
Capabilities gate runtime privileges, independent of data access:
Capability Grants can_register_background_jobRegister an app-triggered ("sync now") background job can_schedule_jobRegister a cron-scheduled job can_use_event_subscribeSubscribe to webhook topics can_write_extension_storageRead/write Extension Storage can_use_billing_apiUse billing = "prebit"
A merchant can grant your app fewer scopes/capabilities than you requested
in your manifest — always check the granted set (returned as scope on
the token response), never assume you have everything you asked for.
Every request passes through one authorization layer
Internally, every Partner API v1 route calls a single function — the Platform Kernel — before doing anything else. This is what actually enforces the rules above, and it's also what:
- Rate-limits your installation: a flat 100 requests / 60-second
rolling window per installation today (deliberately simple for a
handful of curated partners; a per-endpoint cost-weighted budget is the
planned evolution). Exceeding it returns
429. There is currently noX-RateLimit-*response header — build your own backoff on429rather than polling a quota header that doesn't exist. - Rejects a non-active installation — if the merchant uninstalled your
app or it was suspended, every call returns
403, even with an otherwise-valid token. - Logs every call, which is what powers App Health (uptime/error-rate visible to the merchant) and what drives automatic capability revocation if your app's error rate breaches a threshold.
Error responses
| Status | Body | Cause |
|---|---|---|
| 401 | {"error":"Missing bearer token"} | No Authorization header |
| 401 | {"error":"Invalid or expired access token"} | Token doesn't verify, or is expired/revoked |
| 403 | {"error":"This installation was not granted the \"<scope>\" scope"} | Missing scope |
| 403 | {"error":"Installation is not active"} | Uninstalled/suspended |
| 403 | {"error":"This installation was not granted the \"<capability>\" capability"} | Missing capability |
| 429 | {"error":"Rate limit exceeded — retry after a short backoff"} | Over the rolling-window limit |
Token modes: offline vs. online
Every installation's tokens are one of two modes, chosen at install time:
- Offline — a service-account-style token for background jobs,
scheduled jobs, and webhook-driven writes with no live staff session
behind them. Audit entries attribute these to
"{AppName} (automated)". - Online — represents a specific staff member's in-app action. Audit
entries attribute to
"{AppName}"without the "(automated)" suffix.
Most backend integrations only need offline tokens.