channel3-api
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 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 |
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
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.
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.
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). 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. 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.
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.
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. 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.
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.
Locale codes
- Languages:
en,de,fr,it,es,nl,sv,fi,pt,cs - Countries:
US,GB,EU,AU,CA,IE,DE,AT,FR,BE,IT,ES,NL,SE,FI,PT,CZ - Currencies:
USD,CAD,AUD,GBP,EUR,SEK,CZK
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
If the goal is to give an existing AI agent product superpowers without writing code, the Channel3 MCP (https://mcp.trychannel3.com/, free tier, append ?apiKey=<key> for pay-as-you-go) is lower friction — see docs.trychannel3.com/mcp-overview.
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