Skip to main content

Reference data setup

Reference data — brands, categories, styles, seasons, colours, sizes, merchandise departments, and suppliers — is the vocabulary every SKU is described with. Set it up first: SKUs reference these dimensions by key, and the rest of the platform (items, places, orders) is built on SKUs.

This guide covers creating the dimensions, the order to create them in, and the natural-key conventions that let a downstream integration (an IMS/ERP feed) create SKUs without ever looking up a GUID.

Overview

All eight dimensions are CRUD reference-data controllers that behave identically — once you've learned one, you've learned them all. Each exposes:

VerbRoute patternAccess modeReturns
POST/api/<Dim>s/Create<Dim>Createthe Detail DTO
GET/api/<Dim>s/GetFiltered<Dim>sReadPageResult<<Dim>SummaryDto>
GET/api/<Dim>s/Get<Dim>/{id}Readthe Detail DTO
POST/api/<Dim>s/Update<Dim>Updatethe Detail DTO
DELETE/api/<Dim>s/Delete<Dim>/{id}Deletetrue
POST/api/<Dim>s/BulkUpsertCreate + Updateper-row result list

All are gated on the ReferenceData module permission (see the permission bitmask). All follow the platform-wide error and response conventions.

Prerequisites

Which order to create dimensions in

Two dimensions are hierarchical (self-referential parent link): Brand (ParentBrandId) and Category (ParentCategoryId). The rest are flat.

  1. Flat dimensions first, in any order — Style, Season, Colour, Size, Merchandise Department, Supplier.
  2. Hierarchical dimensions roots-first — create a Brand/Category with no parent, then its children referencing the parent's Id. A create/update that points ParentBrandId at a brand that doesn't exist returns 400 ("ParentBrandId"), and one that would form a cycle returns 400 ("ParentBrandId would create a cycle"; the cycle check walks up to 32 levels).

You do not have to create dimensions before SKUs if you enable auto-create — see Letting SKU import create dimensions.

Step-by-step

1. Create a flat dimension

curl -sk -X POST "http://<host>/api/Styles/CreateStyle" \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"Name":"Crew Neck","ExternalRef":"style_crew"}'

Returns 200 with the Detail DTO — a new Id, the fields echoed, audit fields, and EntityVersionNo: 1.

2. Create a hierarchical dimension, roots-first

Create the root brand:

curl -sk -X POST "http://<host>/api/Brands/CreateBrand" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"Name":"Nike","ExternalRef":"nike_01","ParentBrandId":null}'

Then a child referencing the root's Id:

curl -sk -X POST "http://<host>/api/Brands/CreateBrand" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{"Name":"Air Jordan","ExternalRef":"aj_01","ParentBrandId":"<nikeId>"}'

The child's response carries both ParentBrandId and a flattened ParentBrandName ("Nike").

3. List and filter

# All brands (paged)
curl -sk "http://<host>/api/Brands/GetFilteredBrands" \
-H "Authorization: Token $TOKEN"

# OData filter — $ must be percent-escaped as %24 in curl
curl -sk "http://<host>/api/Brands/GetFilteredBrands?%24filter=Name%20eq%20%27Nike%27" \
-H "Authorization: Token $TOKEN"

The list response is a PageResult — the rows are under Items. See Errors & response conventions for the OData functions that are safe to use.

4. Update and delete

Update is a full-replace POST carrying the Id (there is no PUT); send every field, not just the changed ones. EntityVersionNo increments on success. Delete is a soft delete (DELETE …/Delete<Dim>/{id}true); the row's Name/ExternalRef become free to reuse (see natural-key reuse).

Natural keys: the merchandise-department & season exception

SKU imports reference dimensions by key rather than GUID, resolved in this precedence: IdExternalRefName (first non-null wins; the rest are ignored). One wrinkle:

Merchandise Department & Season use Code, not ExternalRef

These two dimensions have no ExternalRef column — their natural key is Code. When a SKU import supplies MerchandiseDepartmentExternalRef or SeasonExternalRef, the resolver matches it against the entity's Code column. Every other dimension (Brand, Category, Style, Supplier) matches ExternalRef directly. Colour and Size resolve by Name.

Letting SKU import create dimensions

The per-tenant flag AutoCreateReferenceData (default false) lets a SKU create/import mint missing dimensions on the fly instead of failing. Toggle it via configuration:

# Read current configuration (full DTO)
curl -sk "http://<host>/api/configurations/getconfigurations" \
-H "Authorization: Token $TOKEN"

# setconfigurations is a full-DTO replace — GET first, change the one flag, POST it back
curl -sk -X POST "http://<host>/api/configurations/setconfigurations" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{ ...full config..., "AutoCreateReferenceData": true }'

With the flag on, a createsku referencing an unknown BrandExternalRef and supplying BrandName creates the brand and links it. With the flag off (or with no Name to create from), the unknown reference fails 400 ("BrandExternalRef '…' not found"). This flag affects single createsku and SKU bulk-upsert; dimension-level bulk-upsert always creates missing rows regardless of the flag.

Bulk sync from an external system (IMS/ERP)

Each dimension's BulkUpsert endpoint is the idempotent sync entry point. Rows are matched by (Organisation, ExternalRef) (or Code); it is designed to be called on a schedule without prior lookups.

Two behaviours to code against — both covered in response conventions:

  • The HTTP status is always 200. Success/failure is per row (Created / Updated / Unchanged / Failed, with ErrorMessage on failures). Never read the HTTP code to decide whether the batch worked — read the per-row Status.
  • A batch with any failed row is rolled back (all-or-nothing). Re-chunk around the failed rows and resubmit.
  • Re-submitting an unchanged payload returns Unchanged for every row — safe to re-run.

For hierarchical dimensions, a parent referenced by BulkUpsertDimItemDto.ParentExternalRef must already exist in the database — an in-batch parent that only appears earlier in the same request is not resolved. Load parents in an earlier batch, children in a later one.

Error handling

SituationResponse
Duplicate Name or ExternalRef (same tenant, non-deleted)400, Message names the field + value
Empty required Name400, Message: "Name"
Unknown parent id400, Message: "ParentBrandId"
Parent link forms a cycle400, "ParentBrandId would create a cycle"
Get/Update/Delete unknown id400 — Get returns []; Update/Delete return NotFound