Search API
Reference for the Search API. One request takes a product query and returns a ranked list of US products, each with offers from the retailers that sell it. It does not hold a conversation; use the shopping_agent tool for comparison, product Q&A, and follow-ups.
Endpoint
| Item | Value |
|---|---|
| REST | POST https://commerceai.ai/api/v1/catalog/search with a JSON body |
| Auth | Authorization: Bearer <YOUR_API_KEY> on every request. The same API keys work for Search and shopping_agent. |
| MCP | Not available. Search is REST only; shopping_agent is the only MCP tool. |
| Price | 5 credits per request. See Price. |
Quickstart
Set your API key, then send one query. Each example costs 5 credits.
export COMMERCEAI_API_KEY="<YOUR_API_KEY>"cURL
curl https://commerceai.ai/api/v1/catalog/search \
-H "Authorization: Bearer $COMMERCEAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "wireless earbuds", "filters": {"price_max": 50}, "limit": 5}'Python
Requires Python 3.9+ and requests.
pip install requestsimport os
import requests
response = requests.post(
"https://commerceai.ai/api/v1/catalog/search",
headers={"Authorization": f"Bearer {os.environ['COMMERCEAI_API_KEY']}"},
json={"query": "wireless earbuds", "filters": {"price_max": 50}, "limit": 5},
timeout=30,
)
data = response.json()
if not response.ok:
raise SystemExit(f"{data.get('error') or data.get('code')}: {data['message']}")
for product in data["products"]:
cheapest = product["offers"][0]
print(product["title"], cheapest["price"], cheapest["source_site"], cheapest.get("buy_url"))
print("credits used:", data["usage"]["credits_used"])JavaScript
Requires Node.js 18+ (built-in fetch). No package to install.
const response = await fetch('https://commerceai.ai/api/v1/catalog/search', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.COMMERCEAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: 'wireless earbuds', filters: { price_max: 50 }, limit: 5 }),
signal: AbortSignal.timeout(30_000),
})
const data = await response.json()
if (!response.ok) throw new Error(`${data.error ?? data.code}: ${data.message}`)
for (const product of data.products) {
const [cheapest] = product.offers
console.log(product.title, cheapest.price, cheapest.source_site, cheapest.buy_url)
}
console.log('credits used:', data.usage.credits_used)Request
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Product keywords or a short description, 1 to 200 characters. Leading and trailing spaces are trimmed. |
filters | object | No | Narrows the results. See Filters. |
limit | integer | No | Number of products to return, 1 to 20. Default 20. There is no next page. |
offer_limit | integer | No | Offers per product, 1 to 20. Default 3. Each retailer contributes at most its cheapest offer. |
market | string | No | Default US. Any other market returns outcome: "UNSUPPORTED" without searching and is not charged. |
request_id | string | No | Your own UUID for this request. The server generates one if omitted. Reusing an id returns IDEMPOTENCY_CONFLICT. |
Unknown parameters are rejected with INVALID_ARGUMENT.
Filters
| Filter | Type | Description |
|---|---|---|
price_min | number | Lowest price in USD. Must be positive and not above price_max. |
price_max | number | Highest price in USD. Must be positive. |
websites | string[] | Only return offers from these retailers: amazon, walmart, macys. Any other value is rejected. |
brands | string[] | Brands to prefer. At most 20, each at most 64 characters. |
Unknown filters are rejected with INVALID_ARGUMENT.
Response
| Field | Type | Description |
|---|---|---|
api_version | string | Always v1. |
request_id | string | Your request_id, or the one the server generated. Quote it when you contact support. |
outcome | string | SUCCESS: products found. NO_RESULTS: nothing matched; products is empty. UNSUPPORTED: the market is not supported; nothing was searched. |
applied_filters | object | The filters the search used, after trimming and de-duplication. |
products | object[] | Matching products, best match first. See Product. |
usage | object | credits_used: credits this request used. |
trace_id | string | Present on some responses. Quote it with request_id when you contact support. |
Product
| Field | Type | Description |
|---|---|---|
title | string | Product name. |
brand | string | Brand, when known. |
category | string | Category, when known. |
image_url | string | Main image, when available. |
price_min, price_max | number | Lowest and highest offer price, in currency. |
currency | string | USD. |
availability | string | in_stock, out_of_stock, or unknown. |
rating, review_count | number | Rating and number of reviews, when available. |
offer_count | integer | Number of offers returned for this product. |
offers | object[] | Offers sorted by price, lowest first. See Offer. |
Fields with no value are omitted rather than sent as null. Products and offers can carry more fields than listed here, such as description, gtin, image_urls, specs, and reviews; ignore fields you do not use.
Offer
| Field | Type | Description |
|---|---|---|
source_site | string | Retailer, lowercase, for example walmart. |
merchant_name | string | Seller name, when it differs from the retailer. |
price | number | Price in currency. |
currency | string | USD. |
availability | string | in_stock, out_of_stock, or unknown. |
buy_url | string | Link to the product page on the retailer's site. |
updated_at | integer | When the price was last checked, in Unix seconds. |
Billing
| Result | Credits |
|---|---|
SUCCESS | 5 |
NO_RESULTS | 5 |
UNSUPPORTED market | 0 |
| Request that ends in an error | 0 |
Search requests are billed separately from shopping_agent. Searches the agent runs inside a turn are billed as its tool calls, not as Search API requests.
Errors
Errors return an HTTP status and this body:
{
"error": "INVALID_ARGUMENT",
"message": "invalid argument",
"request_id": "0192f1c4-7b6e-7c3a-9d2e-5f4a1b2c3d4e",
"retryable": false
}| HTTP | error | Retry |
|---|---|---|
| 400 | INVALID_ARGUMENT | No. Fix the request. |
| 401 | UNAUTHENTICATED | No. The API key is missing, invalid, or deleted. The response also carries a WWW-Authenticate: Bearer header. |
| 403 | INSUFFICIENT_CREDITS, ACCOUNT_DISABLED, FORBIDDEN | No. Top up or contact support. |
| 409 | IDEMPOTENCY_CONFLICT | No. Send a new request_id. |
| 429 | RATE_LIMITED, SHARED_CAPACITY | Yes, after the Retry-After header. |
| 502 | UPSTREAM_ERROR | No. |
| 503 | SERVICE_UNAVAILABLE | Yes, with backoff. |
| 504 | UPSTREAM_TIMEOUT | Yes, with backoff. |
Retry only when retryable is true. Do not parse message; it can change.
Limits
Search has its own rate limits, separate from the shopping_agent limits. Requests over the limit return HTTP 429 RATE_LIMITED with a Retry-After header in seconds. Need higher limits? Email support@commerceai.ai.