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:
| Scope | For | Bound to | Guide |
|---|---|---|---|
core:read, core:write, and domain scopes | Business resources: synchronization, retained deletions, and supported writes | The whole tenant | Core (experimental; availability pending) |
ecom:read, ecom:write | Online stores: catalogue, inventory, orders, click & collect, customers, events | The selected eCom stores | E-commerce |
loyalty_programs:read | Loyalty platforms: the retail stores in a program | The selected loyalty programs | Loyalty programs |
demand_planning | Planning partners: upload sales forecasts and replenishment parameters | The whole tenant | Demand 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:
- A Client ID and Client Secret. A SnowBee administrator for the tenant creates the API client, grants its scopes and selects any eCom stores and loyalty programs it may access. A client can access several stores or programs with the same credentials. The full secret is shown exactly once when created or rotated. Store it in a secrets manager, never in client-side code or a repository.
- The Tenant ID — the customer's organisation in SnowBee.
- For e-commerce and loyalty requests, the eCom Store ID or Loyalty Program ID to use in the
endpoint URL. It must be among the client's selected stores or programs. Core scopes and
demand_planningcover the whole tenant and need neither.
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.
- 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.
- 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.
- 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.
| Parameter | Description |
|---|---|
client_id, client_secret | Your API client credentials |
grant_type | Always client_credentials |
scope | One 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. |
resource | Omit 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
- Instants are ISO 8601 timestamps with an offset, normally UTC:
2026-08-28T03:12:00Z. - Calendar days are ISO 8601 dates:
2026-09-01. A day is never expressed as a timestamp, so it cannot shift with a time zone. - Decimal values — money, quantities, percentages — are returned as JSON strings
(
"149.90","4.7") so no client loses precision through a floating-point parser. Requests to the demand planning API must send strings; the e-commerce API accepts strings or numbers. - Money is a bare amount; the currency is that of the store or company it belongs to, and is given separately where a response spans currencies.
- Phone numbers are E.164 (
+4712345678).
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": "..." } ] }
}| Status | Meaning |
|---|---|
200, 201, 202 | Success; 202 means accepted for asynchronous processing |
400 | The request is malformed or fails validation; the body says where |
401 | Missing, invalid or expired token — get a new one |
403 | Token valid but not for this tenant, resource or scope |
404 | Not found, or not visible from this scope (e.g. a product outside the eCom store's assortment) |
409 | Conflict with existing state — a duplicate order, a reused batch id with a different payload |
413 | A published size limit was exceeded; the body names the limit |
415 | Unsupported Content-Type or Content-Encoding |
429 | Rate limit exceeded; wait Retry-After seconds |
500 | Something 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:
PUTendpoints are idempotent by construction:PUT /customers/:customerIdupserts, and the demand planningPUT .../{batchId}with a client-generated UUID answers200on a repeat with the same payload and409on a repeat with a different one.POST /ordersrejects a duplicateexternalOrderIdwith409, so a retried order creation cannot create two orders.- Core brand creation has no general idempotency key. An uncertain retry with the same code or name
can return
409; reconcile the existing brand before treating creation as failed. GETrequests can always be retried.
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
-
Core Experimental — business resources, incremental synchronization, and supported writes; availability pending
-
E-commerce — online store integration
-
Build a storefront Experimental — a web shop built directly on SnowBee's cart, delivery and payment endpoints
-
Loyalty programs — retail stores in a loyalty program
-
Demand planning Experimental — sales forecasts and replenishment parameters
-
Events — Azure Service Bus notifications for the e-commerce scope
-
API Reference — every endpoint, request and response