Skip to navigation

Variants

Render and interact with product variant selectors — sizes, colors, and other options — using the Channel3 API.

Many products come in more than one configuration: a shirt in several colors and sizes, a shoe in multiple widths, a phone in different storage tiers. Channel3 models these as variants, exposed on the variants field of a Product.

The variant data model

Product.variants is either null (the product has no variations) or a Variants object:

{
"variants": {
"options": [
{
"name": "Color",
"values": [
{
"label": "Midnight Blue",
"exists": true,
"available": "InStock",
"thumbnail_url": "https://cdn.trychannel3.com/asdf",
"product_id": "x8k2mq4"
},
{
"label": "Forest Green",
"exists": true,
"available": "OutOfStock",
"thumbnail_url": "https://cdn.trychannel3.com/abcd",
"product_id": "p3n7wz9"
}
]
},
{
"name": "Size",
"values": [
{ "label": "S", "exists": true, "available": "InStock" },
{ "label": "M", "exists": true, "available": "InStock" },
{ "label": "XL", "exists": false, "available": null }
]
}
],
"selected": [
{ "name": "Color", "label": "Midnight Blue" },
{ "name": "Size", "label": "M" }
]
}
}

Variants

FieldTypeDescription
optionsVariantOption[]Every dimension the product can be configured along. Render one selector per option.
selectedSelectedOption[]The effective selection after the server resolves your request. Use it to highlight the active value.

VariantOption

FieldTypeDescription
namestringThe dimension name ("Color", "Size"). Use as the selector’s label.
valuesOptionValue[]Every possible value for this dimension across the whole product family.

OptionValue

FieldTypeDescription
labelstringThe display value ("Blue", "XL"). Also the value you send when selecting.
existsbooleanWhether this value forms a real variant given the other selected options.
availableAvailabilityStatus | nullStock status. Always null on search results — only hydrated on product detail.
thumbnail_urlstring | nullA swatch image for this value. When set, render as an image swatch rather than text.
product_idstring | nullWhen set, this value resolves to a different product. Navigate to that ID instead.

AvailabilityStatus

ValueMeaning
InStockPurchasable now.
LimitedAvailabilityPurchasable, low stock.
PreOrderOrderable ahead of release.
BackOrderOrderable, ships when restocked.
SoldOutTemporarily unpurchasable.
OutOfStockNot currently purchasable.
DiscontinuedNo longer offered.
UnknownStock state could not be determined.

Search vs. product detail

The same variants shape is returned by both endpoints, but available is only populated on product detail.

POST /v1/searchGET /v1/products/{id}
options, values, labelsFull setFull set
existsPopulatedPopulated
thumbnail_url, product_idPopulatedPopulated
availableAlways nullHydrated per value

Search is optimized for breadth — it returns the full option matrix so you can render selectors immediately, but it does not compute per-value stock. When you display a product for purchase, refetch it with GET /v1/products/{id} to get live available values (this call is free).

from channel3_sdk import Channel3
client = Channel3()
# 1. Discover — variants present, but `available` is null on every value
results = client.products.search(query="merino wool sweater")
product = results.products[0]
for option in product.variants.options:
print(option.name, [v.label for v in option.values])
# 2. Display — refetch to hydrate `available`
detail = client.products.retrieve(product.id)
for option in detail.variants.options:
for value in option.values:
print(option.name, value.label, value.exists, value.available)

Selecting a variant

To resolve a specific configuration, pass each chosen value to GET /v1/products/{id} as an option_<Name>=<Label> query parameter:

curl "https://api.trychannel3.com/v1/products/x8k2mq4?option_Color=Forest%20Green&option_Size=M" \
-H "x-api-key: $CHANNEL3_API_KEY"

The response reflects your selection in two places:

  • product fields (title, price, image, offers) update to the matching variant.
  • variants.selected echoes the effective selection.

Relaxation

If the exact combination you requested doesn’t exist, the server relaxes your selection to the closest valid variant instead of returning nothing. Always read variants.selected after the call to detect what actually happened:

requested = {"Color": "Forest Green", "Size": "XL"}
detail = client.products.retrieve("x8k2mq4")
effective = {s.name: s.label for s in detail.variants.selected}
for name, label in requested.items():
if effective.get(name) != label:
print(f"{name}: requested {label!r}, resolved to {effective[name]!r}")

Some option values point at a separate product rather than reconfiguring the current one. These values have a non-null product_id (and usually a thumbnail_url). When a shopper picks one, navigate to that product ID:

function handleSelect(option: VariantOption, value: OptionValue) {
if (value.product_id && value.product_id !== currentProductId) {
// This value is a different product — navigate to it
navigate(`/products/${value.product_id}`);
} else {
// Same product, different configuration — re-resolve with option params
setSelection({ ...selection, [option.name]: value.label });
}
}

available vs. exists

exists and available describe two different things:

  1. Purchasable (exists: true, in stock) — Full emphasis. The selected value gets a colored border.
  2. Out of stock (exists: true, available is SoldOut/OutOfStock/Discontinued) — Dimmed. It’s a real variant you can’t buy right now.
  3. Doesn’t exist (exists: false) — Faintest. Selecting it would change another option. Show as a faint, disabled swatch.