# How OpenSEO Handles Batch Keyword Metrics: A Complete Technical Guide

> Learn how OpenSEO manages batch keyword metrics by segmenting requests, routing to DataForSEO, and unifying local and national data for efficient analysis.

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

---

**OpenSEO handles batch keyword metrics by chunking requests into 700-keyword segments, routing to DataForSEO's Google Ads or Labs endpoints based on location, and merging local and national data into a unified response.**

Every SEO platform faces the same challenge: retrieving search volume, CPC, competition, and keyword difficulty for hundreds or thousands of terms without hitting API rate limits. According to the every-app/open-seo source code, OpenSEO solves this through a purpose-built batching layer in [`src/server/lib/dataforseo/keyword-metrics.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/keyword-metrics.ts) that abstracts DataForSEO's constraints while delivering consistent, merged results to downstream features like Rank Tracking and Keyword Research.

---

## Batch Size Limits and Chunking Strategy

OpenSEO enforces a strict **700-keyword maximum per request** through the `KEYWORD_METRICS_BATCH_SIZE` constant. This aligns with DataForSEO's per-call limits and prevents oversized payloads that would trigger provider errors.

The `fetchKeywordMetricsForList` function implements the chunking logic:

- Accepts an arbitrary-length array of keywords
- Slices it into consecutive 700-keyword chunks
- Processes each chunk sequentially
- Returns a flat array with one result row per input keyword

This design guarantees **deterministic output sizing**—callers always receive the same number of rows they submitted, even if some keywords return no data.

---

## Provider Routing: Google Ads vs. Labs Endpoints

For each batch chunk, OpenSEO dynamically selects between two DataForSEO endpoints based on location configuration:

| Endpoint | Data Returned | Use Case |
|----------|-------------|----------|
| `adsSearchVolume` | Volume, CPC, competition | Localized or national keyword data |
| `keywordOverview` (Labs) | Difficulty, intent, SERP features | Extended keyword intelligence |

The decision flows through `getKeywordDataProvider`, which examines the target location's data provider settings to determine the optimal path.

---

## Local and National Data Merging

When a request specifies a `locationName`—indicating sub-country targeting like a city or state—OpenSEO calls **both endpoints simultaneously** and merges the results:

1. **Ads data** (volume, CPC, competition) takes precedence for commercial metrics
2. **Labs data** (difficulty, intent) supplements the extended signals
3. **Collapsed keywords** from Ads are backfilled with `null` placeholders to prevent stale national values from persisting

This merge happens inside `mergeLocalAndNationalRows`, which ensures each output row contains the complete metric set when available:

```ts
// Inside fetchKeywordMetricsForList (simplified)
if (params.locationName) {
  const [adsItems, labsItems] = await Promise.all([
    client.keywords.adsSearchVolume(/* … */),
    client.labs.keywordOverview(/* … */),
  ]);
  rows.push(...mergeLocalAndNationalRows(keywords, adsItems, labsItems));
}

```

---

## Error Resilience and Null Handling

OpenSEO's batch keyword metrics implementation defensively handles edge cases:

- **Missing keywords**: Items without a keyword value are silently filtered before the API call
- **Missing rows**: Any keyword that returns no data from DataForSEO receives a `null`-filled placeholder row
- **Partial failures**: Individual chunk failures don't cascade; the design supports per-chunk retry logic

This ensures downstream services in [`RankTrackingService.ts`](https://github.com/every-app/open-seo/blob/main/RankTrackingService.ts) and similar consumers receive predictable, well-shaped data regardless of upstream provider behavior.

---

## Usage in Production: Rank Tracking and Keyword Research

Higher-level services consume `fetchKeywordMetricsForList` without managing batch complexity themselves.

### Direct API Usage

```ts
import { createDataforseoClient } from '@/server/lib/dataforseo/client';
import { fetchKeywordMetricsForList } from '@/server/lib/dataforseo/keyword-metrics';

const client = createDataforseoClient(/* auth config */);

const metrics = await fetchKeywordMetricsForList(client, {
  keywords: ['open seo', 'keyword research', 'site audit'],
  locationCode: 2840,           // United States
  languageCode: 'en',
  creditFeature: 'keyword-research',
  includeClickstreamData: true,
});

```

### Integration in RankTrackingService.ts

```ts
// src/server/features/rank-tracking/services/RankTrackingService.ts
const metrics = await fetchKeywordMetricsForList(client, {
  keywords: keywordsToTrack,
  locationCode: project.locationCode,
  languageCode: project.languageCode,
  creditFeature: 'rank-tracking',
});

```

The service layers pass only configuration and keyword lists; all batching, routing, and merging remain encapsulated in the keyword-metrics module.

---

## Key Implementation Files

| File | Responsibility |
|------|--------------|
| [`src/server/lib/dataforseo/keyword-metrics.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/keyword-metrics.ts) | Core batching logic, `KEYWORD_METRICS_BATCH_SIZE`, provider routing, `mergeLocalAndNationalRows` |
| [`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) | Production consumer demonstrating service-layer integration |
| [`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) | UI-facing API that triggers batch metric requests |

---

## Summary

- **Batch keyword metrics** in OpenSEO are capped at 700 keywords per request via `KEYWORD_METRICS_BATCH_SIZE`
- **`fetchKeywordMetricsForList`** automates chunking, endpoint selection, and result flattening
- **Dual endpoint calls** merge Google Ads commercial data with Labs difficulty/intent signals for local targeting
- **Deterministic output** guarantees one row per input keyword, with `null` placeholders for missing data
- **Service-layer abstraction** lets Rank Tracking and Keyword Research features consume metrics without batch complexity

---

## Frequently Asked Questions

### What is the maximum number of keywords per batch request?

OpenSEO limits each request to 700 keywords, matching DataForSEO's provider maximum. The `KEYWORD_METRICS_BATCH_SIZE` constant in [`keyword-metrics.ts`](https://github.com/every-app/open-seo/blob/main/keyword-metrics.ts) enforces this ceiling, and `fetchKeywordMetricsForList` automatically segments larger lists into consecutive chunks.

### How does OpenSEO handle local vs. national keyword data?

When a `locationName` parameter indicates sub-country targeting, OpenSEO calls both the Google Ads and Labs endpoints in parallel via `Promise.all`. It then merges results—keeping Ads-provided volume, CPC, and competition while layering in Labs difficulty and intent—through the `mergeLocalAndNationalRows` function.

### Why do some keyword metrics return null values?

OpenSEO inserts `null` placeholders for any keyword that DataForSEO cannot resolve, including keywords collapsed by the Ads API. This prevents stale cached values from displaying and maintains array alignment between input keywords and output rows.

### Which OpenSEO features use batch keyword metrics?

Rank Tracking ([`RankTrackingService.ts`](https://github.com/every-app/open-seo/blob/main/RankTrackingService.ts)) and Keyword Research both invoke `fetchKeywordMetricsForList`. The higher-level [`dataforseo-research-tools.ts`](https://github.com/every-app/open-seo/blob/main/dataforseo-research-tools.ts) module also exposes this functionality to the OpenSEO UI, allowing users to request metrics for arbitrary keyword lists with automatic batch handling.