BuyWhere API Reference
Canonical v1 endpoint reference for the current BuyWhere product catalog API.
- Base URL:
https://api.buywhere.ai - API version covered here:
v1 - Source of truth for endpoint signatures:
docs/api/openapi.yaml - Implementation cross-check for runtime behavior:
app/routers/products.pyandapp/auth.py
Authentication
Authenticated v1 endpoints expect an API key in the Authorization header as a bearer token.
curl --get "https://api.buywhere.ai/v1/products/search" \
-H "Authorization: Bearer $BUYWHERE_API_KEY" \
--data-urlencode "q=wireless headphones"
Notes:
- The OpenAPI spec declares bearer auth globally.
- The API middleware also accepts
X-API-Keyin some deployments for compatibility, butAuthorization: Bearer ...is the canonical contract for v1 docs. - Public health endpoints are documented elsewhere and are not covered in this reference.
Rate Limits
The API enforces per-minute rate limits by API key tier and returns rate-limit headers on responses.
Current implementation defaults:
| Tier | Requests/minute | Daily quota |
|---|---|---|
free | 100 | 1,000 |
basic | 1,000 | 1,000 |
pro | 5,000 | 50,000 |
enterprise | 20,000 | Unlimited |
Response headers:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-ResetX-RateLimit-RegionX-DailyQuota-LimitX-DailyQuota-RemainingX-DailyQuota-ResetRetry-Afteron429 Too Many Requests
Example:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 992
X-RateLimit-Reset: 1777087380
X-DailyQuota-Limit: 1000
X-DailyQuota-Remaining: 874
Common Conventions
Pagination
List endpoints in this v1 surface use offset pagination.
| Parameter | Type | Default | Range |
|---|---|---|---|
limit | integer | 20 | 1 to 100 on search, 1 to 1000 on price history |
offset | integer | 0 | 0 to 10000 |
List responses include:
total: total rows known for the current querylimitoffsetitemsorentrieshas_morefor product search
Currency
Search and product detail support an optional currency parameter for price conversion. The current OpenAPI spec lists:
AUD, CNY, EUR, GBP, HKD, IDR, JPY, MYR, PHP, SGD, THB, USD, VND
Converted values may appear in:
converted_priceconverted_currencyprice_sgdusd_price
Market Filters
Search accepts both country and country_code. country_code is treated as an alias for country.
country: comma-separated ISO country codes such asSG,USregion: comma-separated region codes such assg,us,sea
GET /v1/products/search
Full-text search over the BuyWhere product catalog with optional market, price, and platform filters.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | No | Full-text query, max 500 chars |
category | string | No | Partial category filter |
price_min | number | No | Inclusive minimum price |
price_max | number | No | Inclusive maximum price |
platform | string | No | Source/platform filter such as shopee_sg |
country | string | No | Comma-separated country codes |
country_code | string | No | Alias for country |
region | string | No | Comma-separated region codes |
sort_by | string | No | relevance, price_asc, price_desc, newest |
limit | integer | No | Page size, 1-100, default 20 |
offset | integer | No | Page offset, 0-10000, default 0 |
include_facets | boolean | No | Include category/platform/brand/rating/price facet counts |
currency | string | No | Convert prices into a target currency |
Search Behavior
- If
qis omitted, results default to the most recently updated products. - Category filtering uses case-insensitive partial matching.
platformis an exact source match.- When both
price_minandprice_maxare present,price_min > price_maxreturns a validation error. - Facets are only computed when
include_facets=true.
Response Shape
{
"total": 248,
"limit": 20,
"offset": 0,
"items": [
{
"id": 78234,
"sku": "SHOPEE-SG-HEP-001",
"source": "shopee_sg",
"merchant_id": "shop_abc123",
"name": "Sony WH-1000XM5 Wireless Headphones",
"description": "Wireless noise-cancelling headphones",
"price": "449.00",
"currency": "SGD",
"price_sgd": "449.00",
"usd_price": "332.71",
"converted_price": "332.71",
"converted_currency": "USD",
"buy_url": "https://shopee.sg/product/12345",
"affiliate_url": "https://buywhere.ai/track/abc123",
"image_url": "https://cdn.example.com/sony.jpg",
"barcode": null,
"brand": "Sony",
"category": "Electronics",
"category_path": ["Electronics", "Audio", "Headphones"],
"rating": "4.8",
"review_count": 1247,
"avg_rating": "4.8",
"rating_source": "scraped",
"region": "sg",
"country_code": "SG",
"is_available": true,
"in_stock": true,
"stock_level": "high",
"last_checked": "2026-04-24T18:10:00Z",
"data_updated_at": "2026-04-24T18:00:00Z",
"availability_prediction": "likely_in_stock",
"competitor_count": 6,
"confidence_score": 0.97,
"specs": {
"color": "Black"
},
"metadata": {},
"updated_at": "2026-04-24T18:10:00Z",
"price_trend": "stable"
}
],
"has_more": true,
"next_cursor": null,
"facets": null,
"highlights": {
"78234": "Sony WH-1000XM5 <b>Wireless</b> Headphones"
}
}
Response Fields
Core fields returned in each item:
| Field | Type | Description |
|---|---|---|
id | integer | BuyWhere product ID |
sku | string | Source SKU or normalized SKU |
source | string | Platform/source key |
merchant_id | string | Source merchant identifier |
name | string | Product title |
description | string or null | Product description |
price | string | Current native price as a decimal string |
currency | string | Native ISO currency code |
price_sgd | string or null | Price normalized to SGD |
usd_price | string or null | Price normalized to USD |
converted_price | string or null | Price in the requested currency |
converted_currency | string or null | Currency code for converted_price |
buy_url | string | Direct merchant URL |
affiliate_url | string or null | BuyWhere tracked outbound URL |
image_url | string or null | Product image URL |
barcode | string or null | UPC/EAN when available |
brand | string or null | Brand |
category | string or null | Primary category |
category_path | string[] or null | Hierarchical category path |
rating | string or null | Rating value |
review_count | integer or null | Review volume |
avg_rating | string or null | Aggregated rating |
rating_source | string or null | Rating origin |
region | string | Region code |
country_code | string | ISO country code |
is_available | boolean | Product availability flag |
in_stock | boolean or null | Stock flag when available |
stock_level | string or null | Stock bucket such as low, medium, high |
last_checked | datetime or null | Last availability check |
data_updated_at | datetime or null | Last data refresh |
availability_prediction | string or null | Predicted future availability |
competitor_count | integer or null | Count of competing listings |
confidence_score | number or null | Search/result confidence |
specs | object or null | Arbitrary spec key-value pairs |
metadata | object or null | Extended internal metadata |
updated_at | datetime | Last record update |
price_trend | string or null | up, down, or stable |
Search envelope fields:
| Field | Type | Description |
|---|---|---|
total | integer | Total matches when known |
total_count | integer or null | Alternate total field present in schema |
limit | integer | Applied page size |
offset | integer | Applied offset |
items | array | Search results |
has_more | boolean | Whether another page is likely available |
next_cursor | string or null | Reserved cursor field in the schema |
facets | object or null | Facet buckets when requested |
highlights | object or null | Search highlights keyed by product ID |
Example
curl --get "https://api.buywhere.ai/v1/products/search" \
-H "Authorization: Bearer $BUYWHERE_API_KEY" \
--data-urlencode "q=rice cooker" \
--data-urlencode "country_code=SG" \
--data-urlencode "platform=lazada_sg" \
--data-urlencode "price_min=50" \
--data-urlencode "price_max=300" \
--data-urlencode "include_facets=true" \
--data-urlencode "limit=10"
GET /v1/products/{product_id}
Return the canonical product record for a BuyWhere product ID.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
product_id | integer | Yes | BuyWhere product identifier |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
currency | string | No | Convert returned prices to a target currency |
Response
The endpoint returns a single ProductResponse object with the same field set documented in the search section.
{
"id": 78234,
"sku": "SHOPEE-SG-HEP-001",
"source": "shopee_sg",
"merchant_id": "shop_abc123",
"name": "Sony WH-1000XM5 Wireless Headphones",
"price": "449.00",
"currency": "SGD",
"converted_price": "332.71",
"converted_currency": "USD",
"buy_url": "https://shopee.sg/product/12345",
"affiliate_url": "https://buywhere.ai/track/abc123",
"brand": "Sony",
"category": "Electronics",
"region": "sg",
"country_code": "SG",
"is_available": true,
"updated_at": "2026-04-24T18:10:00Z"
}
Example
curl "https://api.buywhere.ai/v1/products/78234?currency=USD" \
-H "Authorization: Bearer $BUYWHERE_API_KEY"
GET /v1/products/{product_id}/price-history
Return historical price observations for a single product.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
product_id | integer | Yes | BuyWhere product identifier |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
days | integer | No | Lookback window in days, 1-365, default 30 |
platform | string | No | Restrict history to one source such as shopee_sg |
limit | integer | No | Returned observations, 1-1000, default 100 |
offset | integer | No | Offset into the matching history set |
Time Range and Granularity
- The current implementation returns raw observations from the
price_historytable within the requesteddayswindow. - Observations are sorted by
scraped_atdescending. - The schema also exposes
aggregated_entries,aggregate, andperiodfields for aggregated time series, but the current router implementation does not accept an aggregation parameter on this endpoint. Treat rawentriesas the supported contract today.
Response Shape
{
"product_id": 78234,
"entries": [
{
"price": "449.00",
"currency": "SGD",
"platform": "shopee_sg",
"scraped_at": "2026-04-24T18:10:00Z"
},
{
"price": "459.00",
"currency": "SGD",
"platform": "shopee_sg",
"scraped_at": "2026-04-22T09:15:00Z"
}
],
"aggregated_entries": [],
"total": 2,
"aggregate": null,
"period": null
}
Price History Fields
Raw entry fields:
| Field | Type | Description |
|---|---|---|
price | string | Observed price |
currency | string | Price currency |
platform | string | Source platform |
scraped_at | datetime | Observation timestamp |
Schema-defined aggregated entry fields:
| Field | Type | Description |
|---|---|---|
date | string | YYYY-MM-DD date bucket |
min_price | string | Lowest observed price for the bucket |
max_price | string | Highest observed price for the bucket |
avg_price | string | Average price for the bucket |
price_count | integer | Number of observations in the bucket |
currency | string | Currency for the bucket |
platform | string or null | Platform when bucketed by source |
Example
curl --get "https://api.buywhere.ai/v1/products/78234/price-history" \
-H "Authorization: Bearer $BUYWHERE_API_KEY" \
--data-urlencode "days=90" \
--data-urlencode "platform=shopee_sg" \
--data-urlencode "limit=200"
Errors
Common Status Codes
| Status | When it happens |
|---|---|
200 | Request succeeded |
401 | Missing or invalid API key |
404 | Product ID not found |
422 | Validation error on a parameter or request shape |
429 | Rate limit or quota exceeded |
500 | Internal server error |
Validation Error Shape
OpenAPI declares FastAPI validation errors through HTTPValidationError.
Typical shape:
{
"detail": [
{
"loc": ["query", "limit"],
"msg": "Input should be less than or equal to 100",
"type": "less_than_equal"
}
]
}
Runtime Error Shapes
Some endpoints also raise explicit JSON error payloads from router code. Example for an invalid search price range:
{
"detail": {
"code": "INVALID_PRICE_RANGE",
"field": "price_min,price_max",
"message": "price_min cannot be greater than price_max",
"price_min": "500",
"price_max": "100",
"suggested_query": "price_min=100&price_max=100",
"retry_after": null
}
}
Typical not-found response:
{
"detail": "Product not found"
}
Troubleshooting
401 Unauthorized
Check:
Authorizationheader is present- header format is exactly
Bearer <api-key> - the API key is active
422 Validation Error
Common causes:
limitoroffsetoutside allowed bounds- non-numeric
price_minorprice_max qlonger than 500 charactersprice_mingreater thanprice_max- non-integer
product_id
Empty Search Results
Check:
- whether
platform,country, orregionfilters are too restrictive - whether the requested currency is only affecting price conversion rather than filtering
- whether
offsethas paged beyond the available results
Sparse Price History
If price history returns few or zero observations:
- expand
days - remove the
platformfilter - confirm the product exists and is still active
- do not rely on aggregated price history fields unless the endpoint adds an aggregation parameter in a future revision