# How OpenSEO Handles Scheduled Rank Tracking Checks and Reporting

> Discover how OpenSEO automates rank tracking with Cloudflare Worker cron jobs and DataForSEO integration. Schedule checks and access reports via MCP or API.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-08-15

---

**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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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

```typescript
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

```typescript
// 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

```typescript
// 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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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.