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
ecomOnline stores: catalogue, inventory, orders, click & collect, customers, eventsOne eCom storeE-commerce
loyalty_programsLoyalty platforms: the retail stores in a programOne loyalty programLoyalty 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:

  • A Client ID and Client Secret. A SnowBee administrator for the tenant creates the API client and chooses what it may access; the secret is shown exactly once at creation. Store it in a secrets manager, never in client-side code or a repository.
  • The Tenant ID — the customer's organisation in SnowBee.
  • For ecom and loyalty_programs, the eCom Store ID or Loyalty Program ID the client is bound to. demand_planning covers the whole tenant and needs 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.

Authenticate

The API uses OAuth 2.0 client credentials. Exchange the client id and secret for a short-lived bearer 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",
  "resource": "your-ecom-store-id"
}

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

ParameterDescription
client_id, client_secretYour API client credentials
grant_typeAlways client_credentials
scopeecom, loyalty_programs or demand_planning
resourceThe eCom Store ID or Loyalty Program ID for those scopes. Omit for demand_planning.
{
  "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 the tenant and resource it was issued for; using it elsewhere is a 403.

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/

Your first request

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

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:

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. Fields SnowBee does not know are ignored and never fail a request; the demand planning API additionally reports them back as warnings. 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. 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:

  • PUT endpoints are idempotent by construction: PUT /customers/:customerId upserts, and the demand planning PUT .../{batchId} with a client-generated UUID answers 200 on a repeat with the same payload and 409 on a repeat with a different one.
  • POST /orders rejects a duplicate externalOrderId with 409, so a retried order creation cannot create two orders.
  • GET requests 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.

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: 1,800 requests per minute (30 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