How OpenSEO Determines If a Project's Domain Ranks for Tracked Keywords

OpenSEO determines whether a project's domain ranks for tracked keywords by normalizing the input target, querying DataForSEO's ranked-keywords API, matching returned SERP hostnames against the configured domain, and persisting the absolute rank position for continuous monitoring.

OpenSEO is an open-source SEO analytics platform built in the every-app/open-seo repository that automates SERP monitoring for multiple projects. Understanding how OpenSEO determines if a project's domain ranks for tracked keywords requires examining the normalization pipeline, the DataForSEO integration, and the domain-matching logic that translates raw SERP data into actionable ranking reports.

Normalizing and Detecting the Target Domain

Before any rank check occurs, OpenSEO must identify whether user input represents a domain or a keyword. The detectTarget helper in [src/shared/targetDetection.ts](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.ts) applies heuristics to classify the input.

When the input resembles a hostname, the utility normalizes it by stripping protocols (http:// or https://), removing the www. subdomain, and trimming trailing slashes. This ensures consistent matching later in the pipeline when comparing against SERP results.

// Normalize user input to identify domain targets
import { detectTarget } from "@/shared/targetDetection";

const input = "https://www.example.com/";
const { type, value } = detectTarget(input);
// type === "domain", value === "example.com"

Storing the Rank-Tracking Configuration

Once normalized, the domain is stored in a RankTrackingConfig defined in [src/types/schemas/rank-tracking.ts](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.ts). The RankTrackingService.createConfig and RankTrackingService.updateConfig methods 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) persist the configuration.

A config records the domain, projectMarket (location and language codes), devices (desktop, mobile, or both), serpDepth (number of results to fetch, specified as total count), and scheduleInterval (frequency of automated checks).

// Create a rank-tracking configuration for continuous monitoring
import { RankTrackingService } from "@/server/features/rank-tracking/services/RankTrackingService";

await RankTrackingService.createConfig({
  projectId: "proj_123",
  projectMarket: { locationCode: 2840, languageCode: "en" },
  domain: "example.com",
  serpDepth: 30,            // Equivalent to 3 SERP pages
  devices: "both",
  scheduleInterval: "weekly",
});

Executing Live and Queued Rank Checks

OpenSEO supports two execution modes defined by the RankCheckMethod type ("live" | "queued") in [src/shared/rank-tracking.ts](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts). The RankTrackingService.triggerCheck method initiates the process, first calling estimateRankCheckCredits to ensure the request does not exceed a user-specified maxCostCredits limit.

For live checks, the service invokes the DataForSEO client directly. For queued checks, it uses client.rankCheckTaskPost to create a background task. The request respects the configured serpDepth and device targeting.

// Trigger an immediate rank check with credit protection
import { RankTrackingService } from "@/server/features/rank-tracking/services/RankTrackingService";

await RankTrackingService.triggerCheck({
  configId: "cfg_abc",
  projectId: "proj_123",
  billingCustomer: { /* billing context */ },
  maxCostCredits: 500,      // Prevents exceeding budget
});

Fetching SERP Data from DataForSEO

The actual rank data originates from DataForSEO's ranked-keywords endpoint. The MCP tool get_ranked_keywords in [src/server/mcp/tools/get-ranked_keywords.ts](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/get-ranked_keywords.ts) invokes client.domain.rankedKeywords via the DataForSEO client wrapper in [src/server/lib/dataforseo/client.ts](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/client.ts).

The API response contains an array of ranked_serp_element.serp_item objects. Each object includes a rank_absolute field (the numeric position on the SERP) and a domain field containing the exact hostname of the ranking page.

Matching Domains and Extracting Rank Positions

The critical matching logic resides in [src/server/features/domain/services/domainKeywordMapper.ts](https://github.com/every-app/open-seo/blob/main/src/server/features/domain/services/domainKeywordMapper.ts). For each serp_item returned by DataForSEO, the service extracts the result hostname from the domain property.

OpenSEO performs a case-insensitive comparison between the normalized domain stored in the RankTrackingConfig and the hostname in the SERP result. When the values match, the corresponding rank_absolute value represents the current rank for that keyword-device pair.

Persisting Results and Scheduling Continuous Monitoring

After matching, the RankTrackingRepository in [src/server/features/rank-tracking/repositories/RankTrackingRepository.ts](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/repositories/RankTrackingRepository.ts) writes the rank, keyword, device, and timestamp to the rank_snapshots table.

To enable ongoing monitoring, the computeNextCheckAt function in [src/shared/rank-tracking.ts](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) calculates the next execution time based on the scheduleInterval. The ScheduledRankChecks service 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) uses this timestamp to queue subsequent jobs automatically.

// Retrieve the latest ranking results for dashboard display
const { config, results } = await RankTrackingService.getTracker(
  "cfg_abc",
  "proj_123",
);
// results contains: [{ keyword: "seo tools", device: "desktop", rank: 3, checkedAt: "2024-01-15T..." }]

Summary

OpenSEO implements a robust pipeline for determining domain keyword rankings:

  • Normalization: The detectTarget utility standardizes domain inputs by removing protocols and www prefixes.
  • Configuration: RankTrackingService stores domain, location, device, and depth settings in RankTrackingConfig.
  • Execution: Supports both live instant checks and queued scheduled monitoring via DataForSEO integration.
  • Data Retrieval: Fetches SERP items from the ranked-keywords endpoint, capturing rank_absolute and hostname data.
  • Matching: Compares configured domains against SERP hostnames case-insensitively using the domain keyword mapper.
  • Persistence: Stores rank snapshots in the database and schedules the next check using computeNextCheckAt.

Frequently Asked Questions

What external API does OpenSEO use to fetch keyword ranking data?

OpenSEO integrates with DataForSEO via the client wrapper in src/server/lib/dataforseo/client.ts. Specifically, it uses the client.domain.rankedKeywords method to retrieve SERP data containing absolute rank positions and result hostnames for tracked keywords.

How does OpenSEO distinguish between a domain and a keyword when adding tracking?

The detectTarget function in src/shared/targetDetection.ts applies pattern matching heuristics to input text. If the input parses as a hostname (containing dots and valid TLD structure), it is normalized and classified as a domain target; otherwise, it is treated as a keyword phrase for competitive tracking.

Can OpenSEO perform both real-time and scheduled rank checks?

Yes. The RankCheckMethod type in src/shared/rank-tracking.ts explicitly supports "live" for synchronous, immediate checks and "queued" for asynchronous background processing. The triggerCheck method handles both modes while respecting credit limits via estimateRankCheckCredits.

Where does OpenSEO store historical ranking positions for trend analysis?

Historical ranks are persisted in the rank_snapshots table by the RankTrackingRepository. Each snapshot records the keyword, device type, absolute rank, and check timestamp, enabling the UI to display ranking trends and velocity over time.

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 →