Skip to main content

Price Monitoring App

Complexity: Beginner

Estimated time: 30-45 minutes

Key Tabstack APIs: /v1/extract/json

Track product prices across multiple retailers and get alerts when prices drop. This example shows you how to build an automated price monitoring system that saves you money by watching products 24/7.

The Story Behind This Project

You're eyeing that new laptop, camera, or pair of sneakers, but the price is just a bit too high. You could check back every day, or you could build a smart price monitor that does it for you. This tool will automatically check prices, track history, and alert you when it's time to buy.

What This Monitor Does

Unlike manual price checking or basic alerts, our price monitor will:

  • Automatically discover product page structure using AI
  • Extract pricing data from any e-commerce site
  • Track price history over time
  • Generate price change reports
  • Identify the best time to buy based on trends
  • (Optional enhancement) Monitor prices across different regions to find the best deals globally

How It Works: The Price Tracking Pipeline

Configure Products → Extract Prices → Compare History → Generate Report
  1. Configuration: You define which products to monitor with their schemas
  2. Price Extraction: We use /v1/extract/json to extract current pricing data
  3. History Tracking: We compare against previous prices
  4. Reporting: We generate a markdown report showing changes

Project Setup

Let's build a simple but effective price monitoring system.

Prerequisites

  • Node.js 18+ installed
  • A Tabstack API key (get one here)
  • Basic command-line knowledge

Step 1: Initialize Your Project

# Create project directory
mkdir price-monitor
cd price-monitor

# Initialize Node.js project
npm init -y

# Install dependencies
npm install @tabstack/sdk dotenv

Step 2: Create Project Structure

mkdir -p src config data
touch .env config/products.json src/monitor.js src/extractor.js src/reporter.js

Your project structure:

price-monitor/
├── config/
│ └── products.json # Products to monitor
├── src/
│ ├── monitor.js # Main monitoring script
│ ├── extractor.js # Price extraction logic
│ └── reporter.js # Report generation
├── data/
│ └── price-history.json # Historical price data
├── .env # API key
└── package.json

Step 3: Configure Environment

Create your .env file:

TABSTACK_API_KEY=your_api_key_here

Update your package.json to enable ES modules and add scripts:

{
"name": "price-monitor",
"version": "1.0.0",
"type": "module",
"scripts": {
"monitor": "node src/monitor.js"
},
"dependencies": {
"@tabstack/sdk": "^2.0.0",
"dotenv": "^16.3.1"
}
}

Important: The "type": "module" line is required to use ES6 imports in Node.js.

Building Your Price Monitor

Step 1: Product Configuration

First, let's define which products we want to monitor. Create config/products.json:

{
"products": [
{
"id": "nordvpn-basic",
"name": "NordVPN Basic Plan",
"url": "https://nordvpn.com/pricing/",
"targetPrice": 5,
"schema": null
},
{
"id": "aliexpress-smartwatch",
"name": "Smartwatch on AliExpress",
"url": "https://www.aliexpress.com/w/wholesale-smartwatch.html",
"targetPrice": 50,
"schema": null
}
]
}
Product URL Selection

The example URLs in this tutorial are for demonstration purposes and may not always work due to:

  • Products being discontinued or out of stock
  • JavaScript-heavy pages that require the /automate endpoint
  • Regional restrictions or anti-bot measures

For best results, test your product URLs first and use consistently-available items. See the Troubleshooting section for guidance on choosing reliable URLs.

Step 2: Price Extractor - The Data Collector

This module handles price extraction using the Tabstack API.

Create src/extractor.js:

// src/extractor.js
import Tabstack from '@tabstack/sdk';
import dotenv from 'dotenv';

dotenv.config();

export class PriceExtractor {
constructor() {
this.client = new Tabstack({
apiKey: process.env.TABSTACK_API_KEY
});
}

/**
* Extract price data using a predefined schema
*/
async extractPrice(url, schema) {
const data = await this.client.extract.json({
url: url,
json_schema: schema || this.getBasicPriceSchema()
});

return {
price: data.current_price || data.price,
originalPrice: data.original_price,
currency: data.currency || 'USD',
availability: data.availability || 'unknown',
discount: data.discount_percentage,
extractedAt: new Date().toISOString()
};
}

/**
* Get a basic schema for common e-commerce sites
*/
getBasicPriceSchema() {
return {
type: "object",
properties: {
product_name: {
type: "string",
description: "The product name or title"
},
current_price: {
type: "number",
description: "Current selling price as a number"
},
original_price: {
type: "number",
description: "Original price if the item is on sale"
},
currency: {
type: "string",
description: "Currency code (USD, EUR, etc.)"
},
availability: {
type: "string",
description: "Stock status (in stock, out of stock, limited, etc.)"
},
discount_percentage: {
type: "number",
description: "Discount percentage if on sale"
}
},
required: ["product_name", "current_price", "currency", "availability"],
additionalProperties: false
};
}
}

Step 3: Reporter - The Alert Generator

This module compares prices and generates reports.

Create src/reporter.js:

// src/reporter.js
import { promises as fs } from 'fs';

export class PriceReporter {
constructor(historyFile = './data/price-history.json') {
this.historyFile = historyFile;
}

/**
* Load price history from file
*/
async loadHistory() {
try {
const data = await fs.readFile(this.historyFile, 'utf8');
return JSON.parse(data);
} catch (error) {
// Return empty history if file doesn't exist
return { products: {} };
}
}

/**
* Save price history to file
*/
async saveHistory(history) {
await fs.mkdir('./data', { recursive: true });
await fs.writeFile(
this.historyFile,
JSON.stringify(history, null, 2),
'utf8'
);
}

/**
* Add a new price point to history
*/
async addPricePoint(productId, priceData) {
const history = await this.loadHistory();

if (!history.products[productId]) {
history.products[productId] = {
prices: [],
lowestPrice: priceData.price,
highestPrice: priceData.price
};
}

const productHistory = history.products[productId];
productHistory.prices.push(priceData);

// Update lowest/highest
if (priceData.price < productHistory.lowestPrice) {
productHistory.lowestPrice = priceData.price;
}
if (priceData.price > productHistory.highestPrice) {
productHistory.highestPrice = priceData.price;
}

await this.saveHistory(history);
return productHistory;
}

/**
* Analyze price changes
*/
analyzePriceChange(productId, currentPrice, productHistory) {
if (!productHistory || productHistory.prices.length === 0) {
return {
status: 'new',
message: 'First time tracking this product',
recommendation: 'Monitor for a few days to establish baseline'
};
}

const previousPrice = productHistory.prices[productHistory.prices.length - 1].price;
const priceDiff = currentPrice - previousPrice;
const percentChange = ((priceDiff / previousPrice) * 100).toFixed(2);

if (priceDiff === 0) {
return {
status: 'unchanged',
message: 'Price unchanged',
recommendation: 'Continue monitoring'
};
} else if (priceDiff < 0) {
const analysis = {
status: 'decreased',
message: `Price dropped by $${Math.abs(priceDiff).toFixed(2)} (${Math.abs(percentChange)}%)`,
recommendation: currentPrice === productHistory.lowestPrice
? '🎉 LOWEST PRICE EVER - Consider buying now!'
: 'Good time to consider purchasing'
};

// Check if it's a significant drop
if (Math.abs(percentChange) >= 10) {
analysis.alert = `🚨 SIGNIFICANT DROP: ${Math.abs(percentChange)}% off!`;
}

return analysis;
} else {
return {
status: 'increased',
message: `Price increased by $${priceDiff.toFixed(2)} (+${percentChange}%)`,
recommendation: 'Wait for price to drop'
};
}
}

/**
* Generate a markdown report
*/
async generateReport(results, targetPrices = {}) {
const timestamp = new Date().toLocaleString();
let markdown = `# Price Monitor Report\n\n*Generated: ${timestamp}*\n\n`;
markdown += `## Summary\n\n`;
markdown += `- **Products Monitored**: ${results.length}\n`;

const drops = results.filter(r => r.analysis?.status === 'decreased').length;
const increases = results.filter(r => r.analysis?.status === 'increased').length;

markdown += `- **Price Drops**: ${drops}\n`;
markdown += `- **Price Increases**: ${increases}\n\n`;

markdown += `---\n\n`;

for (const result of results) {
const { product, priceData, analysis, history } = result;
const targetPrice = targetPrices[product.id];

markdown += `## ${product.name}\n\n`;

// Current Status
markdown += `**Current Price**: ${priceData.currency} $${priceData.price.toFixed(2)}\n\n`;

if (priceData.originalPrice && priceData.originalPrice > priceData.price) {
const savings = priceData.originalPrice - priceData.price;
markdown += `~~$${priceData.originalPrice.toFixed(2)}~~ **Save $${savings.toFixed(2)}**\n\n`;
}

markdown += `**Availability**: ${priceData.availability}\n\n`;

// Price History
if (history) {
markdown += `**Price Range**:\n`;
markdown += `- Lowest: $${history.lowestPrice.toFixed(2)}\n`;
markdown += `- Highest: $${history.highestPrice.toFixed(2)}\n`;
markdown += `- Tracked Points: ${history.prices.length}\n\n`;
}

// Analysis
if (analysis) {
markdown += `**Analysis**: ${analysis.message}\n\n`;

if (analysis.alert) {
markdown += `> ${analysis.alert}\n\n`;
}

markdown += `**Recommendation**: ${analysis.recommendation}\n\n`;
}

// Target Price Check
if (targetPrice && priceData.price <= targetPrice) {
markdown += `> 🎯 **TARGET PRICE MET!** This product is at or below your target price of $${targetPrice}\n\n`;
}

markdown += `[View Product](${product.url})\n\n`;
markdown += `---\n\n`;
}

return markdown;
}

/**
* Save report to file
*/
async saveReport(markdown) {
const filename = `price-report-${Date.now()}.md`;
await fs.writeFile(filename, markdown, 'utf8');
console.log(`\n💾 Report saved to: ${filename}`);
return filename;
}
}

Step 4: Main Monitor - Bringing It Together

Create src/monitor.js:

// src/monitor.js
import { promises as fs } from 'fs';
import { PriceExtractor } from './extractor.js';
import { PriceReporter } from './reporter.js';
import dotenv from 'dotenv';

dotenv.config();

class PriceMonitor {
constructor() {
this.extractor = new PriceExtractor();
this.reporter = new PriceReporter();
}

/**
* Load product configuration
*/
async loadProducts() {
const data = await fs.readFile('./config/products.json', 'utf8');
return JSON.parse(data);
}

/**
* Monitor all configured products
*/
async monitorAll() {
console.log('🚀 Starting Price Monitor\n');
console.log('=' .repeat(60));

const config = await this.loadProducts();
const results = [];

for (const product of config.products) {
console.log(`\n📦 Monitoring: ${product.name}`);
console.log(` URL: ${product.url}`);

try {
console.log(' 💰 Extracting price data...');
const priceData = await this.extractor.extractPrice(
product.url,
product.schema
);

console.log(` ✅ Current price: ${priceData.currency} $${priceData.price.toFixed(2)}`);

// Update price history
const history = await this.reporter.addPricePoint(product.id, priceData);

// Analyze price change
const analysis = this.reporter.analyzePriceChange(
product.id,
priceData.price,
history
);

console.log(` 📊 ${analysis.message}`);

if (analysis.alert) {
console.log(` ${analysis.alert}`);
}

results.push({
product,
priceData,
history,
analysis
});

} catch (error) {
console.error(` ❌ Error monitoring ${product.name}:`, error.message);
}

// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 1000));
}

console.log('\n' + '='.repeat(60));
console.log('📋 Generating Report...\n');

// Generate report
const targetPrices = {};
config.products.forEach(p => {
if (p.targetPrice) {
targetPrices[p.id] = p.targetPrice;
}
});

const report = await this.reporter.generateReport(results, targetPrices);

// Display report to console
console.log(report);

// Save to file
await this.reporter.saveReport(report);

console.log('✅ Monitoring complete!\n');
}
}

// Run the monitor
const monitor = new PriceMonitor();
monitor.monitorAll().catch(console.error);

Running Your Price Monitor

First Run

Run the monitor to start tracking prices:

npm run monitor

Expected Output:

🚀 Starting Price Monitor

============================================================

📦 Monitoring: NordVPN Basic Plan
URL: https://nordvpn.com/pricing/
💰 Extracting price data...
✅ Current price: USD $3.39
📊 First time tracking this product

📦 Monitoring: Smartwatch on AliExpress
URL: https://www.aliexpress.com/w/wholesale-smartwatch.html
💰 Extracting price data...
✅ Current price: USD $5.97
📊 First time tracking this product

============================================================
📋 Generating Report...

# Price Monitor Report

*Generated: 1/22/2026, 3:49:22 PM*

## Summary

- **Products Monitored**: 2
- **Price Drops**: 0
- **Price Increases**: 0

---

## NordVPN Basic Plan

**Current Price**: USD $3.39

**Availability**: in stock

**Price Range**:
- Lowest: $3.39
- Highest: $3.39
- Tracked Points: 1

**Analysis**: First time tracking this product

**Recommendation**: Monitor for a few days to establish baseline

> 🎯 **TARGET PRICE MET!** This product is at or below your target price of $5

[View Product](https://nordvpn.com/pricing/)

---

## Smartwatch on AliExpress

**Current Price**: USD $5.97

**Availability**: in stock

**Price Range**:
- Lowest: $5.97
- Highest: $5.97
- Tracked Points: 1

**Analysis**: First time tracking this product

**Recommendation**: Monitor for a few days to establish baseline

[View Product](https://www.aliexpress.com/w/wholesale-smartwatch.html)

---

💾 Report saved to: price-report-1769125762918.md
✅ Monitoring complete!

Subsequent Runs - Price Tracking

On future runs, the monitor compares prices against history:

npm run monitor

Example Output with Price Changes:

If a price drops, you'll see analysis and alerts:

📦 Monitoring: NordVPN Basic Plan
URL: https://nordvpn.com/pricing/
💰 Extracting price data...
✅ Current price: USD $2.99
📊 Price dropped by $0.40 (11.80%)
🚨 SIGNIFICANT DROP: 11.80% off!

## NordVPN Basic Plan

**Current Price**: USD $2.99

~~$3.39~~ **Save $0.40**

**Availability**: in stock

**Price Range**:
- Lowest: $2.99
- Highest: $3.39
- Tracked Points: 5

**Analysis**: Price dropped by $0.40 (11.80%)

> 🚨 SIGNIFICANT DROP: 11.80% off!

**Recommendation**: 🎉 LOWEST PRICE EVER - Consider buying now!

> 🎯 **TARGET PRICE MET!** This product is at or below your target price of $5

[View Product](https://nordvpn.com/pricing/)

The monitor maintains price history for each product, allowing you to track trends and identify the best time to buy.

Scheduling Regular Checks

To check prices automatically, you can use cron (Linux/Mac) or Task Scheduler (Windows):

Using cron (Mac/Linux):

# Check prices every day at 9 AM
0 9 * * * cd /path/to/price-monitor && npm run monitor

Using node-cron in your code:

npm install node-cron

Create src/scheduler.js:

import cron from 'node-cron';
import { PriceMonitor } from './monitor.js';

const monitor = new PriceMonitor();

// Run every day at 9 AM
cron.schedule('0 9 * * *', () => {
console.log('⏰ Scheduled price check running...');
monitor.monitorAll().catch(console.error);
});

console.log('📅 Price monitor scheduled - will run daily at 9 AM');

Monitoring Regional Prices

Many e-commerce sites show different prices based on the visitor's location. You can track these regional price differences using geotargeting.

Why Monitor Regional Prices?

  • Find the best deals globally: The same product may be cheaper in different countries
  • Track regional sales: Different regions may have different sale schedules
  • Compare shipping costs: Factor in international shipping when comparing prices
  • Identify arbitrage opportunities: Buy in one region, sell in another
Regional Pricing Considerations

The geo_target parameter is most effective with sites that show different prices on a single URL based on visitor location. US-specific retailer sites (amazon.com, bestbuy.com, walmart.com) typically show consistent US pricing regardless of geo_target settings, as they're region-locked domains.

Best candidates for regional price monitoring:

  • VPN services (NordVPN, Surfshark, CyberGhost) - Often show 20-50% price variations
  • International marketplaces (AliExpress, global retailers) - Region-specific pricing and discounts
  • SaaS products (Canva, Notion, Dropbox) - Regional pricing tiers
  • Streaming services (Netflix, Spotify, YouTube Premium) - Currency and pricing adjustments
  • Digital marketplaces (Steam, Epic Games) - Aggressive regional pricing
  • Cloud hosting (DigitalOcean, Linode) - Regional data center pricing

This guide demonstrates regional monitoring with two examples:

  • NordVPN (service) - Shows 45% price variation between Brazil ($1.86 USD) and GB ($3.48 USD)
  • AliExpress (physical products) - Shows 56% price variation between Brazil ($2.63 USD) and US ($5.97 USD)
Finding Products with Regional Pricing

Not all websites work well with the /extract/json endpoint. Some sites use complex JavaScript rendering or require authentication to view pricing. VPN service pricing pages and AliExpress listings work reliably because they're publicly accessible and convert well to structured data. If a product fails to extract, consider using the /automate endpoint for JavaScript-heavy pages.

Note: Some regions may fail extraction due to geo-blocking, different page structures, or access restrictions. The code handles these failures gracefully and will report on regions that succeed.

Adding Regional Price Tracking

Update your products.json to include regions to monitor. Here are examples using services and physical products that implement geolocation-based pricing:

{
"products": [
{
"id": "nordvpn-basic",
"name": "NordVPN Basic Plan",
"url": "https://nordvpn.com/pricing/",
"targetPrice": 5,
"regions": ["US", "GB", "IN", "BR"],
"schema": null
},
{
"id": "aliexpress-smartwatch",
"name": "Smartwatch on AliExpress",
"url": "https://www.aliexpress.com/w/wholesale-smartwatch.html",
"targetPrice": 50,
"regions": ["US", "GB", "BR"],
"schema": null
}
]
}
Schema Configuration

In this example, we use "schema": null to let the API automatically detect the pricing structure. You can also provide a custom JSON schema if you need to extract specific pricing tiers or additional product information. See the JSON Extraction guide for schema customization options.

Update the Price Extractor

First, update the extractPrice method in src/extractor.js to accept an optional options parameter for passing the geo_target:

/**
* Extract price data using a predefined schema
* @param {string} url - Product URL
* @param {object} schema - JSON schema for extraction
* @param {object} options - Optional parameters (e.g., { geoTarget: { country: 'US' } })
*/
async extractPrice(url, schema, options = {}) {
const data = await this.client.extract.json({
url: url,
json_schema: schema || this.getBasicPriceSchema(),
...(options.geoTarget && { geo_target: options.geoTarget })
});

return {
price: data.current_price || data.price,
originalPrice: data.original_price,
currency: data.currency || 'USD',
availability: data.availability || 'unknown',
discount: data.discount_percentage,
region: options.geoTarget?.country || 'default',
extractedAt: new Date().toISOString()
};
}

This allows you to pass { geoTarget: { country: 'BR' } } as the third parameter to fetch prices as seen from a specific country.

Currency Conversion for Accurate Comparison

To properly compare prices across different currencies, we'll use the free Frankfurter API to convert all prices to USD:

/**
* Fetch current exchange rates from Frankfurter API
* Converts all currencies to USD for comparison
*/
async fetchExchangeRates() {
try {
const response = await fetch('https://api.frankfurter.dev/v1/latest?base=USD');
const data = await response.json();
this.exchangeRates = data.rates;
this.exchangeRates.USD = 1.0; // USD to USD is 1:1
console.log('📊 Fetched exchange rates for currency conversion\n');
return this.exchangeRates;
} catch (error) {
console.error('⚠️ Failed to fetch exchange rates:', error.message);
return null;
}
}

/**
* Convert price to USD for comparison
*/
convertToUSD(price, currency) {
if (!this.exchangeRates || currency === 'USD' || currency === '$') {
return price;
}

const rate = this.exchangeRates[currency];
if (!rate) {
console.warn(`⚠️ No exchange rate found for ${currency}`);
return price;
}

// Rate is USD to currency, so divide to get currency to USD
return price / rate;
}

Call await this.fetchExchangeRates() at the start of monitorAll() to load current exchange rates.

Enhanced Monitor for Regional Tracking

Add this method to your PriceMonitor class:

/**
* Monitor prices across multiple regions
*/
async monitorProductRegions(product) {
const regions = product.regions || ['US'];
const regionalPrices = [];

for (const region of regions) {
console.log(` 💰 Checking ${region} pricing...`);

try {
const priceData = await this.extractor.extractPrice(
product.url,
product.schema,
{ geoTarget: { country: region } }
);

// Convert to USD for accurate comparison
const priceUSD = this.convertToUSD(priceData.price, priceData.currency);

regionalPrices.push({
region,
...priceData,
priceUSD
});

console.log(`${region}: ${priceData.currency} ${priceData.price.toFixed(2)} (≈ $${priceUSD.toFixed(2)} USD)`);
} catch (error) {
console.error(`${region}: ${error.message}`);
}
}

// Find the best price across regions (using USD conversion)
if (regionalPrices.length > 0) {
const bestPrice = regionalPrices.reduce((best, current) =>
current.priceUSD < best.priceUSD ? current : best
);

console.log(` 🎯 Best price: ${bestPrice.region} - ${bestPrice.currency} ${bestPrice.price.toFixed(2)} (≈ $${bestPrice.priceUSD.toFixed(2)} USD)`);
}

return {
product: product.name,
regionalPrices,
bestPrice: regionalPrices.length > 0 ? regionalPrices.reduce((best, current) =>
current.priceUSD < best.priceUSD ? current : best
) : null
};
}

Update the Reporter for Regional Pricing

Update the generateReport method in src/reporter.js to handle regional pricing data. Add this logic before the existing single-region code:

for (const result of results) {
const { product, priceData, analysis, history, error, isRegional, regionalData } = result;

// Skip results with errors
if (error) {
markdown += `## ${product.name}\n\n`;
markdown += `**Status**: ❌ Error extracting price data\n\n`;
markdown += `**Error**: ${error}\n\n`;
markdown += `---\n\n`;
continue;
}

markdown += `## ${product.name}\n\n`;

// Regional pricing table
if (isRegional && regionalData) {
markdown += `### Regional Price Comparison\n\n`;
markdown += `| Region | Price | Currency | USD Equivalent | Availability |\n`;
markdown += `|--------|-------|----------|----------------|-------------|\n`;

for (const regional of regionalData.regionalPrices) {
const isBest = regionalData.bestPrice && regional.region === regionalData.bestPrice.region;
const marker = isBest ? ' ⭐ BEST' : '';
const usdEquiv = regional.priceUSD ? `$${regional.priceUSD.toFixed(2)}` : '-';
markdown += `| ${regional.region}${marker} | ${regional.price.toFixed(2)} | ${regional.currency} | ${usdEquiv} | ${regional.availability} |\n`;
}

markdown += `\n`;

if (regionalData.bestPrice) {
const bestPriceUSD = regionalData.bestPrice.priceUSD ? ` (≈ $${regionalData.bestPrice.priceUSD.toFixed(2)} USD)` : '';
markdown += `**Best Price**: ${regionalData.bestPrice.region} - ${regionalData.bestPrice.currency} ${regionalData.bestPrice.price.toFixed(2)}${bestPriceUSD}\n\n`;
}

const targetPrice = targetPrices[product.id];
if (targetPrice && regionalData.bestPrice) {
const comparePrice = regionalData.bestPrice.priceUSD || regionalData.bestPrice.price;
if (comparePrice <= targetPrice) {
markdown += `> 🎯 **TARGET PRICE MET!** Best regional price is at or below your target price of $${targetPrice}\n\n`;
}
}
} else {
// Existing single-region code remains here...
}

markdown += `[View Product](${product.url})\n\n`;
markdown += `---\n\n`;
}

Then update the monitorAll method to call monitorProductRegions when products have multiple regions configured.

Example Output with Regional Tracking

Here's real output from monitoring prices across multiple regions with currency conversion:

📦 Monitoring: NordVPN Basic Plan
URL: https://nordvpn.com/pricing/
💰 Checking US pricing...
✅ US: USD 3.39 (≈ $3.39 USD)
💰 Checking GB pricing...
✅ GB: GBP 2.59 (≈ $3.48 USD)
💰 Checking IN pricing...
✅ IN: USD 3.39 (≈ $3.39 USD)
💰 Checking BR pricing...
✅ BR: BRL 9.90 (≈ $1.86 USD)
🎯 Best price: BR - BRL 9.90 (≈ $1.86 USD)

📦 Monitoring: Smartwatch on AliExpress
URL: https://www.aliexpress.com/w/wholesale-smartwatch.html
💰 Checking US pricing...
✅ US: USD 5.97 (≈ $5.97 USD)
💰 Checking GB pricing...
✅ GB: USD 3.33 (≈ $3.33 USD)
💰 Checking BR pricing...
✅ BR: BRL 13.99 (≈ $2.63 USD)
🎯 Best price: BR - BRL 13.99 (≈ $2.63 USD)

## NordVPN Basic Plan

### Regional Price Comparison

| Region | Price | Currency | USD Equivalent | Availability |
|--------|-------|----------|----------------|-------------|
| US | 3.39 | USD | $3.39 | in stock |
| GB | 2.59 | GBP | $3.48 | VAT may apply |
| IN | 3.39 | USD | $3.39 | VAT may apply |
| BR ⭐ BEST | 9.90 | BRL | $1.86 | VAT may apply |

**Best Price**: BR - BRL 9.90 (≈ $1.86 USD)

## Smartwatch on AliExpress

### Regional Price Comparison

| Region | Price | Currency | USD Equivalent | Availability |
|--------|-------|----------|----------------|-------------|
| US | 5.97 | USD | $5.97 | In stock |
| GB | 3.33 | USD | $3.33 | in stock |
| BR ⭐ BEST | 13.99 | BRL | $2.63 | In stock |

**Best Price**: BR - BRL 13.99 (≈ $2.63 USD)

Key Insights from Regional Pricing:

NordVPN (Service):

  • US pricing: $3.39 USD per month
  • UK pricing: £2.59 GBP = $3.48 USD (3% more expensive!)
  • India pricing: $3.39 USD (same as US)
  • Brazil pricing: 9.90 BRL = $1.86 USD (45% cheaper - best deal!)

AliExpress Smartwatch (Physical Product):

  • US pricing: $5.97 USD
  • UK pricing: $3.33 USD (44% cheaper!)
  • Brazil pricing: 13.99 BRL = $2.63 USD (56% cheaper - best deal!)

Takeaways:

  • Currency conversion is essential - £2.59 looks cheaper than $3.39, but converts to $3.48!
  • Regional pricing differences can be dramatic - savings of 40-56% are possible
  • Brazil consistently offers the lowest prices across both services and products
  • The Frankfurter API provides free, real-time exchange rates for accurate comparisons
  • Both VPN services and international marketplaces like AliExpress are excellent candidates for regional price monitoring

Understanding the Tabstack API Endpoints

This price monitor uses the /v1/extract/json endpoint:

/v1/extract/json Endpoint

Extracts structured data using the schema:

import Tabstack from '@tabstack/sdk';

const client = new Tabstack();

const data = await client.extract.json({
url: 'https://example.com/product',
json_schema: { ... },
geo_target: { country: 'US' } // Optional: for region-specific pricing
});

// Returns extracted data:
// {
// "current_price": 99.99,
// "product_name": "Example Product"
// }

Troubleshooting

Prices returning as 0 or empty

The most common issues:

  1. Product unavailable: The item may be out of stock or discontinued

    • Check the availability field in the debug output
    • Try a different product URL
  2. JavaScript-heavy pages: Some sites load prices dynamically

    • The /extract/json endpoint works with static HTML
    • Consider using /automate endpoint for JavaScript-rendered content
  3. Page structure changed: E-commerce sites update frequently

    • Add debug logging to see what the API returns:
    console.log('Debug - API Response:', JSON.stringify(data, null, 2));
    • Update your schema based on the actual response
  4. Anti-bot protection: Some sites block automated access

    • Try different products or retailers
    • Use the geo_target parameter if region-specific

Better product URL examples

Instead of the sample URLs (which may be unavailable), try these more stable options:

{
"products": [
{
"id": "book-example",
"name": "Example Book on Amazon",
"url": "https://www.amazon.com/dp/[PRODUCT-ID]",
"targetPrice": 20,
"schema": null
}
]
}

Tips for finding good URLs:

  • Use products that are consistently in stock
  • Avoid limited-edition or seasonal items
  • Test the extraction first before adding to regular monitoring
  • Some retailers work better than others (Amazon, Best Buy, and B&H Photo often work well)

Debugging extraction issues

Add this to see what the API returns:

async extractPrice(url, schema, options = {}) {
const data = await this.client.extract.json({
url: url,
json_schema: schema || this.getBasicPriceSchema(),
...(options.geoTarget && { geo_target: options.geoTarget })
});

// Debug: log the raw API response
console.log('🔍 Debug - API Response:', JSON.stringify(data, null, 2));

// Handle case where price is 0 or missing
if (!data.current_price || data.current_price === 0) {
throw new Error(`No valid price found. Availability: ${data.availability || 'unknown'}`);
}

return {
price: data.current_price || data.price,
// ...
};
}

"Price extraction failed"

If you get consistent failures:

  • Verify the product URL loads in a browser
  • Check if the site requires login or has regional restrictions
  • Try the /automate endpoint for complex pages
  • Use geo_target if the site varies by region

Next Steps

You now have a working price monitor! Here's what to explore next:

  1. Add more products to your products.json
  2. Set up scheduling to run checks automatically
  3. Track regional pricing to find the best deals globally (see Monitoring Regional Prices)
  4. Implement notifications via email or Slack
  5. Build a dashboard to visualize price trends
  6. Compare prices across multiple retailers