Channel3 API Integration Guide
Channel3 is a universal product catalog API. Pick the endpoint(s) that match the developer's input.
| Developer has... | Use |
|---|---|
| Free-text query, image URL, or both | POST /v1/search |
| Channel3 product_id and wants similar items | POST /v1/similar |
| Channel3 product_id and wants full details | GET /v1/products/{id} |
| A product_id and a variant choice (color/size/...) and wants the matching offers | GET /v1/products/{id} with option_<name>=<label> query params |
| A merchant URL and wants the canonical product | POST /v1/lookup |
| A product_id and wants price-change alerts | POST /v0/price-tracking/start |
| A free-text term and wants matching category slugs | GET /v1/categories/search |
| A color constraint (blue, navy, red, ...) | filters.colors on POST /v1/search or POST /v1/similar — map color names to sRGB hex; never filters.attributes.color |
| Non-color structured attributes (material, frame-color, ...) | filters.attributes on POST /v1/search or POST /v1/similar — discover handles/values via Category.attributes (skip the color handle) |
Base URL: https://api.trychannel3.com
Auth: x-api-key header
Full docs: docs.trychannel3.com (SDK setup, per-endpoint refs, error handling, retries, async usage)
Offline quick reference: references/api-reference.md
Anti-patterns (read first)
- Don't use
/v1/similarwith a free-text query or an image. Similar takes a Channel3product_id, not a query. If the user describes a product or has an image, that's/v1/search(queryand/orimage_url). - Don't reach for
/v1/lookupto seed/v1/similarwhen a/v1/searchby title would do. Lookup takes seconds and can fail on uncatalogued URLs. The intended flow issearch→ grabproduct.id→similar. Lookup-then-similar can be used if needed but is not an optimal flow for the majority of cases. - Locale (
country/currency) constrains which merchant offers come back. Pan-region storefronts can omitcountryand just setcurrency: "EUR". Default isen/US/USD.
Quick start
npm install @channel3/sdk # TypeScript
pip install channel3_sdk # Python
A Go SDK is also available — see docs.trychannel3.com/sdk.
Set CHANNEL3_API_KEY in the environment (free key at trychannel3.com). Then the minimum end-to-end call:
import { Channel3 } from '@channel3/sdk';
const client = new Channel3({ apiKey: process.env.CHANNEL3_API_KEY });
const { products } = await client.products.search({ query: 'running shoes', limit: 5 });
Locale defaults can be set client-wide via constructor (new Channel3({ country: 'GB', currency: 'GBP' })) or CHANNEL3_LANGUAGE / CHANNEL3_COUNTRY / CHANNEL3_CURRENCY env vars; per-call config.country / config.currency / config.language always wins. For async clients, error classes, retries, timeouts, and logging, see docs.trychannel3.com/sdk.
CLI (terminal & testing)
For ad-hoc API exploration from a terminal — sanity-checking a filter shape, grabbing a product_id to feed into integration tests, or one-off calls without a project — use the Channel3 CLI. It tracks the API spec automatically.
brew install channel3-ai/tap/channel3 # or: go install github.com/channel3-ai/cli/cmd/channel3@latest
export CHANNEL3_API_KEY="..."
channel3 products search --query "running shoes" --max-items 5 \
--filters '{"price":{"max_price":100},"gender":"male"}' \
--format jsonl --transform '{id,title,offers:offers.#.{domain,price:price.price,url}}'
Use the SDK for production code. The CLI is for terminal work.
UI components (React)
Channel3 UI is a source-available React component library, distributed as a shadcn registry and typed directly against @channel3/sdk — a ProductDetail from a search or product fetch drops straight in.
npx shadcn@latest add https://ui.trychannel3.com/r/all.json
- Blocks —
product-search(search bar + faceted filters + infinite-scroll grid) andproduct-details(full PDP: gallery, variant selection, offer comparison, price history, recommendations). - À la carte — components (
product-card,variant-selector,offers-list,image-gallery, …) and hooks (useProductSearch,useVariantSelection,useProductRecommendations, …). - Presentational by design — components take Channel3 data as props and emit intent through callbacks; they never call the API or touch your key. Fetch and shape data on your server (where
CHANNEL3_API_KEYlives), then pass results in.
Endpoints
Search — POST /v1/search
Text, image, or text+image search. Returns a SearchResponse with paginated ProductDetail[] and a next_page_token.
const response = await client.products.search({
query: 'running shoes under $100',
filters: { price: { max_price: 100 }, gender: 'male' },
limit: 10,
});
response = client.products.search(
query="running shoes under $100",
filters={"price": {"max_price": 100}, "gender": "male"},
limit=10,
)
Per-call locale override: config: { country: 'GB', currency: 'GBP' }. Pure keyword matching (skip semantic search): config: { keyword_search_only: true } — incompatible with image input. Full filter shape in references/api-reference.md; full per-endpoint schema at docs.trychannel3.com/api-reference.
Structured attribute and color filters
Color constraints use filters.colors, not filters.attributes. Map color words (navy, blue, red, ...) to sRGB hex. filters.attributes is for non-color handles only (material, frame-color, ...).
const response = await client.products.search({
query: 'leather sofa',
filters: {
category_ids: ['sofas'],
attributes: { material: ['Leather'] },
colors: {
palette: [
{ hex: '#001f3f' }, // navy — use colors for any color intent
{ hex: '#ffffff', percentage: 0.3 },
],
},
},
limit: 10,
});
colors.palette— products must contain every listed color (AND).hexis sRGB (#rrggbb);percentage(0–1) is an optional minimum share of that color in the product image.attributes—Record<string, string[]>. Keys are non-color attribute handles (e.g.material,frame-color); values are OR within a key, AND across keys. Discover valid handles and values for a category viaclient.categories.retrieve(slug)→Category.attributes. Do not pass thecolorhandle here — usefilters.colorsinstead. When a category filter is also supplied, every attribute key must be valid for at least one of those categories.
Don't guess attribute handles or values — categories.retrieve is the source of truth for non-color attributes. Returned products carry structured_attributes (e.g. { color: ["Navy"], material: ["Leather"] }) for display; filter color via filters.colors, not by echoing structured_attributes.color into filters.attributes.
For raw HTTP / non-SDK callers:
curl -X POST https://api.trychannel3.com/v1/search \
-H "x-api-key: $CHANNEL3_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"running shoes","filters":{"price":{"max_price":100}},"limit":10}'
Similar — POST /v1/similar
"More like this" from a Channel3 product_id you already have. Almost always seeded from a previous /v1/search response. Returns the same SearchResponse shape; the source product is excluded.
const search = await client.products.search({ query: 'red leather jacket', limit: 5 });
const seedId = search.products[0].id;
const similar = await client.products.find_similar({
product_id: seedId,
filters: { gender: 'female', price: { max_price: 200 } },
limit: 10,
});
search = client.products.search(query="red leather jacket", limit=5)
seed_id = search.products[0].id
similar = client.products.find_similar(
product_id=seed_id,
filters={"gender": "female", "price": {"max_price": 200}},
limit=10,
)
Filters are recommended to keep results in the same slice (gender, brand, category, price). For "more like this, but in navy", use filters.colors (e.g. { hex: '#001f3f' }), not filters.attributes.color. Non-color attributes use filters.attributes. Returns 404 if the product isn't in the catalog yet — fall back to /v1/search by title.
Lookup — POST /v1/lookup
Resolve a merchant URL to the canonical Channel3 Product. Use this only when the developer's only handle on a product is a URL (e.g. a user-pasted link).
const { product } = await client.products.lookup({
url: 'https://merchant.com/products/red-jacket',
});
result = client.products.lookup(url="https://merchant.com/products/red-jacket")
product = result.product
Latency: typically 2–10 seconds for uncached URLs (real-time extraction), sub-second for cached. Returns 422 for non-product pages (category listings, search results, homepages) and 504 on timeout. max_staleness_hours (default 3) bounds cache freshness. The response carries the same hydrated variants and structured_attributes as GET /v1/products/{id}. Once you have product.id, use it with client.products.retrieve() or client.products.find_similar().
Product Details — GET /v1/products/{product_id}
Full ProductDetail for a known product_id. Same response shape as search results, but with variants fully hydrated for the current selection — including per-option-value availability and thumbnails.
const product = await client.products.retrieve('prod_abc123', {
country: 'GB',
currency: 'GBP',
});
Python is the same shape: client.products.retrieve("prod_abc123", country="GB", currency="GBP"). Optional query params: website_ids, language, country, currency. Returns 404 when the product has no merchant offer in the requested locale — seed product_id from a /v1/search call run under the same locale, or omit the locale to fall back to default.
Variants and option_<name> query params
A ProductDetail with variations carries:
variants.options— every dimension (e.g.Color,Size) and itsvalues(label,exists,available, optionalthumbnail_urlandproduct_idfor color-as-product-swap setups).variants.selected— the dimensions currently resolved on this response ([{ name: "Color", label: "Navy" }, ...]).structured_attributes— extracted attribute values for the resolved variant (e.g.{ color: ["Navy"], material: ["Leather"] }).
To re-fetch the product under a different variant configuration, append option_<DimensionName>=<Label> query params. Multiple dimensions can be combined; case is preserved on the name and matched case-insensitively against the product family.
curl -X GET \
"https://api.trychannel3.com/v1/products/prod_abc123?option_Color=Blue&option_Size=XL" \
-H "x-api-key: $CHANNEL3_API_KEY"
From the SDKs, pass option_* through the client's extra-query mechanism — see docs.trychannel3.com/sdk for the exact parameter name in each language. The server returns the same ProductDetail shape with variants.selected updated to reflect the resolved configuration. Diff your requested options against variants.selected to detect server-side relaxation (e.g. the requested size was unavailable in the requested color).
For the full variant model — search-vs-detail differences, product_id navigation (color-as-separate-product), and the exists/available UI tiers — see references/variants.md. In React, the Channel3 UI useVariantSelection hook implements this selection-and-re-resolution loop for you.
Price Tracking — /v0/price-tracking/...
client.priceTracking.start({ canonical_product_id })— start trackingclient.priceTracking.stop({ canonical_product_id })— stop trackingclient.priceTracking.retrieveHistory(id, { days })— up to 30 days; returnscurrent_price,min/max/mean/std_dev,current_status(low/typical/high)client.priceTracking.listSubscriptions()— cursor-paginated, supportsfor awaititeration
await client.priceTracking.start({ canonical_product_id: 'prod_abc123' });
const history = await client.priceTracking.retrieveHistory('prod_abc123', { days: 30 });
console.log(history.statistics?.current_price, history.statistics?.current_status);
Brands and Websites — /v1/brands*, /v0/websites
Lookup helpers, mostly used to obtain IDs for search filters.
client.brands.search({ query, limit? })— find brands by name; returns up tolimitmatches ordered by relevance (default 5, max 20)client.brands.retrieve(brandId)— by IDclient.brands.list()— cursor-paginated, supportsfor awaititeration. Iterating to exhaustion walks the entire brand catalog (thousands of brands, many pages of API calls); always break early or useclient.brands.searchwhen you just need one brand.client.websites.retrieve({ query: 'nike.com' })— find a retailer
// Find a brand ID for filtering
const { brands } = await client.brands.search({ query: 'Nike', limit: 5 });
const brandId = brands[0]?.id; // top match — inspect `brands` to disambiguate when multiple match
Categories — /v1/categories*
Discover the category slugs you can pass to SearchFilters.category_ids / exclude_category_ids, and the attribute keys/values you can pass to SearchFilters.attributes. Slugs are stable URL-friendly identifiers (e.g. shoes, sofas, handbags) — prefer them over internal IDs. The taxonomy doesn't have a leaf for every conceivable subcategory, and unknown slugs are silently dropped, so always discover real slugs with client.categories.search rather than guessing.
client.categories.search({ query, limit? })— free-text →CategorySummary[](limit1–20, default 5)client.categories.list({ roots_only?, page?, page_size? })— paginated browse, roots first (page_size1–100, default 20)client.categories.retrieve(slug)— fullCategorywith description, attributes, direct children, and root-to-selfpath
const { categories } = await client.categories.search({ query: 'running shoes', limit: 5 });
const slug = categories[0].slug;
const { products } = await client.products.search({
query: 'lightweight trainers',
filters: { category_ids: [slug] },
});
result = client.categories.search(query="running shoes", limit=5)
slug = result.categories[0].slug
response = client.products.search(
query="lightweight trainers",
filters={"category_ids": [slug]},
)
exclude_category_ids excludes the category and all its descendants.
Category.attributes lists indexed attribute handles for SearchFilters.attributes. Use non-color slugs (e.g. material, frame-color). The color entry is informational on products — filter color with filters.colors, not attributes.color.
const category = await client.categories.retrieve('sofas');
const materialAttr = category.attributes.find(a => a.slug === 'material');
// materialAttr?.values → ["Leather", "Velvet", "Linen", ...]
const { products } = await client.products.search({
query: 'leather sectional',
filters: {
category_ids: ['sofas'],
attributes: { material: ['Leather'] },
colors: { palette: [{ hex: '#001f3f' }] }, // navy via colors filter
},
});
Affiliate links
Every ProductOffer.url in a response is an affiliate-tracked link. Surface them as the buy buttons in any UI — sales driven through these URLs earn commission with no additional setup. Use offer.domain to identify the retailer and offer.max_commission_rate to compare earning potential across merchants.
Caching and freshness
- Cache IDs, not data.
product.idand category slugs are stable — cache them freely. Treat everything else on a product (prices, availability, offers, images, descriptions, variants) as unstable; it changes as merchants update catalogs. - Refresh at display time. Before showing a product to a user, refetch with
GET /v1/products/{id}(client.products.retrieve) — this call is free and returns current prices, stock, hydrated variant availability, and fresh offer URLs. - Offer URLs are short-lived. Never cache
ProductOffer.urland serve it later; fetch it fresh before presenting the buy link. - Short TTLs (minutes to a few hours) on presentational fields (title, images) are fine; avoid caching pricing or availability for hours or days.
Locale codes
- Languages:
en,de,fr,it,es,nl,sv,fi,pt,cs,el,ro - Countries:
US,GB,EU,AU,CA,IE,DE,AT,FR,BE,IT,ES,NL,SE,FI,PT,CZ,GR,RO - Currencies:
USD,CAD,AUD,GBP,EUR,SEK,CZK,RON
When country is set alone, the server infers currency (GB → GBP) and language (GB → en). When all three are unset, defaults are en / US / USD.
When to use the MCP instead
For no-code agent integration, use the Channel3 MCP instead of writing API code when the host already supports it.
When stuck
- SDK guide (install, async, errors, retries, logging): docs.trychannel3.com/sdk
- Full API reference (try-it examples, schemas): docs.trychannel3.com/api-reference
- Offline quick-card:
references/api-reference.md - Support: support@trychannel3.com
Scan to join WeChat group