> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://channel3.ferndocs.com/docs/variants/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://channel3.ferndocs.com/_mcp/server. # Variants 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: ```json { "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 | Field | Type | Description | | ---------- | ------------------ | ----------------------------------------------------------------------------------------------------- | | `options` | `VariantOption[]` | Every dimension the product can be configured along. Render one selector per option. | | `selected` | `SelectedOption[]` | The effective selection after the server resolves your request. Use it to highlight the active value. | ### VariantOption | Field | Type | Description | | -------- | --------------- | ------------------------------------------------------------------------ | | `name` | `string` | The dimension name (`"Color"`, `"Size"`). Use as the selector's label. | | `values` | `OptionValue[]` | Every possible value for this dimension across the whole product family. | ### OptionValue | Field | Type | Description | | --------------- | ---------------------------- | ------------------------------------------------------------------------------------ | | `label` | `string` | The display value (`"Blue"`, `"XL"`). Also the value you send when selecting. | | `exists` | `boolean` | Whether this value forms a real variant given the other selected options. | | `available` | `AvailabilityStatus \| null` | Stock status. Always `null` on search results — only hydrated on product detail. | | `thumbnail_url` | `string \| null` | A swatch image for this value. When set, render as an image swatch rather than text. | | `product_id` | `string \| null` | When set, this value resolves to a different product. Navigate to that ID instead. | ### AvailabilityStatus | Value | Meaning | | --------------------- | ------------------------------------ | | `InStock` | Purchasable now. | | `LimitedAvailability` | Purchasable, low stock. | | `PreOrder` | Orderable ahead of release. | | `BackOrder` | Orderable, ships when restocked. | | `SoldOut` | Temporarily unpurchasable. | | `OutOfStock` | Not currently purchasable. | | `Discontinued` | No longer offered. | | `Unknown` | Stock 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/search` | `GET /v1/products/{id}` | | ----------------------------- | ----------------- | ----------------------- | | `options`, `values`, `labels` | Full set | Full set | | `exists` | Populated | Populated | | `thumbnail_url`, `product_id` | Populated | Populated | | `available` | Always `null` | Hydrated 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). **`Python`** ```python title="Python" 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) ``` **`TypeScript`** ```typescript title="TypeScript" import { Channel3 } from "@channel3/sdk"; const client = new Channel3(); // 1. Discover — variants present, but `available` is null on every value const results = await client.products.search({ query: "merino wool sweater" }); const product = results.products[0]; for (const option of product.variants.options) { console.log(option.name, option.values.map(v => v.label)); } // 2. Display — refetch to hydrate `available` const detail = await client.products.retrieve(product.id); for (const option of detail.variants.options) { for (const value of option.values) { console.log(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_=