If you want to add live price comparison to a landing page, shopping assistant, or merchant dashboard, the UI is usually the easy part. The painful part is getting product data from multiple retailers in a shape your frontend can actually use.
BuyWhere API gives you a live comparison route for that job. Instead of stitching together merchant-specific integrations, you can call GET /v2/agents/price-comparison, get back structured listings, and render a comparison widget immediately.
This guide keeps the implementation narrow on purpose:
- one backend proxy route
- one browser widget
- one production-safe architecture
The goal is not to build a perfect shopping app in one pass. The goal is to get to a credible first version quickly, with code you can keep.
Use the Endpoint That Matches the UI
For this use case, build on the live agent-native route:
- Base URL:
https://api.buywhere.ai - Auth header:
Authorization: Bearer <your-key> - Endpoint:
GET /v2/agents/price-comparison
Useful query parameters:
product_namelimitsourcesavailability_onlymin_pricemax_pricesort_by
If your UI is specifically about comparing offers for one product query, this route is a better fit than a generic search endpoint.
Keep the API Key Off the Client
Do not put your BuyWhere API key in frontend JavaScript.
The correct architecture is:
Browser widget -> your backend /api/compare -> BuyWhere /v2/agents/price-comparison
That gives you:
- server-side key storage
- a clean place for caching and rate limiting
- a smaller browser payload
Step 1: Get an API Key
Create a key at https://buywhere.ai/developers/ and export it:
export BUYWHERE_API_KEY="bw_live_your_key_here"
export BUYWHERE_BASE_URL="https://api.buywhere.ai"
Smoke-test the live route directly:
curl --get "$BUYWHERE_BASE_URL/v2/agents/price-comparison" \
-H "Authorization: Bearer $BUYWHERE_API_KEY" \
--data-urlencode "product_name=sony wh-1000xm5" \
--data-urlencode "limit=5" \
--data-urlencode "sort_by=price_asc"
If that works, your next problem is not API access. It is response shaping and UI rendering.
Step 2: Add a Minimal Backend Proxy
Here is a minimal Express handler:
import express from "express";
const app = express();
const API_KEY = process.env.BUYWHERE_API_KEY;
const BASE_URL = process.env.BUYWHERE_BASE_URL || "https://api.buywhere.ai";
app.use(express.static("public"));
app.get("/api/compare", async (req, res) => {
const productName = String(req.query.product_name || "").trim();
if (!productName) {
return res.status(400).json({ error: "product_name is required" });
}
const params = new URLSearchParams({
product_name: productName,
limit: String(req.query.limit || 8),
sort_by: String(req.query.sort_by || "price_asc"),
});
if (req.query.availability_only === "true") {
params.set("availability_only", "true");
}
const rawSources = typeof req.query.sources === "string"
? req.query.sources.split(",")
: [];
for (const source of rawSources) {
const normalized = source.trim();
if (normalized) params.append("sources", normalized);
}
const response = await fetch(`${BASE_URL}/v2/agents/price-comparison?${params.toString()}`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) {
return res.status(response.status).json(data);
}
return res.json({
query: data.query,
total_results: data.total_results,
query_time_ms: data.query_time_ms,
price_stats: data.price_stats,
results: (data.results || []).map((item) => ({
title: item.title,
source: item.source,
price: item.price,
currency: item.currency,
rating: item.rating,
price_rank: item.price_rank,
in_stock: item.in_stock,
affiliate_url: item.affiliate_url || item.url,
})),
});
});
That is enough to give the browser a stable, minimal response contract.
Step 3: Render the Widget in the Browser
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>BuyWhere Price Comparison Widget</title>
</head>
<body>
<h1>Real-Time Price Comparison</h1>
<form id="search-form">
<input id="query" value="sony wh-1000xm5" aria-label="Search query" />
<button type="submit">Compare Prices</button>
</form>
<p id="status">No results yet.</p>
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Source</th>
<th>Rating</th>
<th>Link</th>
</tr>
</thead>
<tbody id="results-body">
<tr><td colspan="5">No results yet.</td></tr>
</tbody>
</table>
<script>
const resultsBody = document.getElementById("results-body");
const statusEl = document.getElementById("status");
async function fetchProducts(query) {
const url = new URL("/api/compare", window.location.origin);
url.searchParams.set("product_name", query);
url.searchParams.set("limit", "8");
url.searchParams.set("sort_by", "price_asc");
const response = await fetch(url);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || `Request failed with ${response.status}`);
}
return data;
}
function renderRows(items) {
if (!items.length) {
resultsBody.innerHTML = '<tr><td colspan="5">No products found.</td></tr>';
return;
}
resultsBody.innerHTML = items.map((item) => `
<tr>
<td>${item.title}</td>
<td>${item.currency} ${item.price}</td>
<td>${item.source}</td>
<td>${item.rating ?? "N/A"}</td>
<td><a href="${item.affiliate_url}" target="_blank" rel="noreferrer">Open</a></td>
</tr>
`).join("");
}
document.getElementById("search-form").addEventListener("submit", async (event) => {
event.preventDefault();
const query = document.getElementById("query").value.trim();
statusEl.textContent = "Loading...";
resultsBody.innerHTML = '<tr><td colspan="5">Loading...</td></tr>';
try {
const data = await fetchProducts(query);
renderRows(data.results || []);
statusEl.textContent = `Loaded ${data.total_results} offers in ${data.query_time_ms} ms.`;
} catch (error) {
statusEl.textContent = error.message;
resultsBody.innerHTML = `<tr><td colspan="5">${error.message}</td></tr>`;
}
});
</script>
</body>
</html>
This is enough to prove the end-to-end UX without overbuilding the first version.
Step 4: Make It Feel Live
For a real-time feel, add polling every 30 to 60 seconds:
setInterval(() => {
const query = document.getElementById("query").value.trim();
if (!query) return;
document.getElementById("search-form").dispatchEvent(new Event("submit"));
}, 30000);
For most commerce surfaces, that is enough. Real-time does not need to mean requerying every second.
Why This Pattern Works
The main advantage here is not the HTML table. It is the separation of responsibilities:
- BuyWhere handles comparison data across sources
- your backend handles authentication and response shaping
- your frontend handles ranking and presentation
That is the right shape for a feature you expect to keep iterating on.
Production Notes
If you ship this:
- keep the key on the server
- cache identical requests for a short window
- add source and price filters
- track outbound affiliate clicks
- do not rank only by cheapest price if stock quality and trust matter
Get Started
If you want to build this in your own app, start with the live comparison route and a small backend proxy, not a browser-side API key.
Get your API key at buywhere.ai/developers/.
Top comments (0)