- Unhandled database business-rule rejections now return HTTP 400 with
VALIDATION_ERRORand the rule's message instead of a generic HTTP 500. For example, an order rejected because a consignment product has no normal B2B sales price now explains the missing price so the configuration can be corrected before retrying.
Changelog
Changes to the SnowBee Public API and developer documentation.
OrderReadyForPaymentCaptureis now emitted when the last picking list for an order in a batch closes with zero picked quantity, allowing integrations to capture payment for the items picked on earlier lists.
- Breaking change (experimental): Frequently bought together suggestions now use
GET /products/frequently_bought_togetherwith one to 50 repeatedproductIdquery parameters. This replacesGET /carts/{cartId}/frequently_bought_together, so integrations must now provide the products directly instead of a Snowbee cart ID.
- The experimental catalog API now provides
GET /carts/{cartId}/frequently_bought_together, returning up to six in-stock product summaries ranked by how often they are bought with products in the cart. Products already in the cart are excluded.
- Breaking: Creating B2B sales orders now requires the invoice country to match the customer’s main invoice address, or the customer’s country when no main invoice address exists. Mismatches return HTTP 400 with
INVALID_REQUEST. B2C orders continue to allow a different billing country.
- MCP clients can now connect to SnowBee at
/mcpthrough the OAuth authorization-code flow and use read-only analytics tools to discover datasets, inspect schemas, and run tenant-scoped queries. - Breaking change: SKU lifecycle state responses now expose
sales_allowedinstead ofnormal_sales_allowed. Update integrations to read the new field;preorder_sales_allowedhas been removed. Existing Sales Allowed settings are preserved. - Breaking change: SKU lifecycle state responses no longer include
inventory_counting_allowed,hide_in_search, orauto_expire_on_expiry_date. Remove references to these fields from integrations. - Customer phone-number changes now update an authoritative linked Trigg profile in the background. Conflicting profiles require review and are not automatically merged.
- Web shops now receive
CampaignUpdatednotifications when a released campaign's selected membership levels change. - eCom voucher orders can again use the legacy customer-phone lookup when the customer has only retired Snowbee loyalty links. Authoritative loyalty customer IDs still take precedence; the fallback does not recreate or update loyalty links.
- eCom orders with loyalty vouchers can temporarily use the Snowbee customer's phone number when no authoritative
loyaltyProgramCustomerIdis available. Order delivery and voucher redemption prefer the authoritative ID and otherwise resolve the customer by phone without creating or updating a loyalty link. Conflicting stored IDs still block voucher redemption. Integrations should migrate membership registration toPOST /customers/{customerId}/loyalty_membershipbefore this compatibility fallback is removed. - Breaking (experimental Core API): B2C customer
loyalty_membershipsno longer includemembership_status. These entries describe identity relationships, not current membership state; the provider remains the source of truth for active membership. - Core API invoices now include nullable
creditor_reference_algorithmandcreditor_reference_numberfields alongside the per-paymentcreditor_reference, allowing integrators to reconstruct Danish FIK 71 and 75 payment instructions. Historical invoices retain null setup metadata.
- Breaking: Orders containing loyalty vouchers now require an authoritative
loyaltyProgramCustomerIdfor the customer in the eCom store's configured loyalty program. Missing or unverified identities return HTTP400before the order is created. Register the customer throughPOST /customers/{customerId}/loyalty_membershipbefore applying vouchers. Order delivery and voucher redemption no longer discover or associate loyalty customers by phone number.
- API clients can now request one token with space-separated scopes across Core, e-commerce, loyalty programs and demand planning. Use
ecom:read,ecom:writeandloyalty_programs:readwithoutresourceto access all stores or programs selected on the client. Read and write grants are independent; requests without the required scope or selected resource return403, and token requests containing any ungranted scope fail withinvalid_scope. Legacy single-resourceecomandloyalty_programstoken requests remain supported;ecomrequires both read and write grants. Mixed tokens and the new eCom and loyalty scopes use the tenant audience; Core-only and demand-planning-only requests retain their existing audiences. Previously issued tokens remain valid until expiry. See Get started for the authentication contract. - Integrations can rotate an API client secret without changing the Client ID or token-request parameters. The current and previous secrets both work until an administrator explicitly revokes the previous secret; revoke it before another rotation. Revocation prevents new token issuance with that secret, while already issued tokens remain valid until expiry. See Secret rotation.
- eCom customer responses now include nullable
loyaltyProgramCustomerId: the authoritative customer ID in the store's configured loyalty program, not proof of active membership. Unmarked legacy links returnnull. This field is read-only and ignored in customer writes. - B2C eCom integrations can register or re-invite a loyalty program customer through
POST /customers/{customerId}/loyalty_membershipunder their authenticated eCom store. No body is needed; SnowBee uses the store's configured loyalty program and returns the updated customer synchronously. Identity conflicts return409, and loyalty program service failures return503. Repeated calls can resend onboarding SMS.
- Demand-planning batch results now report items whose replenishment values already match SnowBee in
counts.unchanged. These items are not written and continue to inherit their assortment defaults.
- Upcoming experimental Core reads include sales orders with embedded lines, typed customer references, fulfillment and invoicing progress, click-and-collect references and retail prepayment history. Existing
transactions:readorcore:readgrants include the new collection. Manual retail-store orders to B2C customers are classified as retail orders too. Related changes update the order for incremental synchronization, and order deletions return tombstones. See the Core integration guide; production availability has not yet been announced. - Experimental Core API export schemas now identify each collection's entity type and distinguish live entities from deletion records. Generated client types no longer include unrelated collections. Response JSON is unchanged.
-
Upcoming experimental Core API: synchronize tenant brands, retail stores, eCom stores and invoices with timestamp pagination and retained deletion items, and create brands. Enable explicit domain permissions or
core:read/core:writeon the API client; read and write grants are independent. Use a configurable overlap window when polling changes and periodically reconcile a full read; database timestamps do not guarantee commit ordering. Production availability has not yet been announced. See the Core integration guide for checkpoints, pagination and supported operations. -
Upcoming Core master-data reads also cover product categories, main and sub product groups, product concepts, lifecycle states, sizes, sales-order types, reason codes, resources, dynamic attributes with options, VAT groups with rates, and loyalty programs without credentials. Existing
master_data:readorcore:readgrants include these collections. Owned-child changes and moves make their parent entities appear as changed; child deletions return updated parents. -
Breaking change: Removed Main activity classification: ecommerce product responses no longer include
mainActivityId, Core product-concept responses no longer includemain_activity_id, andGET /product_schema/main_activitiesnow returns 404. TheMainActivitiesUpdatednotification is no longer published. -
Upcoming Core reads also include companies and warehouses under
organisational_units:read, and retail chains undermaster_data:read. Company and warehouse addresses are embedded; warehouses also include associated company IDs. Address and relationship changes update the owning entity for incremental synchronization, and root deletions return tombstones. -
Breaking change: Core sub-product-group responses no longer include
default_distribution_strategy. Distribution strategy has been retired; integrations must stop reading that field. -
Upcoming Core reads include B2B and B2C customers under
customers:read, and external suppliers undersuppliers:read. Addresses, consumer loyalty memberships and supplier contact people are embedded. Organisation exports identify their active customer/supplier roles, and invoices include a concretecustomer_typealongsidecustomer_id. Child and role changes update the owning entity for synchronization; disabling a role keeps its historical identity, while root deletion emits a tombstone. Sales-order and purchase-order exports remain deferred. See the Core integration guide.
GET /campaignsnow includesvalidFromTime,validToTime,timeZone, and resolvedvalidFromAt/validToAttimestamps. B2C integrations must use the inclusive start and exclusive end timestamps to respect timed campaigns;24:00means midnight on the next calendar date. Existing full-day campaigns keep their current validity. B2B campaign availability continues to use ordering dates.GET /campaignsnow exports selectedmemberLevels, including configured level IDs, provider codes and names, and explicitNORMALbase-member selections. An empty selection adds no level restriction.- Product responses now apply timed campaigns to current prices and include exact activation and expiry instants in
upcomingPriceCalendar, including later changes on the same day. Integrations caching product prices must schedule refreshes at eachactivatesAttimestamp. - E-commerce clients can now read the tenant's IANA
timeZonefromGET /settings, including when no campaigns are available. The tenant time zone is fixed when the tenant is created; changing it is not supported.