TinyFish parallel agents are cloud-based browser sessions that query multiple game storefronts simultaneously and return structured pricing data — game title, current price, discount percentage, and direct purchase URL — normalized across currencies and formats into a single ranked list.
Game prices vary wildly across storefronts and change constantly during sales. IsThereAnyDeal and CheapShark aggregate some of this, but neither covers every platform — itch.io is out, regional stores are out, newer storefronts take months to appear. When a game isn't in the database, you're back to checking each store manually.
This tutorial builds a multi-platform game price comparison tool using TinyFish agents. Your configured storefronts are checked simultaneously — major PC storefronts, indie platforms, regional stores, or any public game store with a search page. You get a ranked price list in the time it takes to manually open two tabs.
Build a video game price comparison tool in 4 steps:
- Define your platform list with store search URLs
- Write a goal prompt that extracts price, discount, and link consistently
- Run all 10 agents in parallel with
Promise.allSettled - Normalize currencies and rank by discounted price
Why Building Beats Using CheapShark or IsThereAnyDeal
CheapShark and IsThereAnyDeal are good starting points. They cover major PC storefronts reliably — and their APIs are free. For straightforward PC game price lookup, they're often sufficient.
Browser agents make sense when:
- Your target platform isn't indexed — regional stores, newer indie platforms, and console storefronts have partial or no CheapShark coverage
- You need real-time data — API-backed aggregators cache prices, sometimes aggressively. During major sale events, cached prices can lag the actual storefront by hours
- You're building beyond price lookup — availability status, regional pricing differences, bundle detection, and DLC pricing require reading the actual store page rather than an API summary
The engineering tradeoff is straightforward:
| Approach | Best for | Limitation |
|---|---|---|
| CheapShark / IsThereAnyDeal API | Quick PC storefront lookup, free, no setup | Doesn't cover all storefronts; cached data |
| Browser agents (this tutorial) | All 10 platforms, real-time, fully customizable | Credit cost per query; rate-limited at Free tier |
Choosing the right TinyFish API for this project: TinyFish offers two tools with different access patterns. The Fetch API (api.fetch.tinyfish.ai) retrieves a page at a known URL — free (0 credits). The Agent API (agent.tinyfish.ai) handles storefronts requiring a search step — 1 credit per step. For storefronts with stable product page URLs, Fetch API can retrieve the price directly (free, 0 credits). For storefronts that require a search query, Agent API is required (1 credit per step). Where you have direct product URLs, Fetch API is the right choice — free on all plans.
Video Game Price Comparison: Running Parallel Agents
Prerequisites: Node.js 18+, TypeScript, and a TinyFish API key.
npm install @tiny-fish/sdk
npm install -D ts-node typescript
export TINYFISH_API_KEY=your_key_here
import { TinyFish } from "@tiny-fish/sdk";
const client = new TinyFish(); // reads TINYFISH_API_KEY from env
// Replace these with the storefronts you want to compare.
// Before adding any storefront, check its Terms of Service regarding automated access.
// Many storefronts have public APIs (e.g. CheapShark, IsThereAnyDeal) that are
// the preferred integration method — use browser agents only for stores without APIs.
const PLATFORMS = [
{ name: "Storefront A", url: "https://example-store-a.com/search?q=" },
{ name: "Storefront B", url: "https://example-store-b.com/games/search?query=" },
{ name: "Storefront C", url: "https://example-store-c.com/search?term=" },
// Add more storefronts here — any public search URL works
];
const buildGoal = (gameTitle: string, platformName: string): string => `
Search for the game "${gameTitle}" on this storefront.
Return a JSON object with:
- gameTitle: string (exact title as shown on this store)
- price: string (current price exactly as displayed, including currency symbol)
- originalPrice: string or null (if there is an active discount, the original/crossed-out price)
- discountPercent: number or null (e.g. 40 for 40% off, null if not discounted)
- currency: string (3-letter code: "USD", "EUR", "GBP", etc.)
- platform: "${platformName}" (e.g. "Storefront A" or "Storefront B")
- url: string (direct link to this game's store page)
- available: boolean (true if the game is purchasable; false if unavailable, region-locked, or subscription-only)
If the game is not found on this storefront, return null.
Return only the most relevant match for "${gameTitle}".
`;
async function compareGamePrices(gameTitle: string) {
const query = encodeURIComponent(gameTitle);
const requests = PLATFORMS.map((platform) =>
client.agent
.run({ url: platform.url + query, goal: buildGoal(gameTitle, platform.name) })
.then((response) => {
// response.result is the parsed JavaScript object returned by the agent.
// null = game not found. Check for goal failure vs legitimate null:
const result = response.result as unknown;
if (result && typeof result === "object" && !Array.isArray(result) &&
(result as Record<string, unknown>).status === "failure") {
return { platform: platform.name, result: null };
}
return { platform: platform.name, result: result ?? null };
})
);
const settled = await Promise.allSettled(requests);
return settled.map((r, i) => ({
platform: PLATFORMS[i].name,
// error: true = network/timeout failure, distinct from null (game not found)
...(r.status === "fulfilled" ? r.value : { result: null, error: true }),
}));
}
Why Promise.allSettled not Promise.all: A single storefront timing out or returning an error shouldn't cancel the nine successful results. allSettled ensures every agent completes and reports independently.
Concurrency note: The Free plan supports 2 concurrent agent runs — 10 platforms run in approximately 5 batches. The Starter plan (10 concurrent) runs all 10 simultaneously. Each agent step consumes 1 credit. Querying all 10 platforms for one game title typically costs 30–60 credits depending on site complexity.
Normalizing Prices Across 10 Currencies and Formats
Ten storefronts return ten different price formats: "$29.99", "€24,99", "£19.99", "AU$39.95", "Free", "N/A (subscription only)". Without normalization, sorting by price is impossible.
// normalize.ts
const CURRENCY_TO_USD: Record<string, number> = {
USD: 1.0,
EUR: 1.09, // update these rates from an exchange rate API in production
GBP: 1.27,
AUD: 0.65,
CAD: 0.73,
};
function parsePriceToUsd(raw: string, currency: string): number | null {
if (!raw || raw.toLowerCase().includes("free")) return 0;
if (raw.toLowerCase().includes("n/a") || raw.toLowerCase().includes("subscription")) return null;
// Strip currency symbols and labels, normalize decimal separator
// Remove all non-numeric chars except dot; handle comma as thousands separator
const cleaned = raw.replace(/[^0-9.,]/g, "").replace(/,/g, "");
const amount = parseFloat(cleaned);
if (isNaN(amount)) return null;
const rate = CURRENCY_TO_USD[currency] ?? 1.0;
return Math.round(amount * rate * 100) / 100; // round to cents
}
function calcSavings(
currentUsd: number | null,
originalUsd: number | null
): { savingsUsd: number; discountPct: number } | null {
if (currentUsd === null || originalUsd === null || originalUsd <= 0) return null;
const savingsUsd = originalUsd - currentUsd;
const discountPct = Math.round((savingsUsd / originalUsd) * 100);
return savingsUsd > 0 ? { savingsUsd, discountPct } : null;
}
export function normalizeGameResult(raw: {
price?: string;
originalPrice?: string;
currency?: string;
discountPercent?: number | null;
available?: boolean;
[key: string]: unknown;
}) {
const currency = raw.currency ?? "USD";
const priceUsd = parsePriceToUsd(raw.price ?? "", currency);
const originalUsd = raw.originalPrice
? parsePriceToUsd(raw.originalPrice, currency)
: null;
const savings = calcSavings(priceUsd, originalUsd);
return {
...raw,
priceUsd,
originalUsd,
effectiveDiscountPct: raw.discountPercent ?? savings?.discountPct ?? 0,
savingsUsd: savings?.savingsUsd ?? 0,
};
}
Three normalization decisions worth explaining:
- Currency conversion at query time. Build the lookup table once, apply it at normalization — don't call an exchange rate API per result. In production, fetch rates daily and cache them.
-
"Free"returns 0. Sorting by price puts free games at the top, which is almost always the right behavior for a deals tool. -
nullfor subscription/unavailable. A subscription-only game shouldn't appear as$0— it's not a direct purchase price.nullprice pushes it to the bottom of sorted output.
Putting it together:
import { normalizeGameResult } from "./normalize";
const rawResults = await compareGamePrices("Elden Ring");
const normalized = rawResults
.filter((r) => r.result !== null)
.map((r) => ({ ...r, result: normalizeGameResult(r.result as Record<string, unknown>) }))
.sort((a, b) => {
const aPrice = a.result.priceUsd ?? Infinity;
const bPrice = b.result.priceUsd ?? Infinity;
return aPrice - bPrice;
});
normalized.forEach((r) => {
const disc = r.result.effectiveDiscountPct > 0 ? ` (${r.result.effectiveDiscountPct}% off)` : "";
console.log(`${r.platform}: $${r.result.priceUsd?.toFixed(2)}${disc} → ${r.result.url}`);
});
Run with npx ts-node index.ts.
Building a Deal Alert Bot
The comparison tool becomes a deal monitor with a scheduled run and a threshold check:
import { WebhookClient } from "discord.js";
const DEAL_THRESHOLD_USD = 15; // alert when any platform drops below this
const webhook = new WebhookClient({ url: process.env.DISCORD_WEBHOOK_URL! });
async function checkForDeals(gameTitle: string) {
const results = await compareGamePrices(gameTitle);
const normalized = results
.filter((r) => r.result !== null)
.map((r) => ({ ...r, result: normalizeGameResult(r.result as Record<string, unknown>) }));
const deals = normalized.filter(
(r) => (r.result.priceUsd ?? Infinity) < DEAL_THRESHOLD_USD
);
if (deals.length > 0) {
const message = deals
.map((d) => `**${d.platform}**: $${d.result.priceUsd?.toFixed(2)} ${d.result.effectiveDiscountPct > 0 ? `(-${d.result.effectiveDiscountPct}%)` : ""} — ${d.result.url}`)
.join("\n");
await webhook.send({ content: `🎮 Deal alert for **${gameTitle}**:\n${message}` });
}
}
// Run daily via cron or GitHub Actions
await checkForDeals("Elden Ring");
await checkForDeals("Baldur's Gate 3");
Extension: Sale season monitor. Run the comparison daily during major sale periods (typically mid-June and late November for PC storefronts). When any game in your watchlist drops more than 50%, trigger the webhook immediately. Track prices over time to see which stores run simultaneous sales and which don't.
Extension: multi-game watchlist. Pass an array of game titles to compareGamePrices in parallel via Promise.all — 5 games × 10 platforms = 50 concurrent agent runs on a Pro plan.
For the same 10-platform parallel pattern applied to retail products, see how parallel agents work for medicine price comparison across pharmacy chains.
Game stores update prices hourly during sales. Checking 10 platforms manually for a single title takes long enough to miss a flash deal. Parallel agents collapse that to a single function call — all 10 checked simultaneously, prices normalized across currencies, ranked by actual cost. The same architecture that powers commercial deal aggregators, without the platform partnership requirements.
FAQ
Can this build a video game price comparison tool that checks all major storefronts?
Yes — browser agents navigate public storefront pages and require no per-platform API registration. Add any storefront with a public search page by appending an entry to the PLATFORMS array — including regional stores, indie platforms, and storefronts not covered by existing aggregator APIs.
When should I use browser agents instead of CheapShark API? Use this decision framework:
| If you need | → Use |
|---|---|
| Storefronts not covered by aggregator APIs | → Browser agents (configure your own platform list) |
| Real-time pricing during an active sale | → Browser agents (aggregators cache) |
| PC storefronts only, fast prototype | → CheapShark API (free, no credits needed) |
| Availability, bundle status, or regional pricing | → Browser agents (read the actual store page) |
In practice: start with CheapShark for the platforms it covers, add browser agents for the gaps.
Why do existing APIs like CheapShark miss some platforms?
CheapShark and IsThereAnyDeal aggregate prices from storefronts that maintain API partnerships or accept data feeds. Platforms without those partnerships — itch.io, console storefronts, newer indie platforms — don't appear. Browser agents have no such dependency: they read the public storefront pages that any customer visits, regardless of whether the platform has an API program.
How does the price normalization handle "Free" or subscription-only titles?
The parsePriceToUsd function returns 0 for "Free" (putting free games first in sorted output) and null for titles that are only available through a subscription service (not a direct purchase price). null prices sort to the bottom — a subscription title isn't a direct purchase price and shouldn't displace paid options.
What happens when a storefront doesn't carry the game?
null is the correct result — the game isn't listed on that platform. This is distinct from error: true (network failure or timeout). The comparison output shows null-result platforms as "not found" in the display layer, so users can see at a glance which stores carry the title.
How many platforms can run simultaneously?
The Free plan (PAYG, 500 credits to start) supports 2 concurrent agent runs — 10 platforms run in 5 batches. The Starter plan ($15/mo, 10 concurrent) runs all 10 at once. Each agent step consumes 1 credit; a typical 10-platform query costs 30–60 credits total depending on site complexity and JavaScript rendering requirements.
Can I extend this to track prices over time?
Yes. Save each run's output to a database keyed by game title, platform, and timestamp. Plotting priceUsd over time reveals sale patterns — which storefronts discount simultaneously, how long post-launch discounts take to appear across different store types, and which platforms hold full price longest. Add a cron job to run daily checks for your watchlist.
Deploy This with a Free Account
The complete workflow above runs on TinyFish's free tier. 500 free steps, no credit card — enough to deploy this project and validate it against real data before choosing a plan.
Related Reading:
- Build a Medicine Price Comparison Tool with Parallel Agents
- Build a Vehicle Rental Price Comparison Tool with Parallel AI Agents
Want to scrape the web without getting blocked? Try TinyFish — a browser API built for AI agents and developers.

Top comments (0)