Price Monitoring App
Complexity: Beginner
Estimated time: 30-45 minutes
Key TABS APIs: /v1/extract/json/schema, /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
How It Works: The Price Tracking Pipeline
Configure Products → Discover Schema → Extract Prices → Compare History → Generate Report
- Configuration: You define which products to monitor
- Schema Discovery: We use
/v1/extract/json/schemato understand each product page - Price Extraction: We use
/v1/extract/jsonto extract current pricing data - History Tracking: We compare against previous prices
- 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 TABS 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 node-fetch 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:
TABS_API_KEY=your_api_key_here
TABS_API_URL=https://api.tabstack.ai
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": {
"node-fetch": "^3.3.2",
"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": "laptop-dell-xps",
"name": "Dell XPS 13 Laptop",
"url": "https://www.dell.com/en-us/shop/dell-laptops/xps-13-laptop/spd/xps-13-9340-laptop",
"targetPrice": 999,
"schema": null
},
{
"id": "camera-sony-a7",
"name": "Sony A7 IV Camera",
"url": "https://www.bhphotovideo.com/c/product/1656859-REG/sony_ilce_7m4_a7_iv_mirrorless_camera.html",
"targetPrice": 2299,
"schema": null
},
{
"id": "shoes-nike-pegasus",
"name": "Nike Pegasus 40 Running Shoes",
"url": "https://www.nike.com/t/pegasus-40-mens-road-running-shoes-C8m9XV",
"targetPrice": 100,
"schema": null
}
]
}
Step 2: Price Extractor - The Data Collector
This module handles both schema discovery and price extraction using the TABS API.
Create src/extractor.js:
// src/extractor.js
import fetch from 'node-fetch';
import dotenv from 'dotenv';
dotenv.config();
const API_KEY = process.env.TABS_API_KEY;
const BASE_URL = process.env.TABS_API_URL || 'https://api.tabstack.ai';
export class PriceExtractor {
constructor() {
this.headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
};
}
/**
* Discover the schema for a product page using AI
*/
async discoverSchema(url, productName) {
console.log(`🔍 Discovering schema for: ${productName}`);
const instructions = `Analyze this product page and create a schema to extract:
- Product name/title
- Current price (as a number)
- Original price if on sale (as a number)
- Currency
- Availability status (in stock, out of stock, etc.)
- Any discount percentage`;
const response = await fetch(
`${BASE_URL}/v1/extract/json/schema`,
{
method: 'POST',
headers: this.headers,
body: JSON.stringify({
url: url,
instructions: instructions
})
}
);
if (!response.ok) {
throw new Error(`Schema discovery failed: ${response.statusText}`);
}
const schema = await response.json();
console.log(`✅ Schema discovered for ${productName}`);
return schema;
}
/**
* Extract price data using a predefined schema
*/
async extractPrice(url, schema) {
const response = await fetch(`${BASE_URL}/v1/extract/json`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
url: url,
json_schema: schema
})
});
if (!response.ok) {
throw new Error(`Price extraction failed: ${response.statusText}`);
}
const data = await response.json();
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);
}
/**
* Save updated product configuration
*/
async saveProducts(config) {
await fs.writeFile(
'./config/products.json',
JSON.stringify(config, null, 2),
'utf8'
);
}
/**
* 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 {
// Discover schema if not already defined
if (!product.schema) {
console.log(' ⚙️ No schema found, discovering...');
product.schema = await this.extractor.discoverSchema(
product.url,
product.name
);
await this.saveProducts(config);
} else {
console.log(' ✅ Using existing schema');
}
// Extract current price
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);
results.push({
product,
error: 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 - Schema Discovery
The first time you run the monitor, it will discover the schema for each product page:
npm run monitor
Expected Output:
🚀 Starting Price Monitor
============================================================
📦 Monitoring: Dell XPS 13 Laptop
URL: https://www.dell.com/en-us/shop/dell-laptops/...
⚙️ No schema found, discovering...
🔍 Discovering schema for: Dell XPS 13 Laptop
✅ Schema discovered for Dell XPS 13 Laptop
💰 Extracting price data...
✅ Current price: USD $1,099.99
📊 First time tracking this product
📦 Monitoring: Sony A7 IV Camera
URL: https://www.bhphotovideo.com/c/product/...
⚙️ No schema found, discovering...
🔍 Discovering schema for: Sony A7 IV Camera
✅ Schema discovered for Sony A7 IV Camera
💰 Extracting price data...
✅ Current price: USD $2,498.00
📊 First time tracking this product
============================================================
📋 Generating Report...
# Price Monitor Report
*Generated: 1/15/2024, 3:45:23 PM*
## Summary
- **Products Monitored**: 3
- **Price Drops**: 0
- **Price Increases**: 0
---
## Dell XPS 13 Laptop
**Current Price**: USD $1,099.99
**Availability**: in stock
**Analysis**: First time tracking this product
**Recommendation**: Monitor for a few days to establish baseline
[View Product](https://www.dell.com/en-us/shop/dell-laptops/xps-13-laptop/spd/xps-13-9340-laptop)
---
💾 Report saved to: price-report-1705345523789.md
✅ Monitoring complete!
Subsequent Runs - Price Tracking
On future runs, the monitor uses the saved schemas and compares prices:
npm run monitor
Example Output with Price Changes:
📦 Monitoring: Dell XPS 13 Laptop
URL: https://www.dell.com/en-us/shop/dell-laptops/...
✅ Using existing schema
💰 Extracting price data...
✅ Current price: USD $999.99
📊 Price dropped by $100.00 (9.09%)
🚨 SIGNIFICANT DROP: 9.09% off!
## Dell XPS 13 Laptop
**Current Price**: USD $999.99
~~$1,099.99~~ **Save $100.00**
**Availability**: in stock
**Price Range**:
- Lowest: $999.99
- Highest: $1,099.99
- Tracked Points: 5
**Analysis**: Price dropped by $100.00 (9.09%)
> 🚨 SIGNIFICANT DROP: 9.09% off!
**Recommendation**: 🎉 LOWEST PRICE EVER - Consider buying now!
> 🎯 **TARGET PRICE MET!** This product is at or below your target price of $999
[View Product](https://www.dell.com/en-us/shop/dell-laptops/xps-13-laptop/spd/xps-13-9340-laptop)
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');
Understanding the TABS API Endpoints
This price monitor uses two key TABS endpoints:
/v1/extract/json/schema Endpoint
Automatically analyzes a product page and generates a JSON schema:
// Discovers the structure of the page
POST https://api.tabstack.ai/v1/extract/json/schema
{
"url": "https://example.com/product",
"instructions": "extract pricing"
}
// Returns a schema like:
{
"type": "object",
"properties": {
"current_price": { "type": "number" },
"product_name": { "type": "string" }
}
}
/v1/extract/json Endpoint
Extracts structured data using the schema:
// Extracts data based on schema
POST https://api.tabstack.ai/v1/extract/json
{
"url": "https://example.com/product",
"json_schema": { ... }
}
// Returns extracted data:
{
"current_price": 99.99,
"product_name": "Example Product"
}
Troubleshooting
"Schema discovery failed"
Some sites are harder to analyze:
- Try simplifying the instructions
- Use the basic schema instead of auto-discovery
- Check if the site requires JavaScript rendering
"Price extraction failed"
The page structure may have changed:
- Delete the schema from
products.jsonto re-discover - Manually update the schema if you know the structure
- Check if the product URL is still valid
Incorrect prices extracted
- Review the schema in
products.json - Make instructions more specific
- Add more required fields to the schema
Next Steps
You now have a working price monitor! Here's what to explore next:
- Add more products to your
products.json - Set up scheduling to run checks automatically
- Implement notifications via email or Slack
- Build a dashboard to visualize price trends
- Compare prices across multiple retailers