Authentication
How to authenticate calls to the Brainerce API — pick the right mode for your use case.
Brainerce supports five authentication modes. Pick exactly one per request:
1. Admin API key — server-to-server
Use for backend integrations (ERPs, custom tooling, server-rendered storefronts that need write access).
Authorization: Bearer brainerce_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/api/v1/*readsAuthorization: Bearerand nothing else. AnX-API-Key: brainerce_…header is not read on these routes: a request carrying only that header fails with401 Missing Authorization header. The value must start withbrainerce_. This is what the SDK sends inapiKeymode.- Issue keys from Settings → API keys in the dashboard. Plain-text key is shown once — store it in your secret manager.
- Keys are scoped (e.g.
products:read,orders:write); the full list is in API key scopes below. Wildcard*scope is no longer mintable — list exactly the scopes your integration needs. Note that scope matching supports{resource}:*(e.g.products:*) but not*:read— a key holding*:readmatches nothing and every call fails withINSUFFICIENT_SCOPE. - Keys never expire unless you set
expiresAt. Rotate via Settings → API Keys → Rotate (zero-downtime). - All
/api/v1/*endpoints require this mode.
Never put an admin key in client JavaScript. CORS will not save you — admin keys are blast-radius credentials and a leaked key = full store access.
2. Sales channel — storefronts
Use for storefronts built by AI agents or custom Next.js/React apps deployed to a specific domain.
GET /api/vc/vc_xxxxxxxxxxxxxxxxxxxxxx/products
Origin: https://your-storefront.com- The sales channel ID (
vc_…) is path-based, not a header.salesChannelIdis the canonical name for it;connectionIdis a deprecated alias that still appears as the literal route parameter in generated reference pages — the value is the samevc_*string either way. - Live mode requires an
Originheader, and the request is accepted only when that origin is (a) on the channel's registereddomain(host equality, with a leadingwww.ignored on both sides, or any subdomain of it) or (b) exactly one of the channel'sallowedOrigins. A request with noOriginis rejected (403 Origin header required), so server-side rendering against a LIVEvc_*channel does not work — see Rules. A LIVE channel with nodomainset refuses every request, even from anallowedOriginsentry (403 Connection missing domain); an entry never replaces the domain. The literal originnull(what a sandboxed iframe sends) is refused in every mode. allowedOriginsis the channel's list of additional exact origins (the dashboard field "Additional allowed origins" in the channel settings, also settable with thecreate_sales_channelandupdate_sales_channelMCP tools): at most 5 after de-duplication, each a full origin with its scheme and port (https://preview.myshop.com,http://localhost:5173), no wildcards, no path, andhttp://only forlocalhost,127.0.0.1and[::1]. A path, query or default port in what you submit is stripped; anything else is refused when you save. An entry matches the whole origin (scheme, host and port), unlikedomain, which ignores the scheme.- Test mode accepts any origin only while the channel has no
domainset; once a domain is configured it is matched exactly as in live mode (domain orallowedOrigins). The header itself is required in test mode too:/api/vc/*routes answer403 Origin header requiredwhen it is missing, before the channel is looked up (the SDK'soriginoption sends it from a server). Test mode is not read-only — it is the same API surface as live, against the same store data, governed by the same per-channel scopes. - Scopes are per-channel (
storefrontReadEnabled,cartEnabled,customerAuthEnabled,ordersWriteEnabled, …).
3. Storefront public — public catalog reads
Use for embedded widgets, marketing pages, and discovery flows that only need to read products/categories/store info.
GET /api/stores/store_xxxxxxxxxxxxxxxxxxxxx/products- No credential. This is the older mirror of the sales-channel routes: catalog reads, plus the cart, checkout and customer routes a storefront needs (the per-route throttles in Rate limits apply). New storefronts use the sales-channel mode above.
- Origin must be present (browser-context only — server-side scripts should use mode 1 or 2).
4. Customer JWT — on behalf of a logged-in shopper
Layered on top of mode 2 or 3 for customer-scoped operations (cart, order history, account).
Authorization: Bearer eyJhbGciOi... # customer JWT (NOT a Clerk JWT)- Obtained via
POST /api/vc/{salesChannelId}/customers/loginor OAuth. - 7-day expiry by default.
- Use alongside the sales-channel path; the JWT carries
customerId.
5. App Installation Tokens
Use for marketplace apps — server-side integrations that a merchant has installed onto their store via the dashboard. The token is bound to one AppInstallation row and carries only the scopes declared by the app's manifest.
Authorization: Bearer app_inst_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# or
X-API-Key: app_inst_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-
Format:
app_inst_+ 32+ random characters. Issued automatically when a merchant installs an app from the marketplace; the plain-text token is delivered to the app's setup URL once and stored encrypted at rest. -
Scopes are app-declared (e.g.
mappings:read,mappings:write,config:write) and cannot be escalated by the app at runtime. -
Routes accepting this mode all live under
/api/v1/installations/{installationId}/*. Today that's:Method Path Scope PUT/v1/installations/{installationId}/secretsconfig:writePOST/v1/installations/{installationId}/mappingsmappings:writeGET/v1/installations/{installationId}/mappingsmappings:readGET/v1/installations/{installationId}/mappings/{mappingId}mappings:readPATCH/v1/installations/{installationId}/mappings/{mappingId}mappings:writeDELETE/v1/installations/{installationId}/mappings/{mappingId}mappings:writePUT/v1/installations/{installationId}/mappings/upsertmappings:write -
The
installationIdin the URL must match theinstallationIdcarried by the token — a 403 is returned otherwise. One app cannot read or write another app's installation data. -
Rate limit: per-installation tier (
BRAINERCE1000/min,ECOMMERCE_PLATFORM500/min, third-party 100/min). Responses includeX-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset, andRetry-Afteron 429. -
For the SDK surface see the SDK reference.
API key scopes
Every admin API key (mode 1) carries an explicit list of scopes. A scope is {area}:{action}, for example products:read or orders:write. This section lists all of them. Each row below traces to the backend: the /api/v1/* routes declare their scope with @RequiredScope, and the dashboard-style routes (for example /analytics) map a store permission to a scope through STORE_PERMISSION_TO_SCOPE in packages/database.
How scopes are matched
- A route needs one exact scope, and the key must hold it.
{area}:*(for exampleproducts:*) also matches every scope of that area.*:readand*do not work: a key holding*:readmatches nothing, and the wildcard*is refused when you create or update a key. :writedoes not include:read. A key with onlyorders:writecannot list orders. Grant both when the integration needs both.- A route that declares no scope at all is refused for API keys (fail closed). It does not fall back to allowing the call.
- A key can only be created or widened with scopes its creator holds. A key minting another key cannot grant beyond its own scopes. A dashboard user who is not the account owner can grant only scopes that map from their own store permissions. The 27 scopes marked "owner only" in the table below have no store-permission mapping, so only the account owner (or a key that already holds them) can grant them. The Create API Key dialog enforces this up front: for a member who cannot grant a scope it shows the row disabled with "Only the account owner can grant this", and the presets and the Select all and All read-only links tick only what that member can grant. It asks
GET /stores/{storeId}/api-keys/grantable-scopes(dashboard session only, not a public API route), which runs the same check as key creation. The backend still refuses an over-grant if you call the create endpoint directly. - A key is bound to one store. A request naming a different store is rejected.
Scopes you can pick in the dashboard
Settings → API keys → Create API Key shows these 72 scopes as a Read / Write matrix. "Single" rows are one standalone scope, not a read/write pair. Descriptions are the ones the dialog shows.
| Area | Read scope | Write scope | What it unlocks |
|---|---|---|---|
| Products | products:read | products:write | Products, variants and offers |
| Inventory | inventory:read | inventory:write | Stock levels and adjustments |
| Media | media:read (owner only) | media:write (owner only) | Images and asset library |
| Taxonomy | taxonomy:read | taxonomy:write | Categories, brands and tags |
| Pricing formulas | pricing-formulas:read | pricing-formulas:write | Dynamic pricing rules |
| Metafields | metafields:read (owner only) | metafields:write (owner only) | Custom metafield data |
| Orders | orders:read | orders:write | Orders and fulfillment |
| Cart | cart:read (owner only) | cart:write (owner only) | Shopping cart operations |
| Checkout | checkout:read (owner only) | checkout:write (owner only) | Checkout sessions and payment |
| Shipping | shipping:read (owner only) | shipping:write (owner only) | Shipping zones and rates |
| Customers | customers:read | customers:write | Customer records and addresses |
| Customer auth (single) | - | customers:auth (owner only) | Login, signup and sessions |
| Coupons | coupons:read | coupons:write | Coupons and discount codes |
| Gift cards | gift_cards:read | gift_cards:write | Read balances and the liability figure; manage a card's status |
| Issue gift cards (single) | - | gift_cards:issue | Create new cards. This mints stored value |
| Adjust gift card balances (single) | - | gift_cards:adjust | Add or remove value from an existing card; every change is recorded on the ledger |
| Discount rules | discount-rules:read | discount-rules:write | Automatic discount rules |
| Loyalty | loyalty:read | loyalty:write | Loyalty program, points and rewards |
| Reviews | reviews:read (owner only) | reviews:write (owner only) | Product reviews and moderation |
| Content | content:read | content:write | Pages and content blocks |
| Blog | blog:read | blog:write | Blog posts and publishing |
email:read (owner only) | email:write (owner only) | Campaigns and templates | |
| Inquiries | inquiries:read | inquiries:write | Contact and product inquiries |
| Webhooks | webhooks:read | webhooks:write | Webhook endpoints and events |
| Apps | apps:read | apps:write | Installed apps and integrations |
| Sync | sync:read (owner only) | sync:write (owner only) | Retired: the only routes that use them, POST /v1/sync and GET /v1/sync/{jobId}, answer 501 |
| OAuth providers | oauth:read (owner only) | oauth:write (owner only) | OAuth apps and providers |
| Connectors | connectors:read | - | External platform connectors (read only) |
| Analytics | analytics:read | - | Reports and traffic data (read only) |
| AI assistant (single) | - | ai-assistant:use | Store AI assistant access |
| Store | store:read (owner only) | - | Store profile and configuration (read only) |
| Settings | settings:read | settings:write | Store settings and preferences |
| Storefront Bot | bot-settings:read | bot-settings:write | AI shopping assistant settings |
| Bot conversations | bot-conversations:read | bot-conversations:write | Shopper-bot chat history and summaries |
| Regions | regions:read (owner only) | regions:write (owner only) | Market regions and currencies |
| Tax | tax:read (owner only) | tax:write (owner only) | Tax rates and settings |
| Tax classes | tax-classes:read (owner only) | tax-classes:write (owner only) | Product tax class definitions |
| API keys | api-keys:read | api-keys:write | Manage other API keys |
| Team | team:read (owner only) | team:write | Invite and manage members |
| Billing | - | billing:write | Plans and payment methods (write only) |
The four presets
The dialog's Start from a preset buttons only tick boxes. Any manual change switches the active chip back to Custom. The wildcard is never involved: every preset is an explicit list. For a member who is not the account owner, a preset ticks only the scopes that member can grant and a short notice under the preset chips says how many it left out (for example, Storefront skips cart:* and checkout:*, which are owner only). The account owner always gets the full list.
| Preset | Scopes it ticks |
|---|---|
| Custom | None. It clears the matrix. A new key starts with products:read ticked. |
| Storefront | store:read, products:read, inventory:read, media:read, taxonomy:read, pricing-formulas:read, metafields:read, cart:read, cart:write, checkout:read, checkout:write, shipping:read, tax:read, regions:read, customers:auth, reviews:read, reviews:write, coupons:read, discount-rules:read, content:read, blog:read (21 scopes) |
| Read-only | Every :read scope in the matrix above (35 scopes). No write, no customers:auth, no gift_cards:issue or gift_cards:adjust, no ai-assistant:use. |
| Full access | All 72 scopes in the matrix, read, write and the single scopes. It is not a * wildcard, so it does not cover the scopes below that the dialog does not offer. Do not use it for a key you deploy. |
The Storefront preset is the dashboard's "everything a storefront needs" set: catalog reads, cart and checkout writes, and customer auth. A key is still a server-side credential, so a browser storefront uses a sales channel (mode 2) instead.
Scopes that exist in the backend but the dialog does not offer
STORE_PERMISSION_TO_SCOPE also defines these scopes. They have no row in the Create API Key dialog, so a merchant cannot tick them there.
| Scope | Store permission behind it | Note |
|---|---|---|
store_events:read, store_events:write | View Events, Manage Events | The Events feature is in limited rollout. These scopes are not part of the general API surface yet. |
donations:read, donations:write | View Donations, Manage Donations | Not offered in the dialog. |
seo:read, seo:write | View SEO Autopilot, Manage SEO Autopilot | Not offered in the dialog. |
developer:write | Manage Developer Portal | Not offered in the dialog. |
Because * is refused, the Full access preset cannot reach these. Only a key created through the API with these scopes listed explicitly carries them, and only by a caller allowed to grant them.
What has no scope of its own
analytics:readis an API-key scope, but not an MCP scope. On an API key it is real: the/analyticsroutes require it. On the MCP surfaces it cannot be issued, so analytics tools there useorders:reador the read scope of the resource they report on, and there is noanalytics:readto request. See MCP.- Billing has only
billing:write. No scope grants a read of plan or payment data.
Rate limit, expiry and IP allowlist
- Rate limit. A key's per-minute quota follows the store's billing plan: FREE 60, PRO 500, GROWTH 2000, ENTERPRISE 10000. The dialog pins the tier to the plan and caps the optional per-key override at that ceiling; a key created through the API can pin a lower
rateLimitOverride. The tier and the override cannot go above the store's plan: atierhigher than the plan answers400with codeAPI_KEY_TIER_EXCEEDS_PLAN, and arateLimitOverrideabove the plan ceiling answers400with codeAPI_KEY_RATE_LIMIT_EXCEEDS_PLAN. See Rate Limits. - IP allowlist. Optional list of IPs or CIDR ranges (IPv4 and IPv6). Empty means any IP. A request from an address not on a non-empty list is rejected and logged as an
auth_failed_ipaudit event. - Expiry. Optional
expiresAt; without it the key never expires. Rotating a key keeps its scopes, tier, override and IP allowlist. - How many keys. A store can hold up to 100 active keys; creating another, or turning a revoked one back on, answers
400with codeAPI_KEY_LIMITuntil you revoke one. Rotating a key does not count against the limit. - Test keys.
isTest: trueonly changes the prefix tobrainerce_test_. It is not a sandbox: the key has the same scopes against the same store.
What you cannot do
- Mix two credentials in one request. Sending both
Authorization: Bearer brainerce_*andX-API-Key: app_inst_*is rejected. - Use a Clerk dashboard JWT against
/api/v1/*. Clerk JWTs are dashboard-only; integrators get API keys. - Reuse a leaked key with the same prefix. The
keyPrefixshown in the dashboard is only the first 12 characters (17 for abrainerce_test_key) — the rest is unrecoverable. Rotate immediately if leaked.
Rate limits per mode
See Rate Limits for the full table. Quick summary:
| Mode | Limit enforced today |
|---|---|
| Admin API key | 60/min + 1000/hour per IP, plus a per-key quota by tier: FREE 60, PRO 500, GROWTH 2000, ENTERPRISE 10000 req/min |
| App installation | 60/min + 1000/hour per IP, plus 100–1000/min per installation by app tier |
| Sales channel | 60/min + 1000/hour per IP. Individual routes (login, register, forgot-password, review submit, …) set their own tighter caps, down to 3/min |
| Storefront | 60/min + 1000/hour per IP, same per-route overrides |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds remaining, not a Unix timestamp), plus Retry-After on a 429.
Per-key quotas are live, not planned. Per-channel quotas are the ones that do not exist — a sales channel has no rate-limit override of its own, so a busy storefront is bounded by the per-IP tiers and the per-route caps.