# How OpenSEO Integrates with DataForSEO: A Technical Deep Dive

> Learn how OpenSEO integrates with DataForSEO using a thin wrapper for lazy SDK loading, billing-aware execution, and unified error handling. Explore the technical details.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: deep-dive
- Published: 2026-09-06

---

**OpenSEO integrates with DataForSEO through a thin, metered wrapper in `src/server/lib/dataforseo` that provides lazy SDK loading, billing-aware execution, and unified error handling.**

The `every-app/open-seo` repository implements a **billing-aware façade** over the DataForSEO API. This integration shields the rest of the codebase from the raw SDK while enforcing credit limits and attributing costs to specific features. Let's examine the three-layer architecture that makes this possible.

## Lazy Loading of the DataForSEO SDK

The DataForSEO SDK weighs approximately **3 MB**—too heavy for server cold starts. OpenSEO solves this with **lazy loading**: the SDK is imported only when the first API call is made.

```typescript
export function loadDataforseoSections(): Promise<DataforseoSections> {
  return (sectionsPromise ??= import("@/server/lib/dataforseo/sections"));
}

```

As implemented in [`src/server/lib/dataforseo/client.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/client.ts) at lines 30-34, this pattern keeps initial boot times fast by deferring the heavy import until actually needed.

## The Metered Client Creator

The `createDataforseoClient` function returns an object whose methods are wrapped by a **meter** function. This meter injects billing logic, enforces credit limits, and records spend per feature.

```typescript
export function createDataforseoClient(customer: BillingCustomerContext) {
  return {
    business: { /* businessListings, etc. */ },
    backlinks: { /* backlink methods */ },
    keywords: { /* keyword research methods */ },
    domain: { /* domain analysis methods */ },
    serp: { /* SERP checking methods */ },
    labs: { /* experimental features */ },
    lighthouse: { /* page speed data */ },
    aiSearch: { /* AI-powered search features */ },
  };
}

```

See [`src/server/lib/dataforseo/client.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/client.ts) lines 63-72. The `meter` helper at lines 48-60 wraps each method:

```typescript
function meter<I, T>(customer, pick, defaultFeature?) {
  return (input) => meterDataforseoCall(
    customer,
    async () => pick(await loadDataforseoSections())(input),
    input.creditFeature ?? defaultFeature
  );
}

```

This adds credit-feature logic and delegates to `meterDataforseoCall`.

## Hosted-Mode Billing and Error Handling

When OpenSEO runs as a hosted SaaS platform, every DataForSEO call undergoes **three billing stages**:

1. **Pre-flight check** – `assertUsageCreditsAvailable` verifies the organization's credit balance
2. **Execution** – The actual DataForSEO API call runs through the metered wrapper
3. **Post-flight tracking** – `trackDataforseoCost` records spend using the feature mapped from the SDK path

The `meterDataforseoCall` function (lines 54-78 in [`client.ts`](https://github.com/every-app/open-seo/blob/main/client.ts)) implements this flow. Even charged task errors are tracked—unless they're validation-only failures, ensuring accurate billing attribution.

## How the Wrapper Is Used in Practice

Most OpenSEO functionality obtains a client via `createDataforseoClient(context.billing)` and calls the appropriate method. Here's the **"Get ranked keywords"** MCP tool:

```typescript
const client = createDataforseoClient(context.billing);
const keywords = await client.domain.rankedKeywords({
  target: target.hostname,
  locationCode: market.locationCode,
  languageCode: market.languageCode,
  // ...other options
});

```

See [`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) lines 800-806.

### Common Usage Patterns Across OpenSEO

- **Local business search** – `client.business.businessListings`
- **SERP competitors** – `client.labs.serpCompetitors`
- **Keyword metrics** – `client.keywords.related` (via `fetchKeywordMetricsForList`)

Every call automatically: lazy-loads the SDK, enforces credit limits (hosted mode only), attributes spend to a feature (`rank_tracking`, `local_seo`, etc.), and converts DataForSEO responses into OpenSEO's internal schema via helpers like `toRankedKeywordRow`.

## Code Examples

### Pulling Ranked Keywords for a Domain

```typescript
import { createDataforseoClient } from "@/server/lib/dataforseo";
import { resolveMarketSelector } from "@/server/mcp/tools/dataforseo-research-tools";

const client = createDataforseoClient(billingContext);

const market = resolveMarketSelector(
  { locationCode: 284, languageCode: "en" }, // US-English
  projectMeta,
);

const result = await client.domain.rankedKeywords({
  target: "example.com",
  locationCode: market.locationCode,
  languageCode: market.languageCode,
  limit: 20,
  orderBy: ["ranked_serp_element.serp_item.rank_absolute,asc"],
});

console.log(result.items); // → array of ranked keyword rows

```

### Searching Local Businesses

```typescript
import { createDataforseoClient } from "@/server/lib/dataforseo";

const client = createDataforseoClient(billingContext);
const rows = await client.business.businessListings({
  categories: ["pizza_restaurant"],
  title: "Joe's Pizzeria",
  locationCoordinate: "37.7749,-122.4194,5",
  isClaimed: false,
  limit: 10,
});
console.log(rows.map(r => r.title));

```

### Direct Use in a Server Workflow

```typescript
import { createDataforseoClient } from "@/server/lib/dataforseo";
import { parseResearchTargetOrThrow } from "@/server/lib/domainUtils";

export async function runRankCheck(project, billing, url) {
  const client = createDataforseoClient(billing);
  const target = parseResearchTargetOrThrow(url);
  
  const serp = await client.serp.rankCheck({
    target: target.hostname,
    locationCode: project.locationCode,
    languageCode: project.languageCode,
  });
  
  return serp;
}

```

See [`src/server/workflows/RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/RankCheckWorkflow.ts) lines 334-336.

## Key Files in the Integration

| File | Purpose |
|------|---------|
| [`src/server/lib/dataforseo/client.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/client.ts) | Core metered client factory (`createDataforseoClient`) and billing wrapper |
| [`src/server/lib/dataforseo/sections.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/sections.ts) | Actual DataForSEO SDK fetchers; loaded lazily |
| [`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) | MCP tools calling the client (ranked keywords, local business, SERP competitors) |
| [`src/server/workflows/RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/RankCheckWorkflow.ts) | Server workflow obtaining client for rank checks |
| [`src/server/lib/billing/subscription.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/billing/subscription.ts) | Credit limit enforcement and spend recording |
| [`src/shared/billing-credit-features.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing-credit-features.ts) | Maps DataForSEO endpoint paths to billing features |

## Summary

- **Lazy loading** in [`client.ts`](https://github.com/every-app/open-seo/blob/main/client.ts) defers the 3 MB DataForSEO SDK import until first use
- **Metered execution** wraps every API call with billing logic via `meterDataforseoCall`
- **Feature attribution** maps SDK paths to credit features like `rank_tracking` and `local_seo`
- **Unified façade** exposes domain, serp, keywords, business, labs, lighthouse, and aiSearch endpoints
- **Automatic response shaping** converts DataForSEO payloads to OpenSEO's internal schema

## Frequently Asked Questions

### Does OpenSEO require the DataForSEO SDK at startup?

No. The SDK is **lazily loaded** via `loadDataforseoSections()` only when the first API call is made. This keeps server cold starts fast despite the SDK's 3 MB size.

### How does OpenSEO handle billing for DataForSEO usage?

In hosted mode, the `meterDataforseoCall` function checks credits before execution, runs the API call, then records spend via `trackDataforseoCost`. Each endpoint maps to a specific credit feature like `rank_tracking` or `local_seo`.

### Can I use the DataForSEO client outside of MCP tools?

Yes. The `createDataforseoClient` function is used throughout OpenSEO—including server workflows like [`RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/RankCheckWorkflow.ts)—not just MCP tools. Any code with access to a `BillingCustomerContext` can instantiate the client.

### What happens if a DataForSEO call fails?

Charged task errors are still tracked for billing unless they're validation-only failures. The meter wrapper ensures costs are accurately attributed even when API calls don't return usable data.