Skip to content

Custom attributes

Brands define their own typed attributes — volume, material, fragrance family, care instructions — and every product carries their values in attributes.

GET/attributes
Definitions (filter by type or group)
POST/attributes
Create an attribute
PATCH/attributes/{idOrCode}
Change label, options, validation …
DELETE/attributes/{idOrCode}
Delete and remove its values
GET/attribute-groups
Groups (sections in the editor)
GET/categories/{idOrCode}/attributes
Attributes that apply in a category
PUT/categories/{idOrCode}/attributes
Assign attributes to a category

Types and values

TypeValue in JSONExample
textstring"Eau de parfum"
textareastring (multi-line)"Top: bergamot\nHeart: rose"
richtextstring (sanitized HTML)"<p>Hand-poured</p>"
integernumber (whole)100
decimalnumber12.5
booleantrue / falsetrue
selectoption code"woody-oriental"
multiselectlist of option codes["cotton", "linen"]
date"yyyy-mm-dd""2026-10-01"
urlhttp(s) URL"https://example.com/sds.pdf"
color"#rrggbb""#8A5A2B"

On write, values are coerced when unambiguous: "100" becomes 100 for integers, "yes"/"true"/"1" become true, a select accepts the option label or code and stores the code, and a multiselect accepts "a|b" or an array. Invalid values answer 422 with a field error such as attributes.volume_ml. Set a value to null to remove it.

Create an attribute

cURL
bash
curl -X POST "https://library.retailcommerceai.com/api/v1/attributes" \
  -H "Authorization: Bearer cl_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "fragrance_family",
  "label": "Fragrance family",
  "type": "select",
  "scope": "category",
  "groupCode": "specifications",
  "options": [
    {
      "code": "woody-oriental",
      "label": "Woody oriental",
      "translations": {
        "ar": "خشبي شرقي"
      }
    },
    {
      "code": "floral",
      "label": "Floral",
      "translations": {
        "ar": "زهري"
      }
    }
  ],
  "translations": {
    "ar": {
      "label": "عائلة العطر"
    }
  }
}'
  • code is lowercase letters, digits and _; it is the key in product.attributes and in file columns (attr.fragrance_family). It cannot change once products use it.
  • scope: "global" applies to every product; "category" only to products in the categories it is assigned to (and their subcategories).
  • required counts toward completeness; isVariantAxis lets it distinguish variants (select, text, integer).
  • validation adds minLength, maxLength, pattern, min, max or decimals.

Attributes per category

Assign category-scoped attributes to a category — optionally as required there — and every product in it or its subcategories gets them:

cURL
bash
curl -X PUT "https://library.retailcommerceai.com/api/v1/categories/fragrance/attributes" \
  -H "Authorization: Bearer cl_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "attributes": [
    {
      "attribute": "fragrance_family",
      "required": true
    },
    {
      "attribute": "volume_ml",
      "required": true
    }
  ]
}'

GET /categories/{code}/attributes answers with the effective list (global plus the category chain) and each attribute's effectiveRequired flag — exactly what a product in that category must fill.

Localized values

Text attributes marked localizable have a value per language: the default one in attributes, others in translations.<locale>.attributes. Select option labels are translated on the attribute definition (options[].translations) while products store the language-neutral option code.

cURL
bash
curl -X PATCH "https://library.retailcommerceai.com/api/v1/products/FR-OUD-100" \
  -H "Authorization: Bearer cl_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "attributes": {
    "scent_notes": "Oud, amber, saffron"
  },
  "translations": {
    "ar": {
      "attributes": {
        "scent_notes": "عود، عنبر، زعفران"
      }
    }
  }
}'

Deleting is permanent

Deleting an attribute removes its values from every product. Rename it or stop assigning it instead if you may need the data again.