Skip to main content

Documentation Index

Fetch the complete documentation index at: https://dev.ranked.ai/llms.txt

Use this file to discover all available pages before exploring further.

Overview

A Node.js script that pulls keyword rankings daily, stores them in a local database, and generates a summary of changes.

Full example

const Database = require('better-sqlite3');

const API_KEY = process.env.RANKED_API_KEY;
const PROJECT_ID = process.env.RANKED_PROJECT_ID;
const BASE = `https://app.ranked.ai/api/v1/projects/${PROJECT_ID}`;
const headers = { 'Authorization': `Bearer ${API_KEY}` };

// Initialize SQLite database
const db = new Database('rankings.db');
db.exec(`
  CREATE TABLE IF NOT EXISTS snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    keyword_id TEXT,
    keyword TEXT,
    desktop INTEGER,
    mobile INTEGER,
    ai_mode INTEGER,
    maps INTEGER,
    net_change INTEGER,
    recorded_at TEXT DEFAULT CURRENT_TIMESTAMP
  )
`);

async function fetchAndStore() {
  let offset = 0;
  const limit = 1000;
  let total = 0;

  while (true) {
    const response = await fetch(
      `${BASE}/rankings/keywords?limit=${limit}&offset=${offset}`,
      { headers }
    );
    const { data, meta } = await response.json();

    const insert = db.prepare(`
      INSERT INTO snapshots (keyword_id, keyword, desktop, mobile, ai_mode, maps, net_change)
      VALUES (?, ?, ?, ?, ?, ?, ?)
    `);

    const batch = db.transaction((keywords) => {
      for (const kw of keywords) {
        insert.run(
          kw.id, kw.keyword,
          kw.desktop_position, kw.mobile_position,
          kw.ai_mode_position, kw.maps_position,
          kw.net_change
        );
      }
    });

    batch(data);
    total += data.length;

    if (!meta.pagination.has_more) break;
    offset += limit;
  }

  console.log(`Stored ${total} keyword snapshots`);
}

function generateReport() {
  const today = new Date().toISOString().split('T')[0];

  const improved = db.prepare(`
    SELECT keyword, net_change FROM snapshots
    WHERE date(recorded_at) = date(?) AND net_change > 0
    ORDER BY net_change DESC LIMIT 10
  `).all(today);

  const declined = db.prepare(`
    SELECT keyword, net_change FROM snapshots
    WHERE date(recorded_at) = date(?) AND net_change < 0
    ORDER BY net_change ASC LIMIT 10
  `).all(today);

  console.log('\n--- Daily Ranking Report ---\n');

  if (improved.length > 0) {
    console.log('Top Improvements:');
    improved.forEach(kw => console.log(`  ${kw.keyword}: +${kw.net_change}`));
  }

  if (declined.length > 0) {
    console.log('\nBiggest Declines:');
    declined.forEach(kw => console.log(`  ${kw.keyword}: ${kw.net_change}`));
  }

  if (improved.length === 0 && declined.length === 0) {
    console.log('No ranking changes today.');
  }
}

// Run
fetchAndStore().then(generateReport);

Running daily

Use cron (Linux/Mac) or Task Scheduler (Windows):
# Run daily at 7am
0 7 * * * cd /path/to/project && node monitor.js >> /var/log/rankings.log 2>&1
Or use the keywords.updated webhook to trigger automatically when the scan completes instead of running on a fixed schedule.