API ReferenceAuthentication

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/* reads Authorization: Bearer and nothing else. An X-API-Key: brainerce_… header is not read on these routes: a request carrying only that header fails with 401 Missing Authorization header. The value must start with brainerce_. This is what the SDK sends in apiKey mode.
  • 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 *:read matches nothing and every call fails with INSUFFICIENT_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. salesChannelId is the canonical name for it; connectionId is a deprecated alias that still appears as the literal route parameter in generated reference pages — the value is the same vc_* string either way.
  • Live mode requires an Origin header, and the request is accepted only when that origin is (a) on the channel's registered domain (host equality, with a leading www. ignored on both sides, or any subdomain of it) or (b) exactly one of the channel's allowedOrigins. A request with no Origin is rejected (403 Origin header required), so server-side rendering against a LIVE vc_* channel does not work — see Rules. A LIVE channel with no domain set refuses every request, even from an allowedOrigins entry (403 Connection missing domain); an entry never replaces the domain. The literal origin null (what a sandboxed iframe sends) is refused in every mode.
  • allowedOrigins is the channel's list of additional exact origins (the dashboard field "Additional allowed origins" in the channel settings, also settable with the create_sales_channel and update_sales_channel MCP 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, and http:// only for localhost, 127.0.0.1 and [::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), unlike domain, which ignores the scheme.
  • Test mode accepts any origin only while the channel has no domain set; once a domain is configured it is matched exactly as in live mode (domain or allowedOrigins). The header itself is required in test mode too: /api/vc/* routes answer 403 Origin header required when it is missing, before the channel is looked up (the SDK's origin option 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/login or 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:

    MethodPathScope
    PUT/v1/installations/{installationId}/secretsconfig:write
    POST/v1/installations/{installationId}/mappingsmappings:write
    GET/v1/installations/{installationId}/mappingsmappings:read
    GET/v1/installations/{installationId}/mappings/{mappingId}mappings:read
    PATCH/v1/installations/{installationId}/mappings/{mappingId}mappings:write
    DELETE/v1/installations/{installationId}/mappings/{mappingId}mappings:write
    PUT/v1/installations/{installationId}/mappings/upsertmappings:write
  • The installationId in the URL must match the installationId carried by the token — a 403 is returned otherwise. One app cannot read or write another app's installation data.

  • Rate limit: per-installation tier (BRAINERCE 1000/min, ECOMMERCE_PLATFORM 500/min, third-party 100/min). Responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After on 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 example products:*) also matches every scope of that area. *:read and * do not work: a key holding *:read matches nothing, and the wildcard * is refused when you create or update a key.
  • :write does not include :read. A key with only orders:write cannot 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.

AreaRead scopeWrite scopeWhat it unlocks
Productsproducts:readproducts:writeProducts, variants and offers
Inventoryinventory:readinventory:writeStock levels and adjustments
Mediamedia:read (owner only)media:write (owner only)Images and asset library
Taxonomytaxonomy:readtaxonomy:writeCategories, brands and tags
Pricing formulaspricing-formulas:readpricing-formulas:writeDynamic pricing rules
Metafieldsmetafields:read (owner only)metafields:write (owner only)Custom metafield data
Ordersorders:readorders:writeOrders and fulfillment
Cartcart:read (owner only)cart:write (owner only)Shopping cart operations
Checkoutcheckout:read (owner only)checkout:write (owner only)Checkout sessions and payment
Shippingshipping:read (owner only)shipping:write (owner only)Shipping zones and rates
Customerscustomers:readcustomers:writeCustomer records and addresses
Customer auth (single)-customers:auth (owner only)Login, signup and sessions
Couponscoupons:readcoupons:writeCoupons and discount codes
Gift cardsgift_cards:readgift_cards:writeRead balances and the liability figure; manage a card's status
Issue gift cards (single)-gift_cards:issueCreate new cards. This mints stored value
Adjust gift card balances (single)-gift_cards:adjustAdd or remove value from an existing card; every change is recorded on the ledger
Discount rulesdiscount-rules:readdiscount-rules:writeAutomatic discount rules
Loyaltyloyalty:readloyalty:writeLoyalty program, points and rewards
Reviewsreviews:read (owner only)reviews:write (owner only)Product reviews and moderation
Contentcontent:readcontent:writePages and content blocks
Blogblog:readblog:writeBlog posts and publishing
Emailemail:read (owner only)email:write (owner only)Campaigns and templates
Inquiriesinquiries:readinquiries:writeContact and product inquiries
Webhookswebhooks:readwebhooks:writeWebhook endpoints and events
Appsapps:readapps:writeInstalled apps and integrations
Syncsync: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 providersoauth:read (owner only)oauth:write (owner only)OAuth apps and providers
Connectorsconnectors:read-External platform connectors (read only)
Analyticsanalytics:read-Reports and traffic data (read only)
AI assistant (single)-ai-assistant:useStore AI assistant access
Storestore:read (owner only)-Store profile and configuration (read only)
Settingssettings:readsettings:writeStore settings and preferences
Storefront Botbot-settings:readbot-settings:writeAI shopping assistant settings
Bot conversationsbot-conversations:readbot-conversations:writeShopper-bot chat history and summaries
Regionsregions:read (owner only)regions:write (owner only)Market regions and currencies
Taxtax:read (owner only)tax:write (owner only)Tax rates and settings
Tax classestax-classes:read (owner only)tax-classes:write (owner only)Product tax class definitions
API keysapi-keys:readapi-keys:writeManage other API keys
Teamteam:read (owner only)team:writeInvite and manage members
Billing-billing:writePlans 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.

PresetScopes it ticks
CustomNone. It clears the matrix. A new key starts with products:read ticked.
Storefrontstore: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-onlyEvery :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 accessAll 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.

ScopeStore permission behind itNote
store_events:read, store_events:writeView Events, Manage EventsThe Events feature is in limited rollout. These scopes are not part of the general API surface yet.
donations:read, donations:writeView Donations, Manage DonationsNot offered in the dialog.
seo:read, seo:writeView SEO Autopilot, Manage SEO AutopilotNot offered in the dialog.
developer:writeManage Developer PortalNot 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:read is an API-key scope, but not an MCP scope. On an API key it is real: the /analytics routes require it. On the MCP surfaces it cannot be issued, so analytics tools there use orders:read or the read scope of the resource they report on, and there is no analytics:read to 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: a tier higher than the plan answers 400 with code API_KEY_TIER_EXCEEDS_PLAN, and a rateLimitOverride above the plan ceiling answers 400 with code API_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_ip audit 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 400 with code API_KEY_LIMIT until you revoke one. Rotating a key does not count against the limit.
  • Test keys. isTest: true only changes the prefix to brainerce_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_* and X-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 keyPrefix shown in the dashboard is only the first 12 characters (17 for a brainerce_test_ key) — the rest is unrecoverable. Rotate immediately if leaked.

Rate limits per mode

See Rate Limits for the full table. Quick summary:

ModeLimit enforced today
Admin API key60/min + 1000/hour per IP, plus a per-key quota by tier: FREE 60, PRO 500, GROWTH 2000, ENTERPRISE 10000 req/min
App installation60/min + 1000/hour per IP, plus 100–1000/min per installation by app tier
Sales channel60/min + 1000/hour per IP. Individual routes (login, register, forgot-password, review submit, …) set their own tighter caps, down to 3/min
Storefront60/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.