# How OpenSEO's Target Detection Algorithm Works for SERP Analysis

> Understand OpenSEO's target detection algorithm for SERP analysis. Learn how it classifies domains or keywords to optimize your DataForSEO queries.

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

---

**OpenSEO uses a deterministic heuristic in the `detectTarget` function to classify user input as either a domain or keyword, routing SERP queries to the appropriate DataForSEO endpoints.**

The `every-app/open-seo` repository implements a lightweight but robust target detection system that sits at the heart of its SERP analysis pipeline. This algorithm determines whether a user-provided string represents a **domain** (e.g., `example.com`) or a **keyword/brand phrase** (e.g., `best coffee maker`), ensuring accurate API routing and predictable credit consumption.

## The Detection Algorithm Explained

The complete logic lives in [`src/shared/targetDetection.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.ts) within the `detectTarget` exported function. The algorithm follows five deterministic steps:

### 1. Input Sanitization

Raw input is trimmed of leading and trailing whitespace:

```typescript
const trimmed = rawInput.trim();

```

### 2. Domain Pattern Recognition

The algorithm checks three conditions to flag domain-like input:

- **Non-empty string** (`trimmed.length > 0`)
- **No internal whitespace** (`!/\s/.test(trimmed)`)
- **Contains at least one dot** (`trimmed.includes(".")`)

### 3. Domain Normalization

Candidates passing the pattern check are passed to `normalizeDomain` from [`src/types/schemas/domain.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/domain.ts). This function:

- Converts to lowercase
- Strips protocols (e.g., `https://`)
- Validates proper hostname structure

### 4. Post-Normalization Validation

After normalization, the result must still contain a dot to qualify as a valid domain:

```typescript
if (hostname.includes(".")) {
  return { type: "domain", value: hostname };
}

```

### 5. Type Assignment

Success returns `{ type: "domain", value: hostname }`. Any failure—including normalization errors—falls back to `{ type: "keyword", value: trimmed }`.

## Complete Implementation

Here's the full `detectTarget` function as implemented in [`src/shared/targetDetection.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.ts) (lines 13-29):

```typescript
export function detectTarget(rawInput: string): DetectedTarget {
  const trimmed = rawInput.trim();
  const looksLikeDomain =
    trimmed.length > 0 && !/\s/.test(trimmed) && trimmed.includes(".");

  if (looksLikeDomain) {
    try {
      const hostname = normalizeDomain(trimmed);
      if (hostname.includes(".")) {
        return { type: "domain", value: hostname };
      }
    } catch {
      // Normalization failed → fall back to keyword.
    }
  }

  return { type: "keyword", value: trimmed };
}

```

## Why Target Detection Matters for SERP Workflows

SERP-related tools in OpenSEO—including `get-serp-results`, rank tracking, and local SERP endpoints—accept a **target** parameter that can be either type. The `detectTarget` algorithm enables:

- **Domain-level queries**: "Who ranks for `example.com`?" → routed to domain-centric DataForSEO endpoints
- **Keyword-level queries**: "SERP for 'best coffee maker'" → triggers keyword-focused fetches

This separation prevents accidental API misuse and keeps credit consumption predictable.

## Practical Usage Examples

```typescript
import { detectTarget } from "@/shared/targetDetection";

const inputs = [
  "example.com",
  "  https://example.com  ",
  "Best coffee maker",
  "sub.domain.co.uk",
  "invalid domain",
];

inputs.forEach((i) => {
  const result = detectTarget(i);
  console.log(`${i} → ${result.type}: ${result.value}`);
});

/* Expected output:
example.com → domain: example.com
  https://example.com   → domain: example.com
Best coffee maker → keyword: Best coffee maker
sub.domain.co.uk → domain: sub.domain.co.uk
invalid domain → keyword: invalid domain
*/

```

## Key Source Files

- **[`src/shared/targetDetection.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.ts)** — Core detection logic and `detectTarget` implementation
- **[`src/types/schemas/domain.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/domain.ts)** — `normalizeDomain` function for hostname canonicalization
- **[`src/server/mcp/tools/get-serp-results.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/get-serp-results.ts)** — Consumes detected targets for SERP API calls
- **[`src/server/mcp/tools/dataforseo-research-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/dataforseo-research-tools.ts)** — Handles SERP, local SERP, and competitor queries using target typing

## Summary

- **The `detectTarget` algorithm** uses a simple deterministic heuristic: no whitespace + contains dot = potential domain
- **Normalization via `normalizeDomain`** validates and canonicalizes domain candidates before final classification
- **Graceful fallback** treats any invalid domain as a keyword, ensuring robustness
- **Critical for API routing** — correct classification prevents wasted DataForSEO credits and returns relevant SERP data

## Frequently Asked Questions

### How accurate is OpenSEO's target detection algorithm?

The algorithm achieves high accuracy for typical inputs through its conservative approach. It only classifies strings as domains when they pass strict pattern matching **and** survive normalization. Strings with spaces, missing dots, or invalid hostname structures automatically fall back to keyword classification. According to the source code, edge cases like protocol-prefixed URLs (`https://example.com`) are handled during the normalization phase in [`src/types/schemas/domain.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/domain.ts).

### Can the target detection be bypassed or overridden?

The current implementation in [`src/shared/targetDetection.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.ts) does not expose override parameters—classification is fully automatic. Tools consuming the output (such as [`get-serp-results.ts`](https://github.com/every-app/open-seo/blob/main/get-serp-results.ts)) receive the structured `{ type, value }` object and route requests accordingly. If you need to force keyword treatment for a domain-like string, you could introduce intentional whitespace (e.g., `"example .com"`), though this is not a recommended pattern.

### What happens if domain normalization throws an error?

Failed normalization triggers the catch block in `detectTarget`, immediately falling back to keyword classification without crashing the pipeline. This defensive design means malformed domains like `"not..valid.com"` or strings with invalid characters become keywords rather than causing exceptions. The original trimmed input is preserved as the keyword value.

### Does the algorithm support internationalized domain names (IDNs)?

The detection logic itself is IDN-agnostic—it checks for dots and whitespace only. Actual IDN support depends on the `normalizeDomain` implementation in [`src/types/schemas/domain.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/domain.ts). Review that file's source code to verify whether punycode conversion or Unicode normalization is applied during hostname canonicalization.