Get started

The SnowBee Public API is one HTTPS/JSON API with several scopes. A scope is what an API client is allowed to do, and each scope has its own integration guide:

ScopeForBound toGuide
core:read, core:write, and domain scopesBusiness resources: synchronization, retained deletions, and supported writesThe whole tenantCore (experimental; availability pending)
ecom:read, ecom:writeOnline stores: catalogue, inventory, orders, click & collect, customers, eventsThe selected eCom storesE-commerce
loyalty_programs:readLoyalty platforms: the retail stores in a programThe selected loyalty programsLoyalty programs
demand_planningPlanning partners: upload sales forecasts and replenishment parametersThe whole tenantDemand planning

This page covers what every scope shares: credentials, authentication, the base URL, and the conventions all endpoints follow. Read it once, then go to your guide.

Credentials and test tenants

You need:

Ask your SnowBee contact for a test tenant with its own credentials before integrating against a customer's tenant. A test tenant is a separate tenant: its data, stores and API clients are isolated from production tenants, so you can upload, order and reset freely. The API host is the same.

Rotate a client secret

A tenant administrator can rotate the secret on the existing API client, including when the full secret has been lost. The Client ID, scopes and selected stores or programs stay the same.

  1. Choose Rotate on the API client and save the new secret when it is shown. The former current secret becomes the previous secret; both remain valid for token requests until the previous one is explicitly revoked. If a previous secret already exists, revoke it before another rotation.
  2. Update the integration's secret in its secrets manager. Keep the same Client ID and token-request parameters. Both the canonical token endpoint and the tenant-scoped token endpoint accept the current secret and the previous valid secret.
  3. Confirm that each system using the client can obtain a token with the new secret, then ask the administrator to revoke the previous secret. There is no automatic expiry of the previous secret.

Revocation prevents new tokens from being issued with that secret. It does not invalidate tokens already issued; these remain valid until their expiry, up to one hour in production.

The administrator's API-client list shows a current secret preview, consisting of its first four and last four characters, such as abcd...wxyz. The preview helps identify the saved credential and cannot replace the full secret. Older clients without a recorded preview show N/A; rotate the secret to create a new preview.

Last token issued shows the client's most recent successful token exchange, including exchanges using either secret and previously recorded issuance. It does not show the last API request; a system can keep making requests with a token it already holds. The client's modification time and modifier reflect configuration changes, rotation and revocation, rather than token issuance.

Authenticate

The API uses OAuth 2.0 client credentials. Exchange the client id and secret for a short-lived bearer token:

Reference: POST /v1/tenants/{tenantId}/oauth2/v2.0/token.

POST https://api.snowbee.no/v1/tenants/TENANT_ID/oauth2/v2.0/token
Content-Type: application/json
 
{
  "client_id": "your-client-id",
  "client_secret": "your-client-secret",
  "grant_type": "client_credentials",
  "scope": "ecom:read"
}

application/x-www-form-urlencoded bodies are accepted too.

ParameterDescription
client_id, client_secretYour API client credentials
grant_typeAlways client_credentials
scopeOne or more space-separated, explicitly granted scopes: ecom:read, ecom:write, loyalty_programs:read, demand_planning, or Core scopes. Scopes from different API families can share one token.
resourceOmit for the scopes above. Only legacy single-resource token requests use this parameter; see below.

Every requested scope must be explicitly granted to the client. If any scope is unsupported or ungranted, the entire token request fails with OAuth invalid_scope. Read and write are independent: ecom:write does not include ecom:read, just as core:write does not include core:read.

For example, a client granted all four scopes can request "scope": "core:read ecom:read ecom:write loyalty_programs:read" in one exchange. The token has the audience https://api.snowbee.no/tenants/TENANT_ID. Its ecom_store_ids and loyalty_program_ids claims contain all the client's selected resources for the requested API families. Each request still names one store or program in its URL; access requires both the corresponding scope and membership in the token's resource list. The same eCom permissions apply to every selected store.

Requests containing only Core scopes retain the audience https://api.snowbee.no/tenants/TENANT_ID/core, and requests containing only demand_planning retain https://api.snowbee.no/tenants/TENANT_ID/demand_planning. Requests mixing API families, or requesting ecom:read, ecom:write or loyalty_programs:read, use the tenant audience above.

The token response contains:

{
  "expires_in": 3600,
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Send the token on every request:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Tokens live for 1 hour. Request a new one shortly before expiry rather than on the first 401, and never share one token between systems — the token is what the rate limit and audit trail are keyed on. A token is valid only for its tenant, granted scopes and selected resources; using it elsewhere is a 403. Changes to a client's permissions or resource selections apply to newly issued tokens. Existing tokens retain their access until expiry.

Existing integrations

Legacy requests with exactly "scope": "ecom" and "resource": "ECOM_STORE_ID", or exactly "scope": "loyalty_programs" and "resource": "LOYALTY_PROGRAM_ID", remain supported. They issue single-resource tokens with the previous audience and claims. These legacy scope names cannot be combined with other scopes. The ecom alias requires both ecom:read and ecom:write grants; a read-only client must request ecom:read and omit resource.

Existing clients preserve their access: selected eCom stores grant both eCom read and write, and selected loyalty programs grant loyalty read. Existing Core and demand_planning token requests without resource continue to work and retain their existing audiences when requested separately. Previously issued tokens remain accepted until they expire.

Base URL

https://api.snowbee.no/v1/tenants/TENANT_ID/ecom_stores/ECOM_STORE_ID/
https://api.snowbee.no/v1/tenants/TENANT_ID/loyalty_programs/LOYALTY_PROGRAM_ID/
https://api.snowbee.no/v1/tenants/TENANT_ID/demand_planning/
https://api.snowbee.no/v1/tenants/TENANT_ID/core/

Your first request

With a token for the ecom:read scope, list the product ids on a selected store's assortment:

Reference: GET /products.

curl "https://api.snowbee.no/v1/tenants/TENANT_ID/ecom_stores/ECOM_STORE_ID/products" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

For demand_planning, read the published limits:

Reference: GET /limits.

curl "https://api.snowbee.no/v1/tenants/TENANT_ID/demand_planning/limits" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Either returns 200 with JSON; a 401 means the token is missing or expired, a 403 means it was issued for another tenant, resource or scope.

Conventions

These hold for every endpoint. The guides only describe where an API deviates.

Identifiers

SnowBee ids are UUIDv7 strings (019c1234-5678-7abc-8def-123456789012). Business keys — SKU numbers, store numbers, product numbers — are strings that are unique within a tenant and are what the demand planning API keys on.

Dates, times and decimals

JSON and unknown fields

Requests and responses are application/json. Unknown request fields are ignored by the e-commerce and loyalty APIs; demand planning additionally reports them back as warnings. Core writes reject unknown request fields with 400 to prevent silently unapplied values. Where an API lets you attach your own data, it is under a field named ext, an object SnowBee stores or echoes but never interprets. New fields may appear in responses at any time — parse leniently.

Errors

Errors carry a JSON body with a stable machine-readable error code and a human-readable message. Some APIs add a details object with structured specifics.

{
  "error": "VALIDATION_ERROR",
  "message": "3 validation error(s); nothing was stored",
  "details": { "errors": [ { "path": "items[17].quantities[3]", "code": "INVALID_DECIMAL", "message": "..." } ] }
}
StatusMeaning
200, 201, 202Success; 202 means accepted for asynchronous processing
400The request is malformed or fails validation; the body says where
401Missing, invalid or expired token — get a new one
403Token valid but not for this tenant, resource or scope
404Not found, or not visible from this scope (e.g. a product outside the eCom store's assortment)
409Conflict with existing state — a duplicate order, a reused batch id with a different payload
413A published size limit was exceeded; the body names the limit
415Unsupported Content-Type or Content-Encoding
429Rate limit exceeded; wait Retry-After seconds
500Something failed on our side; safe to retry idempotent requests

Generic codes: BAD_REQUEST (body could not be parsed), UNAUTHORIZED, FORBIDDEN, NOT_FOUND, TOO_MANY_REQUESTS, INTERNAL_SERVER_ERROR. An unhandled database business-rule rejection returns 400 VALIDATION_ERROR with the rule's message; correct the input or configuration before retrying. Each guide lists the codes specific to its endpoints.

Idempotency and retries

Network failures leave a client unsure whether a request landed. The API is designed so that retrying is safe:

Retry 429, 500, 502, 503 and network errors with exponential backoff; retry 409 never — read the existing resource instead.

Pagination

List endpoints in the e-commerce and loyalty APIs return the complete list; they are bounded by the size of a store's assortment. Demand planning list endpoints page with a cursor/nextCursor pair and a limit parameter; keep requesting until nextCursor is null. Core collections use since or cursor, return mixed entity/deletion items, and expose next_cursor and sync_through. Save the completed sync_through checkpoint only after next_cursor is null and every page has been applied. On later Core polls, subtract a configurable overlap window from that checkpoint before sending since; 60 seconds is a starting point, and periodic reconciliation is needed for changes that arrive outside that window. See Core synchronization.

Rate limits and fair use

The API is shared infrastructure. Cache what does not change, react to event notifications instead of polling, and spread bulk work out.

A fair-use limit is enforced per API client: 500 requests per minute (about 8 per second on average, counted per rolling one-minute window). Normal integrations stay far below it; it exists to stop runaway loops. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Above the limit you get 429 with Retry-After — honour it and back off exponentially. If an integration legitimately needs more, contact us; the limit can be raised per client.

Versioning and compatibility

All endpoints live under /v1. Within /v1 changes are additive: new endpoints, new optional request fields, new response fields. We do not remove or rename fields, change types, or tighten validation without announcing it in the changelog ahead of time. Endpoints tagged Experimental in the API Reference are the exception: they are published so partners can build against them early and may change until the tag is removed.

Next