# OpenSEO Keyword Locations API Endpoints: Complete Developer Guide

> Explore OpenSEO's API endpoints for keyword locations. Discover `/api/keywords/research` for location-aware research and `/api/serp-locations/:country` to get sub-country SERP data.

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

---

**OpenSEO provides two API endpoints for keyword locations: `/api/keywords/research` for location-aware keyword research and `/api/serp-locations/:country` for retrieving sub-country SERP locations.**

OpenSEO, an open-source SEO tool developed by every-app, exposes a focused API surface for handling geographic targeting in keyword research. The keyword locations API endpoints work together to resolve market parameters and fetch granular location data from DataForSEO's infrastructure. Understanding these endpoints is essential for building custom integrations that require precise geographic control over SERP analysis.

## Primary Keyword Research Endpoint

The main entry point for location-aware keyword research is **`/api/keywords/research`**.

### Endpoint Specification

| Attribute | Value |
|-----------|-------|
| HTTP Method | `POST` |
| Implementation | [`src/serverFunctions/keywords.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/keywords.ts) (lines 31-33) |
| Primary Function | `researchKeywords` |

This endpoint accepts a keyword query with optional location and language codes. Before processing, the request passes through `resolveMarket()` in [`src/shared/keyword-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/keyword-locations.ts) (lines 7-19), which merges client-provided parameters with project defaults to guarantee a valid location-language pair.

### Request Body Structure

```typescript
// POST /api/keywords/research
{
  keyword: string;           // Required: search term to analyze
  locationCode?: number;     // Optional: DataForSEO location code
  languageCode?: string;     // Optional: ISO language code (e.g., "en")
}

```

### Complete Request Example

```typescript
// Example: request keyword research with a custom location
await fetch('/api/keywords/research', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    keyword: 'open source seo',
    locationCode: 2840,   // United States (default)
    languageCode: 'en',
  }),
}).then(r => r.json());

```

The `KeywordResearchService.research` method then constructs the DataForSEO request using these resolved market codes.

## SERP Locations Endpoint

For populating location pickers or analyzing sub-country targeting options, use **`/api/serp-locations/:country`**.

### Endpoint Specification

| Attribute | Value |
|-----------|-------|
| HTTP Method | `GET` |
| Path Parameter | `:country` — ISO-2 country code (e.g., "us", "gb", "de") |
| Implementation | [`src/server/lib/dataforseo/serp-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/serp-locations.ts) (lines 59-62) |
| Primary Function | `fetchSerpLocationsForCountry` |

This endpoint proxies to DataForSEO's `googleLocationsCountry` endpoint (`GET https://api.dataforseo.com/v3/serp/google/locations/{country}`), defined in [`src/server/lib/dataforseo/core.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/core.ts). Results are filtered to supported location types and cached in KV storage for 30 days.

### Supported Location Types

The API filters DataForSEO's response to include only these sub-country types:

- **City**
- **County**
- **Municipality**
- **DMA Region**
- **Region**

### Complete Request Example

```typescript
// Example: fetch SERP locations for United Kingdom (ISO "gb")
await fetch('/api/serp-locations/gb')
  .then(r => r.json())
  .then(locations => console.log(locations));

```

## Location Resolution Architecture

Both endpoints rely on shared utilities that ensure consistent market parameter handling across the OpenSEO codebase.

### `resolveMarket()` Utility

Located in [`src/shared/keyword-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/keyword-locations.ts) (lines 7-19), this function:

1. Accepts optional `locationCode` and `languageCode` from the client
2. Merges with project default market settings
3. Returns a guaranteed valid location-language pair

### `resolveLabsMarket()` Variation

A Labs-specific counterpart for experimental features, also defined in [`src/shared/keyword-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/keyword-locations.ts).

## How the Endpoints Work Together

The keyword locations API endpoints follow a coordinated flow:

1. **Client initiates research** — POST to `/api/keywords/research` with optional location parameters
2. **Market resolution** — `resolveMarket()` or `resolveLabsMarket()` finalizes location-language pair
3. **External API construction** — `KeywordResearchService` builds DataForSEO request with resolved codes
4. **Location picker data** — When UI needs available locations, call `/api/serp-locations/:country` for filtered, cached DataForSEO results

## Key Source Files Reference

| File Path | Role |
|-----------|------|
| [`src/serverFunctions/keywords.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/keywords.ts) | Implements `/api/keywords/research` server function |
| [`src/shared/keyword-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/keyword-locations.ts) | Provides `resolveMarket`, `resolveLabsMarket`, and location/language constants |
| [`src/server/lib/dataforseo/serp-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/serp-locations.ts) | Contains `/api/serp-locations/:country` logic with caching and filtering |
| [`src/server/lib/dataforseo/core.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/core.ts) | Defines `googleLocationsCountry` call mapping to DataForSEO's public endpoint |

## Summary

- **`/api/keywords/research`** — Primary POST endpoint for location-aware keyword research, enriched by `resolveMarket()` before DataForSEO processing
- **`/api/serp-locations/:country`** — GET endpoint returning filtered, cached sub-country locations for any ISO-2 country code
- **Shared resolution logic** — `resolveMarket()` in [`src/shared/keyword-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/keyword-locations.ts) guarantees valid market parameters across both endpoints
- **Performance optimization** — SERP locations are cached for 30 days to minimize external API calls
- **Integration pattern** — Use `/api/serp-locations/:country` to populate location pickers, then pass selected codes to `/api/keywords/research`

## Frequently Asked Questions

### What authentication is required for these OpenSEO keyword locations API endpoints?

The source analysis does not specify authentication mechanisms — check your OpenSEO deployment configuration. Typically, serverless functions in this architecture inherit project-level API keys for DataForSEO access, while client requests may require session-based or token authentication depending on your hosting setup.

### Can I use `/api/keywords/research` without providing location or language codes?

Yes. The `locationCode` and `languageCode` parameters are optional. When omitted, `resolveMarket()` automatically applies your project's default market settings from [`src/shared/keyword-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/keyword-locations.ts), ensuring the request still reaches DataForSEO with valid geographic targeting.

### How fresh is the location data from `/api/serp-locations/:country`?

Data is cached for 30 days in KV storage. The endpoint first checks cache, then fetches from DataForSEO's `googleLocationsCountry` endpoint only if stale or missing. This balances freshness with API quota conservation.

### Why does OpenSEO filter DataForSEO's location results?

The implementation in [`src/server/lib/dataforseo/serp-locations.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/serp-locations.ts) specifically retains only `City`, `County`, `Municipality`, `DMA Region`, and `Region` types. This filtering removes unsupported location granularity (such as specific postal codes or neighborhoods) that would error in subsequent keyword research calls.