Sign In Get Started
Tutorials Analytics dashboard showing keyword ranking data and charts

How to Build a Keyword Rank Tracker with a SERP API

By Serpent API Team · · 12 min read

Rank tracking is one of the most valuable applications of a SERP API. Knowing where your site ranks for target keywords, and how those rankings change over time, is fundamental to any SEO strategy. Commercial rank trackers can cost $50-300 per month. In this tutorial, you will build your own for a fraction of that cost using Node.js and the Serpent API.

What We Are Building

By the end of this tutorial, you will have a working keyword rank tracker that can:

  • Check your website's ranking for a list of keywords
  • Store ranking history in a local JSON database
  • Detect ranking changes (improvements and drops)
  • Send alerts when significant changes occur
  • Run automatically on a schedule

The entire application is around 150 lines of JavaScript. It uses no external dependencies beyond Node.js built-in modules and the Serpent API.

Prerequisites

  • Node.js 18 or later (for native fetch support)
  • A Serpent API key (sign up at apiserpent.com to get 100 free searches)
  • Basic JavaScript knowledge

Project Setup

Create a new directory and initialize the project:

mkdir rank-tracker
cd rank-tracker
npm init -y

Create a configuration file called config.json with your tracking settings:

{
  "apiKey": "YOUR_SERPENT_API_KEY",
  "domain": "yourdomain.com",
  "keywords": [
    "best project management tools",
    "project management software",
    "team collaboration app",
    "kanban board online",
    "agile project tracking"
  ],
  "country": "us",
  "language": "en",
  "checkInterval": "daily"
}

Replace yourdomain.com with the domain you want to track and update the keywords to match your target terms.

Developer writing rank tracking code on dark terminal

Fetching Rankings from the SERP API

The core function queries the Serpent API for each keyword and finds where your domain ranks in the results. Create the main file tracker.js:

import { readFileSync, writeFileSync, existsSync } from 'fs';

const config = JSON.parse(readFileSync('./config.json', 'utf-8'));
const DB_FILE = './rankings.json';

// Fetch ranking for a single keyword
async function getRanking(keyword) {
  const params = new URLSearchParams({
    q: keyword,
    apikey: config.apiKey,
    num: 100,          // Check top 100 results
    gl: config.country,
    hl: config.language
  });

  const response = await fetch(
    `https://apiserpent.com/api/search?${params}`
  );

  if (!response.ok) {
    throw new Error(`API error for "${keyword}": ${response.status}`);
  }

  const data = await response.json();
  const results = data.organic_results || [];

  // Find our domain in the results
  const match = results.find(r =>
    r.link && r.link.includes(config.domain)
  );

  return {
    keyword,
    position: match ? match.position : null,
    url: match ? match.link : null,
    title: match ? match.title : null,
    timestamp: new Date().toISOString()
  };
}

// Check all keywords with rate limiting
async function checkAllKeywords() {
  console.log(`Checking ${config.keywords.length} keywords...`);
  const results = [];

  for (const keyword of config.keywords) {
    try {
      const ranking = await getRanking(keyword);
      results.push(ranking);

      if (ranking.position) {
        console.log(`  "${keyword}" => #${ranking.position}`);
      } else {
        console.log(`  "${keyword}" => Not in top 100`);
      }

      // Small delay between requests to be respectful
      await new Promise(r => setTimeout(r, 1000));
    } catch (error) {
      console.error(`  Error checking "${keyword}":`, error.message);
      results.push({
        keyword,
        position: null,
        url: null,
        title: null,
        timestamp: new Date().toISOString(),
        error: error.message
      });
    }
  }

  return results;
}

This function queries for up to 100 results per keyword, then searches through them to find your domain. If your domain appears, it records the exact position, URL, and page title.

Storing Results Over Time

To track ranking changes, you need to store results historically. We use a simple JSON file as a lightweight database:

// Load existing ranking history
function loadDatabase() {
  if (existsSync(DB_FILE)) {
    return JSON.parse(readFileSync(DB_FILE, 'utf-8'));
  }
  return { checks: [] };
}

// Save ranking data
function saveDatabase(db) {
  writeFileSync(DB_FILE, JSON.stringify(db, null, 2));
}

// Get the most recent check for a keyword
function getLastRanking(db, keyword) {
  for (let i = db.checks.length - 1; i >= 0; i--) {
    const check = db.checks[i];
    const found = check.results.find(r => r.keyword === keyword);
    if (found) return found;
  }
  return null;
}

Each check creates a timestamped entry with results for all keywords. Over time, this builds a complete ranking history that you can query and analyze.

Detecting Ranking Changes

The most valuable feature of a rank tracker is detecting when rankings change. Here is how to compare current results with previous ones:

// Compare current rankings with previous
function detectChanges(db, currentResults) {
  const changes = [];

  for (const current of currentResults) {
    const previous = getLastRanking(db, current.keyword);

    if (!previous) {
      // First time tracking this keyword
      changes.push({
        keyword: current.keyword,
        type: 'new',
        currentPosition: current.position,
        message: current.position
          ? `First check: ranking #${current.position}`
          : `First check: not ranking in top 100`
      });
      continue;
    }

    const prevPos = previous.position;
    const currPos = current.position;

    if (prevPos === currPos) continue; // No change

    if (prevPos === null && currPos !== null) {
      changes.push({
        keyword: current.keyword,
        type: 'entered',
        currentPosition: currPos,
        message: `Entered rankings at #${currPos}`
      });
    } else if (prevPos !== null && currPos === null) {
      changes.push({
        keyword: current.keyword,
        type: 'dropped',
        previousPosition: prevPos,
        message: `Dropped out of top 100 (was #${prevPos})`
      });
    } else if (currPos < prevPos) {
      changes.push({
        keyword: current.keyword,
        type: 'improved',
        previousPosition: prevPos,
        currentPosition: currPos,
        change: prevPos - currPos,
        message: `Improved from #${prevPos} to #${currPos} (+${prevPos - currPos})`
      });
    } else {
      changes.push({
        keyword: current.keyword,
        type: 'declined',
        previousPosition: prevPos,
        currentPosition: currPos,
        change: currPos - prevPos,
        message: `Declined from #${prevPos} to #${currPos} (-${currPos - prevPos})`
      });
    }
  }

  return changes;
}

Building Alerts

When significant ranking changes occur, you want to know immediately. Here is a simple console alert system that you can extend with email or Slack notifications:

// Display ranking changes
function reportChanges(changes) {
  if (changes.length === 0) {
    console.log('\nNo ranking changes detected.');
    return;
  }

  console.log(`\n${'='.repeat(50)}`);
  console.log('RANKING CHANGES DETECTED');
  console.log('='.repeat(50));

  for (const change of changes) {
    const icon = change.type === 'improved' || change.type === 'entered'
      ? '[UP]'
      : change.type === 'declined' || change.type === 'dropped'
      ? '[DOWN]'
      : '[NEW]';

    console.log(`${icon} ${change.keyword}: ${change.message}`);
  }

  console.log('='.repeat(50));
}

To extend this with email alerts, you could add a function that sends notifications via an SMTP service or a webhook to a Slack channel whenever a keyword drops more than 5 positions.

SEO analytics dashboard displaying keyword positions

Automating with Cron Jobs

Put it all together in a main function and run it on a schedule:

// Main execution
async function main() {
  console.log(`\nRank Tracker - ${new Date().toLocaleString()}`);
  console.log(`Tracking: ${config.domain}`);
  console.log('-'.repeat(40));

  const db = loadDatabase();
  const results = await checkAllKeywords();
  const changes = detectChanges(db, results);

  // Save the new check
  db.checks.push({
    timestamp: new Date().toISOString(),
    results
  });
  saveDatabase(db);

  // Report changes
  reportChanges(changes);

  // Summary
  const ranked = results.filter(r => r.position !== null);
  console.log(`\nSummary: ${ranked.length}/${results.length} keywords ranking`);
  if (ranked.length > 0) {
    const avg = ranked.reduce((s, r) => s + r.position, 0) / ranked.length;
    console.log(`Average position: #${avg.toFixed(1)}`);
  }
}

main().catch(console.error);

To run this daily, add a cron job (on Linux/Mac):

# Run every day at 8 AM
0 8 * * * cd /path/to/rank-tracker && node tracker.js >> tracker.log 2>&1

Or on a server, use a process manager like PM2 with its built-in cron support:

pm2 start tracker.js --cron "0 8 * * *" --no-autorestart

Next Steps

You now have a functional keyword rank tracker. Here are some ways to extend it:

  • Web dashboard - Build a frontend to visualize ranking trends with charts. See our guide on automating SEO reports for a complete walkthrough.
  • Competitor tracking - Track multiple domains to compare your rankings against competitors. Read more in our competitor analysis tool guide.
  • SERP feature tracking - Track whether your site appears in featured snippets, People Also Ask, or other SERP features.
  • Database upgrade - Replace the JSON file with SQLite or PostgreSQL for better performance with large datasets.
  • Email reports - Send weekly summary reports with ranking trends and notable changes.

Cost estimate: Tracking 50 keywords daily uses about 1,500 API calls per month. With Serpent API's pay-as-you-go pricing from $0.01/1K (DDG Scale tier), that costs under $0.02 per month. Compare that to $50-300/month for a commercial rank tracker.

Start Building Your Rank Tracker

Get started with Serpent API today. 100 free searches included, no credit card required.

Get Your Free API Key

Explore: SERP API · News API · Image Search API · Try in Playground