Product Listing API — Developer Documentation

BUY-3253 | Agent-native product catalog API — paginated listing with category and price-range filters


Overview

GET /v1/products provides a high-performance, paginated product listing endpoint optimized for AI agents and developer applications. It supports cursor-based pagination, flexible filtering, and consistent response envelopes designed for agent-native commerce use cases.

Key characteristics:

  • Cursor-based pagination — efficient traversal of large datasets (tested against 1M+ product indexes)
  • Filtered browsing — category, price range, currency, and region filters
  • Agent-optimized response{data: [], meta: {total, next_cursor}} envelope for easy parsing
  • p99 < 100ms — performance target for up to 50 results in Singapore region

Endpoint

GET /v1/products

Authentication

All requests require a Bearer token:

curl "https://api.buywhere.ai/v1/products" \
  -H "Authorization: Bearer bw_live_xxxxx"

See Authentication Guide for API key generation.


Query Parameters

ParameterTypeRequiredDescription
category_idstringNoFilter by category ID (exact match)
price_minnumberNoMinimum price filter (in specified currency, default: SGD)
price_maxnumberNoMaximum price filter (in specified currency, default: SGD)
currencystringNoCurrency for price filters and response (SGD, MYR, THB, etc.)
regionstringNoGeographic region filter (SG, MY, TH, PH)
sort_bystringNoSort order: price_asc, price_desc, relevance
limitintegerNoResults per page (1–50, default: 20)
cursorstringNoCursor for pagination (from previous response meta.next_cursor)
offsetintegerNoNumeric offset fallback (0–10000, use cursor when possible)

Response Envelope

{
  "data": [
    {
      "id": 12345,
      "sku": "DY-VC7-XXXX",
      "source": "lazada_sg",
      "merchant_id": "LAZADA_SG_001",
      "name": "Dyson V15 Detect Vacuum Cleaner",
      "description": "Laser detects microscopic dust...",
      "price": "749.00",
      "currency": "SGD",
      "price_sgd": "749.00",
      "buy_url": "https://www.lazada.sg/products/...",
      "affiliate_url": "https://buywhere.ai/track/...",
      "image_url": "https://...jpg",
      "brand": "Dyson",
      "category": "Vacuum Cleaners",
      "category_path": ["Home & Garden", "Cleaning", "Vacuum Cleaners"],
      "rating": "4.8",
      "review_count": 1247,
      "is_available": true,
      "in_stock": true,
      "stock_level": "high",
      "last_checked": "2026-04-18T10:00:00Z",
      "data_updated_at": "2026-04-18T08:30:00Z",
      "updated_at": "2026-04-18T14:30:00Z"
    }
  ],
  "meta": {
    "total": 1423,
    "next_cursor": "eyJpZCI6MTIzNDUsIm9mZnNldCI6MjB9",
    "has_more": true,
    "limit": 20
  }
}

Meta Fields

FieldTypeDescription
totalintegerTotal matching products
next_cursorstringOpaque cursor for next page (null when no more results)
has_morebooleanTrue if additional pages exist
limitintegerResults per page used for this response

Examples

Basic Listing (20 products)

curl "https://api.buywhere.ai/v1/products" \
  -H "Authorization: Bearer bw_live_xxxxx"

Filter by Category and Price Range

curl "https://api.buywhere.ai/v1/products?category_id=electronics&price_min=50&price_max=500" \
  -H "Authorization: Bearer bw_live_xxxxx"

Cursor-Based Pagination

# First page
curl "https://api.buywhere.ai/v1/products?limit=50" \
  -H "Authorization: Bearer bw_live_xxxxx"

# Next page using cursor from previous response
curl "https://api.buywhere.ai/v1/products?limit=50&cursor=eyJpZCI6MTIzNDUsIm9mZnNldCI6MjB9" \
  -H "Authorization: Bearer bw_live_xxxxx"

Sort by Price (Low to High)

curl "https://api.buywhere.ai/v1/products?sort_by=price_asc&limit=10" \
  -H "Authorization: Bearer bw_live_xxxxx"

Filter with Currency Conversion

curl "https://api.buywhere.ai/v1/products?price_min=100&price_max=1000&currency=MYR" \
  -H "Authorization: Bearer bw_live_xxxxx"

Filtering Behavior

Category Filter

category_id performs an exact match against the BuyWhere category identifier. For category hierarchy (e.g., "Electronics > Audio > Headphones"), use the full category path or browse categories via GET /v1/categories.

Price Filters

  • price_min — inclusive lower bound
  • price_max — inclusive upper bound
  • Prices are converted to the specified currency before filtering when provided
  • Default currency is SGD; all prices are also returned in price_sgd for reference

Region Filter

region filters products by the target market:

ValueMarket
SGSingapore
MYMalaysia
THThailand
PHPhilippines

Sorting

ValueBehavior
relevanceDefault — most recently updated
price_ascLowest price first
price_descHighest price first

Performance Characteristics

Performance varies based on load conditions, result size, and catalog size. Targets below represent baseline latency for up to 50 results:

ConditionExpected LatencyNotes
Baseline (single request, 20 results)p50 < 30ms, p99 < 100msLow traffic, warm cache
Baseline (single request, 50 results)p50 < 50ms, p99 < 150msMaximum page size
Moderate load (100 concurrent)p50 < 150ms, p99 < 400msTypical API usage
Heavy load (1000+ concurrent)p50 < 400ms, p99 < 1000msPeak traffic scenarios

Tested index size: 1M+ products (Singapore region)

Performance factors:

  • Result size: Larger pages take proportionally longer
  • Deep pagination: Cursor-based pagination is O(1); offset pagination degrades on deep offsets
  • Category filtering: Adds minimal overhead (< 10ms)
  • Price range filtering: Adds minimal overhead (< 10ms)

Cache TTL: 10 minutes (600 seconds)


Error Responses

StatusCodeDescription
400INVALID_PRICE_RANGEprice_min > price_max
401UNAUTHORIZEDMissing or invalid API key
422VALIDATION_ERRORInvalid parameter value
429RATE_LIMIT_EXCEEDEDSlow down requests
500INTERNAL_ERRORServer-side failure

Error Response Format

{
  "error": {
    "code": "INVALID_PRICE_RANGE",
    "message": "price_min cannot be greater than price_max",
    "details": {
      "price_min": "500.00",
      "price_max": "200.00"
    }
  }
}

SDK Usage

Python SDK

from buywhere import BuyWhere

client = BuyWhere(api_key="bw_live_xxxxx")

# Basic listing
response = client.products.list(limit=20)

# Filtered browsing
response = client.products.list(
    category_id="electronics",
    price_min=50,
    price_max=500,
    sort_by="price_asc"
)

# Cursor pagination
while response.meta.has_more:
    print(f"Page with {len(response.data)} products")
    response = client.products.list(
        limit=20,
        cursor=response.meta.next_cursor
    )

JavaScript/TypeScript SDK

import { BuyWhere } from "@buywhere/sdk";

const client = new BuyWhere({ apiKey: "bw_live_xxxxx" });

// Basic listing
const response = await client.products.list({ limit: 20 });

// Filtered browsing
const filtered = await client.products.list({
  categoryId: "electronics",
  priceMin: 50,
  priceMax: 500,
  sortBy: "price_asc"
});

// Cursor pagination
let cursor = response.meta.nextCursor;
while (cursor) {
  const page = await client.products.list({ cursor, limit: 20 });
  console.log(`Page: ${page.data.length} products`);
  cursor = page.meta.nextCursor;
}

Implementation Notes

  • Cursor format — cursors are Base64-encoded JSON containing id and offset. Do not parse or construct cursors manually.
  • Cursor vs Offset — use cursor-based pagination for large datasets (> 1000 products). Offset pagination is available as a fallback but may be slower on deep offsets.
  • Caching — responses are cached for 10 minutes (600 seconds). Use Cache-Control headers for client-side caching.
  • Rate limits — applies per API key. See Rate Limits for details.

Related Endpoints