How OpenSEO Handles Scheduled Rank Tracking Checks and Reporting

OpenSEO uses a Cloudflare Worker cron job that runs every 5 minutes to queue due rank tracking configurations, while Cloudflare Workbooks fetch SERP data from DataForSEO and store results accessible via the MCP get_rank_tracker tool or REST API.

OpenSEO (every-app/open-seo) is an open-source SEO platform that automates rank tracking through scheduled checks and comprehensive reporting. The system coordinates a cron-based scheduler, workflow runners, and retrieval tools to monitor keyword positions across desktop and mobile devices. Understanding how these components interact helps developers optimize their rank tracking implementations and stay within API rate limits.

Scheduler Architecture: The Cron Worker

The scheduling system resides in src/server/features/rank-tracking/services/scheduledRankChecks.ts and operates as a Cloudflare Worker that executes every 5 minutes. This worker identifies which rank tracking configurations are due for checking and manages execution constraints to prevent overwhelming external APIs.

Fetching Due Configurations

The scheduler begins by calling RankTrackingRepository.getDueConfigsWithOrganization() to retrieve all configurations where next_check_at has passed. Each configuration includes the associated organization's billing status, allowing the system to enforce plan restrictions before executing costly API calls.

The repository method returns complete tracking profiles including:

  • schedule_interval (daily, weekly, monthly, or manual)
  • devices array (desktop and/or mobile)
  • serp_depth for result pagination
  • Current next_check_at timestamps

Budget Enforcement and Rate Limiting

To respect DataForSEO's rate limits, the scheduler implements strict resource budgeting. Each tick operates with a SCHEDULED_TASK_UNIT_BUDGET of 1,000 task units, calculated as keywords multiplied by devices. The system also enforces a TICK_DEADLINE_MS of 3 minutes to ensure the worker completes before the next invocation.

The loop terminates when either:

  • The task unit budget is exhausted
  • The 3-minute deadline approaches
  • No more due configurations remain

Plan Verification and Atomic Claiming

For hosted deployments, the scheduler verifies the organization has a paid plan via customerHasPaidPlan(). Free-tier configurations are skipped and logged with skippedFree status.

Eligible configurations are claimed atomically using RankTrackingRepository.claimDueConfig(), which:

  1. Updates next_check_at based on the schedule_interval
  2. Clears any previous last_skip_reason
  3. Prevents race conditions between concurrent worker instances

After claiming, the scheduler calls beginRankCheckRun() to launch the Cloudflare Workflow. If a workflow is already running for that configuration, the scheduler restores the original schedule and records the conflict.

The Rank Check Runner: Cloudflare Workflows

When beginRankCheckRun() initiates in src/server/features/rank-tracking/services/RankTrackingService.ts, it launches the RANK_CHECK_WORKFLOW Cloudflare Workflow. This workflow handles the actual SERP data retrieval:

  • Fetches ranking data from the DataForSEO API for every keyword/device combination
  • Stores results in the database with timestamps
  • Records a unique run ID for later querying
  • Updates last_checked_at upon completion

The same RankTrackingService.triggerCheck() method serves both scheduled runs and manual triggers from the run_rank_tracker MCP tool, ensuring consistent behavior regardless of initiation source.

Reporting and Data Retrieval

OpenSEO exposes rank tracking data through multiple interfaces, allowing both programmatic access via MCP tools and human-readable dashboards.

MCP Tool Interface

The get_rank_tracker tool (defined following the pattern in src/server/mcp/tools/run-rank-tracker.ts) returns structured data including:

  • trackerId: Unique identifier for the configuration
  • lastCheckedAt: ISO timestamp of the most recent completion
  • keywords: Array containing each keyword's current rank and rankChange
  • scheduleInterval: Current frequency setting
  • devices: Active device targets
  • serpDepth: Configured result depth
  • lastSkipReason: Explanation if last run was skipped

REST API Endpoints

The public API exposes endpoints like /api/rank-tracking/:id that return the same dataset consumed by the frontend. The web/src/routes/api/backlink-check.ts file demonstrates similar rank data handling patterns used throughout the platform.

Frontend Visualization

The rank tracking UI in src/web/src/routes/_marketing/features/rank-tracking/ renders this data with:

  • Movement arrows indicating rank changes
  • SERP feature flags (featured snippets, local packs, etc.)
  • Historical trend charts
  • Device-specific breakdowns

Implementation Examples

Creating a Scheduled Configuration

import { RankTrackingRepository } from '../features/rank-tracking/repositories/RankTrackingRepository';

const tracker = await RankTrackingRepository.create({
  projectId: "proj_123",
  domain: "example.com",
  devices: ["desktop", "mobile"],
  scheduleInterval: "daily",
  serpDepth: 10,
  keywords: ["seo tools", "rank tracker"]
});
// Returns a config with next_check_at set based on the interval

Triggering Manual Checks

// Using the MCP tool for immediate checks
import { runRankTrackerTool } from '../mcp/tools/run-rank-tracker';

const result = await runRankTrackerTool.handler({
  projectId: "proj_123",
  trackerId: "c0f2b9e4-8d3a-4b5e-9c1f-2d3e4f5a6b7c",
  maxCostCredits: 500,
});
// Returns workflow instance ID and estimated cost

Retrieving Current Rankings

// Using the get_rank_tracker MCP tool
const report = await getRankTrackerTool.handler({
  projectId: "proj_123",
  trackerId: "c0f2b9e4-8d3a-4b5e-9c1f-2d3e4f5a6b7c",
});

/* Report structure:
{
  trackerId: "c0f2b9e4-8d3a-4b5e-9c1f-2d3e4f5a6b7c",
  lastCheckedAt: "2024-07-12T08:15:00Z",
  keywords: [
    { keyword: "seo tools", rank: 3, rankChange: +1 },
    { keyword: "rank tracker", rank: 7, rankChange: -2 }
  ],
  scheduleInterval: "daily",
  devices: ["desktop", "mobile"],
  serpDepth: 10,
  lastSkipReason: null
}
*/

Summary

  • Scheduled checks rely on a Cloudflare Worker cron job running every 5 minutes from src/server/features/rank-tracking/services/scheduledRankChecks.ts
  • Budget protection enforces a 1,000 task-unit limit per tick and 3-minute execution deadlines to respect DataForSEO rate limits
  • Atomic claiming via RankTrackingRepository.claimDueConfig() prevents duplicate executions and handles next_check_at scheduling
  • Workflow execution uses beginRankCheckRun() in RankTrackingService.ts to launch Cloudflare Workflows that fetch SERP data
  • Reporting combines MCP tools (get_rank_tracker), REST APIs, and frontend dashboards displaying rank changes and SERP features

Frequently Asked Questions

How often does OpenSEO check rankings?

OpenSEO checks rankings based on the schedule_interval set in each configuration: daily, weekly, or monthly. The scheduler worker runs every 5 minutes to identify due configurations, but actual API calls respect the user-selected interval. Manual checks can be triggered instantly via the run_rank_tracker MCP tool regardless of schedule settings.

What happens if a rank check is already running?

If beginRankCheckRun() detects an existing workflow instance for the same tracker, it restores the original next_check_at schedule and records the conflict without initiating duplicate API calls. This prevents double-billing and data corruption while preserving the next scheduled check time.

How does OpenSEO handle API rate limits?

The scheduler enforces a SCHEDULED_TASK_UNIT_BUDGET of 1,000 units per tick (keywords × devices) and a 3-minute TICK_DEADLINE_MS. When limits are reached, remaining configurations wait for the next 5-minute interval. The system also verifies paid plan status via customerHasPaidPlan() before executingDataForSEO calls to ensure credit availability.

Can I retrieve historical rank data programmatically?

Yes. While the get_rank_tracker MCP tool returns the most recent check results including lastCheckedAt and current rank values with rankChange deltas, the underlying database schema in src/db/app.schema.ts stores historical records. Query the rank_tracking_configs table directly or use the REST API endpoints to build custom time-series reports showing position trends over months of scheduled checks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →