# How OpenSEO Handles DataForSEO API Rate Limits and Billing

> Discover how OpenSEO effectively manages DataForSEO API rate limits and billing. Learn about its layered architecture for handling errors and tracking usage credits with configurable pricing.

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

---

**OpenSEO manages DataForSEO API constraints through a layered architecture that maps HTTP 429 responses to typed `RATE_LIMITED` errors, converts HTTP 402 balance errors into specific billing `AppError` codes, and tracks usage credits with configurable pricing markups.**

OpenSEO integrates DataForSEO (DF-SEO) services using a sophisticated billing and rate-limiting system designed to prevent double-charging and handle API constraints gracefully. The open-source codebase implements distinct credit pools, automated retry policies, and granular error classification to ensure predictable costs for hosted customers while allowing self-hosted deployments to pay raw DF-SEO rates. Understanding how OpenSEO handles DataForSEO API rate limits and billing reveals a robust pattern for third-party API cost management.

## Centralized Request Wrapper with Authentication

All DataForSEO API calls flow through the `createAuthenticatedFetch` function in [`src/server/lib/dataforseo/core.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/core.ts), which serves as the primary gateway for request authentication and baseline error handling. This wrapper automatically injects the `DATAFORSEO_API_KEY` environment variable into request headers and enforces a strict 60-second timeout on all outbound calls.

The wrapper distinguishes between **idempotent reads** (such as keyword data fetches) and **non-idempotent operations** (like Lighthouse live tests). Idempotent requests automatically retry on transient 5xx errors up to two times with a 250ms backoff per attempt, while non-idempotent calls disable retries entirely to prevent accidental double-charging.

## Billing Classification and Balance Error Detection

When DataForSEO returns non-2xx responses, OpenSEO employs a specialized classification system to distinguish between generic failures and billing-specific issues. The `createDataforseoBillingClassifier` function in [`src/server/lib/dataforseoBillingClassification.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseoBillingClassification.ts) analyzes error responses for HTTP 402 status codes or message content containing "insufficient funds".

When detected, the system throws a typed `AppError` with specific codes such as `BACKLINKS_BILLING_ISSUE`, `AI_BILLING_ISSUE`, or `KEYWORDS_BILLING_ISSUE`. This allows upstream workflows to surface clear "billing problem" messages to users rather than generic failure notifications.

```typescript
// Example: Performing a DF-SEO request with billing & rate-limit handling
import { keywordsDataApi } from "@/server/lib/dataforseo/core";
import { createDataforseoBillingClassifier } from "@/server/lib/dataforseoBillingClassification";

const classifyKeywordsError = createDataforseoBillingClassifier({
  pathPrefix: "/keywords/",
  billingIssueCode: "KEYWORDS_BILLING_ISSUE",
  billingIssueMessage: "The connected DataForSEO account has a billing or balance issue",
});

async function fetchKeywordIdeas(projectId: string, kw: string) {
  const response = await keywordsDataApi(classifyKeywordsError).relatedKeywordsLive([
    /* request payload… */
  ]);
  // `assertOk` throws a RATE_LIMITED AppError if DF-SEO returned 429
  const task = assertOk(response, { classify: classifyKeywordsError, classifyPath: "/v3/keywords_data/related_keywords/live" });

  // Billing info is attached to the task result
  const { data, billing } = parseTaskItems(task);
  return { data, billing };
}

```

## Usage Credit Pools and Pricing Markup

OpenSEO tracks two distinct credit pools: **monthly usage credits** and **top-up credits**. The system deducts costs first from the monthly pool, then from any available top-up balance. The conversion rate is fixed at 1 USD to 1000 DF-SEO credits, defined by the `AUTUMN_SEO_DATA_CREDITS_PER_USD` constant in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts).

For hosted customers, the platform applies a markup factor of approximately 1.28× to raw DataForSEO costs before billing the amount to their OpenSEO subscription. The `applyBillingMarkupUsd` function handles this calculation, while `isHostedServerAuthMode()` checks determine whether to apply credit deductions or allow direct DF-SEO billing.

Self-hosted deployments bypass the OpenSEO credit pool entirely, paying DataForSEO directly using the raw cost conversion via `autumnSeoDataCreditsToUsd` without any platform markup.

## Rate Limit Handling and Retry Policies

When DataForSEO returns HTTP 429 (`RATE_LIMITED`), the `createAuthenticatedFetch` wrapper maps this to a generic `RATE_LIMITED` error code within the `AppError` type. Calling workflows such as rank-checking or backlink fetching can then decide whether to retry later or surface a user-friendly rate-limit message.

For internal billing operations, the `fetchAutumnEventsPage` function in [`src/serverFunctions/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/billing.ts) implements exponential backoff when Autumn's `events.list` endpoint returns HTTP 429. This retry logic uses a maximum retry count to prevent infinite loops while ensuring usage data eventually synchronizes.

```typescript
// Example: Fetching billing usage events (rate-limited retry)
import { getBillingUsageEvents } from "@/serverFunctions/billing";

await getBillingUsageEvents({ start: 0, end: Date.now() });
// Internally this will back‑off on 429 responses from Autumn and retry up to 3 times.

```

## Billing Usage Reporting and Balance Monitoring

The platform periodically queries Autumn's API to pull usage-credit events, aggregating them into consumable lists for the user interface. The `getBillingUsageEvents` function handles this synchronization, complete with rate-limit aware retry logic.

For real-time balance checks without consuming credits, the `whoami` tool in [`src/server/mcp/tools/whoami.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/whoami.ts) exposes current credit balances. Additionally, [`src/server/lib/market.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/market.ts) guards feature usage based on supported DataForSEO locations, preventing API calls that would immediately fail and waste credits on unsupported regions.

## Self-Hosted vs. Hosted Mode Behavior

The system behaves differently depending on deployment mode. **Hosted mode** activates the full credit pool system, applies the 1.28× markup, and manages DataForSEO API keys centrally. **Self-hosted mode** bypasses these mechanisms, allowing direct payment to DataForSEO and eliminating the markup layer.

This distinction is enforced through `isHostedServerAuthMode()` checks in [`src/serverFunctions/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/billing.ts), which trigger early returns for non-hosted environments, ensuring self-hosted users are charged only for raw DataForSEO consumption.

## Summary

- **Authentication Layer**: All requests route through `createAuthenticatedFetch` in [`src/server/lib/dataforseo/core.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/core.ts) with 60s timeouts and automatic API key injection.
- **Billing Errors**: The `createDataforseoBillingClassifier` converts HTTP 402 and "insufficient funds" messages into typed `AppError` codes like `BACKLINKS_BILLING_ISSUE`.
- **Credit System**: Hosted customers use a dual-pool system (monthly + top-up) with 1 USD = 1000 credits and a 1.28× markup applied via `applyBillingMarkupUsd`.
- **Rate Limiting**: HTTP 429 responses map to `RATE_LIMITED` errors, with exponential backoff for Autumn billing events and configurable retries for idempotent DF-SEO reads.
- **Deployment Modes**: Self-hosted installations pay raw DF-SEO rates without markup, while hosted users participate in the managed credit pool system.

## Frequently Asked Questions

### How does OpenSEO prevent double-charging when DataForSEO API calls fail?

OpenSEO disables automatic retries for non-idempotent operations (such as Lighthouse live tests) within the `createAuthenticatedFetch` wrapper, ensuring failed requests are not blindly retried. Idempotent reads retry only on transient 5xx errors with a maximum of two attempts and 250ms backoff, preventing duplicate charges while handling temporary outages.

### What happens when my DataForSEO account runs out of credits?

When DataForSEO returns HTTP 402 or messages containing "insufficient funds", the `createDataforseoBillingClassifier` throws a specific `AppError` (such as `AI_BILLING_ISSUE` or `KEYWORDS_BILLING_ISSUE`). This surfaces a clear billing problem message to the user rather than a generic API error, allowing immediate identification of balance issues.

### How does credit pricing differ between hosted and self-hosted OpenSEO deployments?

Hosted customers pay a marked-up rate of approximately 1.28× the raw DataForSEO cost, managed through OpenSEO's credit pool system (1 USD = 1000 credits). Self-hosted deployments bypass this entirely, paying DataForSEO directly at raw rates without platform markup or credit pool management.

### Where does OpenSEO handle retry logic for DataForSEO API rate limits?

Primary rate-limit handling occurs in [`src/server/lib/dataforseo/core.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/core.ts), where HTTP 429 responses map to `RATE_LIMITED` error codes. For billing-specific rate limits when querying Autumn's `events.list` endpoint, [`src/serverFunctions/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/billing.ts) implements exponential backoff with maximum retry limits to ensure reliable usage reporting without overwhelming upstream services.