Build a Marketplace Arbitrage Finder in 50 Lines of JavaScript
I flip stuff on the side. Not full-time — more like "I check prices when I'm bored and occasionally make $50 on a guitar pedal someone underpriced on Reverb."
The tedious part was always manually searching the same item across multiple platforms. So I wrote a script that does it for me. It checks TCGPlayer and Reverb, compares prices, and spits out anything with a meaningful price gap.
Here's the whole thing.
The Idea
Price arbitrage = same item listed at different prices on different platforms. A Boss DS-1 pedal for $35 on Reverb might be $55 on eBay. A Charizard card listed at $12 on one platform might sell for $20 on another.
The trick is finding those gaps before everyone else does.
Setup
You need Node.js and a RapidAPI key (free tier works for testing).
mkdir arbitrage-finder && cd arbitrage-finder
npm init -y
npm install node-fetch
Create a .env file:
RAPIDAPI_KEY=your_key_here
The Script
Here's find-deals.mjs — the whole thing:
import fetch from 'node-fetch';
const RAPIDAPI_KEY = process.env.RAPIDAPI_KEY;
const RAPIDAPI_HOST = 'marketplace-price-tracker.p.rapidapi.com';
const headers = {
'x-rapidapi-key': RAPIDAPI_KEY,
'x-rapidapi-host': RAPIDAPI_HOST,
};
async function searchReverb(query) {
const url = `https://${RAPIDAPI_HOST}/reverb/search?query=${encodeURIComponent(query)}&limit=10`;
const res = await fetch(url, { headers });
const data = await res.json();
return (data.results || []).map(item => ({
title: item.title,
price: parseFloat(item.price?.amount || 0),
platform: 'Reverb',
url: item.url,
}));
}
async function searchTCG(query) {
const url = `https://${RAPIDAPI_HOST}/tcg/search?query=${encodeURIComponent(query)}&limit=10`;
const res = await fetch(url, { headers });
const data = await res.json();
return (data.results || []).map(item => ({
title: item.name || item.title,
price: parseFloat(item.marketPrice || item.price || 0),
platform: 'TCGPlayer',
url: item.url,
}));
}
async function findArbitrage(query, minGapPercent = 20) {
console.log(`\nSearching: "${query}"\n`);
const [reverbResults, tcgResults] = await Promise.all([
searchReverb(query),
searchTCG(query),
]);
const allListings = [...reverbResults, ...tcgResults]
.filter(item => item.price > 0)
.sort((a, b) => a.price - b.price);
if (allListings.length < 2) {
console.log('Not enough listings to compare.');
return;
}
const lowest = allListings[0];
const highest = allListings[allListings.length - 1];
const gap = ((highest.price - lowest.price) / lowest.price) * 100;
console.log(`Lowest: $${lowest.price.toFixed(2)} on ${lowest.platform}`);
console.log(`Highest: $${highest.price.toFixed(2)} on ${highest.platform}`);
console.log(`Gap: ${gap.toFixed(1)}%\n`);
if (gap >= minGapPercent) {
console.log(`🔥 DEAL: Buy on ${lowest.platform} → sell on ${highest.platform}`);
console.log(` Potential profit: $${(highest.price - lowest.price).toFixed(2)}`);
} else {
console.log('No significant arbitrage found.');
}
}
// Run it
const query = process.argv[2] || 'Boss DS-1';
findArbitrage(query);
Running It
RAPIDAPI_KEY=your_key node find-deals.mjs "Charizard Base Set"
Output looks like:
Searching: "Charizard Base Set"
Lowest: $12.50 on TCGPlayer
Highest: $24.99 on Reverb
Gap: 99.9%
🔥 DEAL: Buy on TCGPlayer → sell on Reverb
Potential profit: $12.49
Making It Useful
The basic version above is a starting point. Here's how I actually use it:
1. Batch search from a watchlist:
const watchlist = [
'Boss DS-1',
'MXR Phase 90',
'Ibanez Tube Screamer',
'Pokemon Base Set Booster',
'Charizard VMAX',
];
for (const item of watchlist) {
await findArbitrage(item, 15);
await new Promise(r => setTimeout(r, 1000)); // rate limit
}
2. Run on a cron:
# Check every 6 hours
0 */6 * * * cd /path/to/arbitrage-finder && node find-deals.mjs "Boss DS-1" >> deals.log
3. Add more marketplaces:
The same API also has endpoints for OfferUp, Poshmark, eBay, Mercari, and a bunch of others. Same pattern — just swap the route:
// Same headers, different endpoint
const url = `https://${RAPIDAPI_HOST}/offerup/search?query=${query}`;
What I Learned
- Timing matters more than price gaps. A 30% gap means nothing if the item takes 3 months to sell.
- Condition is everything. "Used - Good" on one platform might be "Fair" on another. Always check photos.
- Shipping eats profits. Factor in $5-15 shipping per flip before getting excited about a $10 gap.
- The real edge is speed. Underpriced items get snatched fast. Running this on a cron and checking results on your phone is the actual workflow.
The API
I built the marketplace search API for my own flipping workflow. It's on RapidAPI with a free tier (50 requests/month) if you want to try it: Marketplace Price Tracker.
Supports 20+ marketplaces. The whole thing runs on Apify actors behind a Railway backend — I wrote about the architecture in a previous post.
If you've got a better arbitrage strategy or want to add a marketplace I'm missing, drop a comment.
Top comments (0)