Documentation

Variant Groups API

Link catalog products that are the same device in different finishes, and manage the members of each link.

A variant group is a named link between catalog products that are the same device in different finishes, for example a ceiling speaker sold in white and in black. Every member of a group is a catalog product of the same make. The group's optionName says what differs between the members, for example Colour. Each member's variantLabel is that member's value for the option, for example White.

Rules

  • A group holds one make. The group takes its make from its products, so no request sends it.
  • A product belongs to one group at most. Remove it from its group before you add it to another.
  • Creating a group takes two or more products.
  • A group's name is unique within its make. The comparison ignores letter case and surrounding spaces.
  • Removing the last member of a group deletes the group.
  • Deleting a group or removing a member never deletes a product. The product stays in the catalog and loses its group and its label.
  • Each write is checked in full before anything is saved. When a rule is broken, nothing is written and the response lists every problem under VARIANT_GROUP_RULE.
  • Text values are trimmed. A name holds up to 100 characters, an optionName up to 50, a description up to 500 and a variantLabel up to 50.

To copy connection metadata from one member to the others, use POST /api/v1/products/{id}/metadata/apply-to-variants in the Products API.


GET /api/v1/variant-groups

Returns the variant groups in the catalog, ordered by name.

Query Parameters

Field Type Required Description
search string No Matches the group name or the make. The match ignores letter case and finds the text anywhere in either value. Leave it out to list every group.
limit integer No The most groups to return. The default is 50. A value above 500 is read as 500. A value below 1 is read as 20.

Example Request

GET /api/v1/variant-groups?search=biamp&limit=10
Authorization: Bearer YOUR_JWT_TOKEN

Response (200 - Success)

{
    "success": true,
    "data": [
        {
            "id": 12,
            "make": "Biamp",
            "name": "C-IC6LP",
            "optionName": "Colour",
            "memberCount": 2,
            "labels": ["Black", "White"]
        }
    ],
    "message": "Retrieved 1 variant groups",
    "timestamp": "2026-10-08T15:30:00Z"
}
Field Type Description
id integer The variant group's ID
make string The make that every member shares
name string The group name
optionName string What differs between the members, for example Colour
memberCount integer The number of products in the group
labels array of string The members' labels, ordered by the members' model numbers

The response has no paging and no total count. When it holds exactly limit groups, more groups may exist. Send a narrower search to reach them.


GET /api/v1/variant-groups/{id}

Returns one variant group with its members, ordered by model number.

Path Parameters

Field Type Required Description
id integer Yes The variant group's ID

Example Request

GET /api/v1/variant-groups/12
Authorization: Bearer YOUR_JWT_TOKEN

Response (200 - Success)

{
    "success": true,
    "data": {
        "id": 12,
        "make": "Biamp",
        "name": "C-IC6LP",
        "optionName": "Colour",
        "description": "6.5-inch low-profile ceiling speaker",
        "members": [
            {
                "productId": 413,
                "model": "C-IC6LP-B",
                "part": "C-IC6LP-B",
                "variantLabel": "Black",
                "catalogPrice": 205.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": false
            },
            {
                "productId": 412,
                "model": "C-IC6LP-W",
                "part": "C-IC6LP-W",
                "variantLabel": "White",
                "catalogPrice": 212.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": true
            }
        ]
    },
    "message": null,
    "timestamp": "2026-10-08T15:30:00Z"
}
Field Type Description
description string or null The group's description
members[].productId integer The member's catalog product ID
members[].model string The product's model number
members[].part string or null The product's part number
members[].variantLabel string The member's value for the group's option
members[].catalogPrice number or null The product's catalog price
members[].vendor string or null The product's vendor
members[].productType string or null The product's type
members[].hasMetadata boolean true when the product has connection metadata

The fields id, make, name and optionName have the same meaning as in the list. Every endpoint below that returns a group returns this shape.

Error Responses

StatusCodeDescription
404VARIANT_GROUP_NOT_FOUNDNo variant group has this ID

POST /api/v1/variant-groups

Creates a variant group from two or more products. The products must be of one make, and none of them may already be in a group.

Request Body

Field Type Required Description
name string Yes The group name, up to 100 characters. It is usually the part of the model numbers that the members share. It must be unique within the make.
optionName string Yes What differs between the members, for example Colour or Finish. Up to 50 characters.
description string No A description of the group, up to 500 characters
members array Yes The products to group. The list must name two or more different products.
members[].productId integer Yes A catalog product ID. A product may be listed once.
members[].variantLabel string Yes The product's value for the option, for example White. Up to 50 characters.

Example Request

{
    "name": "C-IC6LP",
    "optionName": "Colour",
    "description": "6.5-inch low-profile ceiling speaker",
    "members": [
        { "productId": 412, "variantLabel": "White" },
        { "productId": 413, "variantLabel": "Black" }
    ]
}

Response (201 - Created)

The response carries a Location header that points at the new group.

{
    "success": true,
    "data": {
        "id": 12,
        "make": "Biamp",
        "name": "C-IC6LP",
        "optionName": "Colour",
        "description": "6.5-inch low-profile ceiling speaker",
        "members": [
            {
                "productId": 413,
                "model": "C-IC6LP-B",
                "part": "C-IC6LP-B",
                "variantLabel": "Black",
                "catalogPrice": 205.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": false
            },
            {
                "productId": 412,
                "model": "C-IC6LP-W",
                "part": "C-IC6LP-W",
                "variantLabel": "White",
                "catalogPrice": 212.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": true
            }
        ]
    },
    "message": "Variant group created",
    "timestamp": "2026-10-08T15:30:00Z"
}

Error Responses

StatusCodeDescription
400VARIANT_GROUP_RULEA rule was broken: fewer than two products, more than one make, a product that is already in a group, a product ID that was not found, a product listed twice, a missing or too long value, or a name the make already uses
400UNKNOWN_FIELDSThe body names a field other than name, optionName, description and members
403READ_ONLY_ACCESSThe caller has read-only access

PUT /api/v1/variant-groups/{id}

Renames a variant group, changes its option name, or changes its description. Send only the fields to change. A field that is left out keeps its value. The make and the members cannot be changed with this endpoint.

Path Parameters

Field Type Required Description
id integer Yes The variant group's ID

Request Body

Field Type Required Description
name string No The new group name, up to 100 characters. It must be unique within the make.
optionName string No The new option name, up to 50 characters
description string No The new description, up to 500 characters. An empty string clears it.

Example Request

{
    "optionName": "Finish"
}

Response (200 - Success)

{
    "success": true,
    "data": {
        "id": 12,
        "make": "Biamp",
        "name": "C-IC6LP",
        "optionName": "Finish",
        "description": "6.5-inch low-profile ceiling speaker",
        "members": [
            {
                "productId": 413,
                "model": "C-IC6LP-B",
                "part": "C-IC6LP-B",
                "variantLabel": "Black",
                "catalogPrice": 205.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": false
            },
            {
                "productId": 412,
                "model": "C-IC6LP-W",
                "part": "C-IC6LP-W",
                "variantLabel": "White",
                "catalogPrice": 212.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": true
            }
        ]
    },
    "message": "Variant group updated",
    "timestamp": "2026-10-08T15:30:00Z"
}

Error Responses

StatusCodeDescription
400UNKNOWN_FIELDSThe body names a field other than name, optionName and description
400NO_CHANGESThe body names none of the three fields
400VARIANT_GROUP_RULEA rule was broken: an empty name or optionName, a value that is too long, or a name the make already uses for another group
403READ_ONLY_ACCESSThe caller has read-only access
404VARIANT_GROUP_NOT_FOUNDNo variant group has this ID

DELETE /api/v1/variant-groups/{id}

Deletes a variant group. Its products stay in the catalog and lose their group and their labels.

Path Parameters

Field Type Required Description
id integer Yes The variant group's ID

Example Request

DELETE /api/v1/variant-groups/12
Authorization: Bearer YOUR_JWT_TOKEN

Response (200 - Success)

{
    "success": true,
    "data": {
        "id": 12,
        "deleted": true
    },
    "message": "Variant group deleted; its products are ungrouped",
    "timestamp": "2026-10-08T15:30:00Z"
}

Error Responses

StatusCodeDescription
403READ_ONLY_ACCESSThe caller has read-only access
404VARIANT_GROUP_NOT_FOUNDNo variant group has this ID

POST /api/v1/variant-groups/{id}/members

Adds products to a variant group. Each product must be of the group's make and must not already be in a group.

Path Parameters

Field Type Required Description
id integer Yes The variant group's ID

Request Body

Field Type Required Description
members array Yes The products to add. The list must name at least one product.
members[].productId integer Yes A catalog product ID. A product may be listed once.
members[].variantLabel string Yes The product's value for the option, up to 50 characters

Example Request

{
    "members": [
        { "productId": 414, "variantLabel": "Silver" }
    ]
}

Response (200 - Success)

{
    "success": true,
    "data": {
        "id": 12,
        "make": "Biamp",
        "name": "C-IC6LP",
        "optionName": "Colour",
        "description": "6.5-inch low-profile ceiling speaker",
        "members": [
            {
                "productId": 413,
                "model": "C-IC6LP-B",
                "part": "C-IC6LP-B",
                "variantLabel": "Black",
                "catalogPrice": 205.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": false
            },
            {
                "productId": 414,
                "model": "C-IC6LP-S",
                "part": "C-IC6LP-S",
                "variantLabel": "Silver",
                "catalogPrice": 219.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": false
            },
            {
                "productId": 412,
                "model": "C-IC6LP-W",
                "part": "C-IC6LP-W",
                "variantLabel": "White",
                "catalogPrice": 212.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": true
            }
        ]
    },
    "message": "Products added to the variant group",
    "timestamp": "2026-10-08T15:30:00Z"
}

Error Responses

StatusCodeDescription
400VARIANT_GROUP_RULEA rule was broken: an empty list, a product of another make, a product that is already in a group, a product ID that was not found, a product listed twice, or a missing or too long label
403READ_ONLY_ACCESSThe caller has read-only access
404VARIANT_GROUP_NOT_FOUNDNo variant group has this ID

PUT /api/v1/variant-groups/{id}/members/{productId}

Changes the label of one member of a variant group.

Path Parameters

Field Type Required Description
id integer Yes The variant group's ID
productId integer Yes The catalog product ID of the member

Request Body

Field Type Required Description
variantLabel string Yes The member's new label, up to 50 characters

Example Request

{
    "variantLabel": "Arctic White"
}

Response (200 - Success)

{
    "success": true,
    "data": {
        "id": 12,
        "make": "Biamp",
        "name": "C-IC6LP",
        "optionName": "Colour",
        "description": "6.5-inch low-profile ceiling speaker",
        "members": [
            {
                "productId": 413,
                "model": "C-IC6LP-B",
                "part": "C-IC6LP-B",
                "variantLabel": "Black",
                "catalogPrice": 205.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": false
            },
            {
                "productId": 414,
                "model": "C-IC6LP-S",
                "part": "C-IC6LP-S",
                "variantLabel": "Silver",
                "catalogPrice": 219.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": false
            },
            {
                "productId": 412,
                "model": "C-IC6LP-W",
                "part": "C-IC6LP-W",
                "variantLabel": "Arctic White",
                "catalogPrice": 212.00,
                "vendor": "ADI Global",
                "productType": "Ceiling Speaker",
                "hasMetadata": true
            }
        ]
    },
    "message": "Variant label updated",
    "timestamp": "2026-10-08T15:30:00Z"
}

Error Responses

StatusCodeDescription
400VARIANT_GROUP_RULEThe label is empty or longer than 50 characters
403READ_ONLY_ACCESSThe caller has read-only access
404VARIANT_GROUP_MEMBER_NOT_FOUNDThe product is not a member of this group, or no variant group has this ID

DELETE /api/v1/variant-groups/{id}/members/{productId}

Takes one product out of a variant group and clears its label. The product stays in the catalog. Removing the last member deletes the group.

Path Parameters

Field Type Required Description
id integer Yes The variant group's ID
productId integer Yes The catalog product ID of the member

Example Request

DELETE /api/v1/variant-groups/12/members/414
Authorization: Bearer YOUR_JWT_TOKEN

Response (200 - Success)

{
    "success": true,
    "data": {
        "groupId": 12,
        "productId": 414,
        "removed": true
    },
    "message": "Product removed from the variant group",
    "timestamp": "2026-10-08T15:30:00Z"
}

Error Responses

StatusCodeDescription
403READ_ONLY_ACCESSThe caller has read-only access
404VARIANT_GROUP_MEMBER_NOT_FOUNDThe product is not a member of this group, or no variant group has this ID

POST /api/v1/variant-groups/suggest

Suggests a variant group for a set of products: a group name, an option name and one label for each product. The endpoint writes nothing, so a caller with read-only access can use it. Use the result to fill a create request.

Request Body

Field Type Required Description
productIds array of integer Yes The catalog product IDs to group. Send two or more.

Example Request

{
    "productIds": [412, 413]
}

Response (200 - Success)

{
    "success": true,
    "data": {
        "name": "C-IC6LP",
        "optionName": "Colour",
        "members": [
            {
                "productId": 412,
                "make": "Biamp",
                "model": "C-IC6LP-W",
                "variantLabel": "White"
            },
            {
                "productId": 413,
                "make": "Biamp",
                "model": "C-IC6LP-B",
                "variantLabel": "Black"
            }
        ],
        "problems": []
    },
    "message": null,
    "timestamp": "2026-10-08T15:30:00Z"
}
Field Type Description
name string The start of the model numbers that all the products share. It is an empty string when they share none.
optionName string The suggested option name. It is always Colour.
members array One entry for each product that was found, in the order of the request. Each entry holds the product's productId, make and model, and a suggested variantLabel.
problems array of string Each reason these products cannot be grouped as they are: fewer than two products, a product ID that was not found, more than one make, or a product that is already in a group. The list is empty when none of these applies.

A set of products that cannot be grouped still returns 200. Read problems to find out. The name is yours to change, so problems does not report a name that the make already uses. The create endpoint reports that.


Errors

The two not-found codes, UNKNOWN_FIELDS and NO_CHANGES use the standard error body. See Error Handling.

{
    "success": false,
    "error": {
        "code": "VARIANT_GROUP_NOT_FOUND",
        "message": "Variant group with ID 12 not found",
        "details": null
    },
    "timestamp": "2026-10-08T15:30:00Z"
}

400 VARIANT_GROUP_RULE

A broken rule returns a different body. error.problems is an array with one sentence for each broken rule. error.message is the same sentences joined with ; . This body has no details field and no timestamp.

{
    "success": false,
    "error": {
        "code": "VARIANT_GROUP_RULE",
        "message": "Enter a group name.; Product 999 was not found.",
        "problems": [
            "Enter a group name.",
            "Product 999 was not found."
        ]
    }
}

The same code and body are returned by PUT /api/v1/products/{id} when it would change the make of a product that is in a group, and by POST /api/v1/products/{id}/metadata/apply-to-variants when the source product has no connection metadata.

StatusCodeDescription
400VARIANT_GROUP_RULEA rule was broken and nothing was written. error.problems lists every broken rule.
400UNKNOWN_FIELDSThe body of a create or an update names a field the endpoint does not accept. The message lists those fields and the fields that are valid.
400NO_CHANGESAn update named none of name, optionName and description
403READ_ONLY_ACCESSThe caller has read-only access. Every endpoint on this page returns it except the two GET endpoints and suggest.
404VARIANT_GROUP_NOT_FOUNDNo variant group has this ID. Returned by get, update, delete and add members.
404VARIANT_GROUP_MEMBER_NOT_FOUNDThe product is not a member of the group, or the group does not exist. Returned by the two /members/{productId} endpoints.
A null is not the same as a missing field

On the endpoints that write, a required field that is left out is reported as a VARIANT_GROUP_RULE problem, for example Enter a group name. A JSON null in place of a required field is refused earlier, by request validation. So is a member entry that has no variantLabel. That 400 response uses the framework's validation format, with title, status and errors. It does not use the error bodies shown above.