How DataForSEO API Billing and Rate Limiting Work in OpenSEO (2025 Implementation Guide)
OpenSEO separates authentication, billing, and rate-limit handling into distinct layers that track usage credits, apply pricing markup for hosted customers, and gracefully handle DataForSEO API 429 responses with exponential backoff.
OpenSEO's DataForSEO integration provides a production-ready foundation for SEO data workflows. The platform abstracts the complexity of credit management, error classification, and retry policies behind a unified request wrapper. This guide breaks down the exact implementation in every-app/open-seo.
DataForSEO API Architecture Overview
OpenSEO routes all DataForSEO calls through a centralized authentication and request wrapper in src/server/lib/dataforseo/core.ts. The createAuthenticatedFetch function injects the DATAFORSEO_API_KEY and enforces a 60-second request timeout.
This wrapper serves as the interception point for:
- Authentication header injection
- Error classification and mapping
- Retry policy enforcement
- Rate-limit detection
Billing Classification and Error Handling
Mapping DataForSEO Errors to Typed AppErrors
After any non-2xx response, OpenSEO classifies errors that indicate depleted account balance. The system looks for HTTP 402 status codes or messages containing "insufficient funds" and throws typed AppError instances.
The createDataforseoBillingClassifier function in src/server/lib/dataforseoBillingClassification.ts builds these classifiers per API path:
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",
});
This produces error codes like:
BACKLINKS_BILLING_ISSUEAI_BILLING_ISSUEKEYWORDS_BILLING_ISSUE
Each code surfaces a clear billing problem message to the user interface.
Usage Credit Pool Management
OpenSEO tracks two distinct credit pools:
- Monthly usage credits — deducted first
- Top-up credits — used after monthly pool depletion
The conversion rate is fixed at 1 USD → 1000 DataForSEO credits. Hosted customer pricing includes a markup factor (approximately 1.28×) applied to raw DataForSEO costs before billing to the OpenSEO subscription.
Constants and conversion helpers reside in src/shared/billing.ts:
// Conversion constants
AUTUMN_SEO_DATA_CREDITS_PER_USD
// Markup application for hosted mode
applyBillingMarkupUsd(rawCost)
Self-hosted deployments bypass the OpenSEO credit pool entirely. The code checks isHostedServerAuthMode() before applying deductions, and these users pay DataForSEO directly using autumnSeoDataCreditsToUsd without markup.
Rate Limiting and Retry Policies
Handling DataForSEO 429 Responses
When DataForSEO returns HTTP 429 (RATE_LIMITED), the wrapper maps it to a generic RATE_LIMITED error code in AppError. The calling workflow decides whether to:
- Retry the request later
- Surface a user-friendly rate-limited message
This mapping occurs in src/server/lib/dataforseo/core.ts:
// Simplified error mapping logic
status === 429 ? "RATE_LIMITED" : /* other codes */
Per-API Retry Configuration
The same wrapper implements differentiated retry behavior:
| Call Type | Retry Policy | Rationale |
|---|---|---|
| Idempotent reads (keyword data, rankings) | Up to 2 retries, 250ms backoff | Safe to repeat without side effects |
| Non-idempotent calls (Lighthouse live tests) | No retries | Prevents double-charging |
This is controlled by DATAFORSEO_MAX_RETRIES logic within createAuthenticatedFetch.
Billing Usage Reporting with Autumn API
For hosted customers, OpenSEO queries Autumn's events.list endpoint to pull usage-credit events. The client implements exponential backoff with maximum retry count for HTTP 429 responses:
import { getBillingUsageEvents } from "@/serverFunctions/billing";
await getBillingUsageEvents({ start: 0, end: Date.now() });
// Internally backs off on 429 and retries up to 3 times
The implementation in src/serverFunctions/billing.ts includes fetchAutumnEventsPage with full retry logic.
Practical DataForSEO API Integration Example
Here's a complete pattern for making DataForSEO API calls with full billing and rate-limit handling:
import { keywordsDataApi } from "@/server/lib/dataforseo/core";
import { createDataforseoBillingClassifier } from "@/server/lib/dataforseoBillingClassification";
import { assertOk, parseTaskItems } from "@/server/lib/dataforseo/helpers";
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([
{ keyword: kw, location_code: 2840, language_code: "en" }
]);
// Throws RATE_LIMITED AppError if DataForSEO returned 429
const task = assertOk(response, {
classify: classifyKeywordsError,
classifyPath: "/v3/keywords_data/related_keywords/live"
});
// Billing metadata attached to task result
const { data, billing } = parseTaskItems(task);
return { data, billing };
}
Self-Hosted vs. Hosted Billing Modes
| Aspect | Hosted Mode | Self-Hosted Mode |
|---|---|---|
| Credit pool | Managed by OpenSEO | Direct DataForSEO billing |
| Cost calculation | Raw cost × 1.28 markup | Raw cost only |
| Credit tracking | Autumn events API | N/A |
| Implementation check | isHostedServerAuthMode() returns true |
isHostedServerAuthMode() returns false |
The early-return logic in src/serverFunctions/billing.ts prevents unnecessary credit operations when running self-hosted.
Key Files Reference
| File | Purpose |
|---|---|
src/shared/billing.ts |
Credit pools, conversion functions, markup logic |
src/server/lib/dataforseo/core.ts |
Authentication, timeout, retries, error mapping |
src/server/lib/dataforseoBillingClassification.ts |
Balance error classification |
src/serverFunctions/billing.ts |
Autumn events fetching with backoff |
src/server/mcp/tools/whoami.ts |
Credit balance query (no consumption) |
src/server/lib/market.ts |
Location validation to prevent wasted credits |
Summary
- Authentication layer:
createAuthenticatedFetchincore.tscentralizes all DataForSEO API calls with 60s timeout - Billing classification: Per-path error classifiers convert balance errors into typed
AppErrorcodes - Credit pools: Monthly + top-up pools with 1:1000 USD conversion and 1.28× hosted markup
- Rate limiting: 429 responses map to
RATE_LIMITEDerrors; Autumn API calls use exponential backoff - Retry policies: Idempotent reads retry twice with 250ms delay; non-idempotent calls never retry
- Deployment modes: Hosted uses OpenSEO credit system; self-hosted pays DataForSEO directly
Frequently Asked Questions
What happens when DataForSEO returns HTTP 429?
OpenSEO maps the 429 status to a RATE_LIMITED AppError. The calling workflow determines whether to retry later or display a rate-limited message to users. For Autumn billing API calls, the client automatically retries with exponential backoff up to 3 times.
How does OpenSEO prevent double-charging for API calls?
Non-idempotent operations like Lighthouse live tests have retries disabled in createAuthenticatedFetch. Only idempotent read operations (keyword data, ranking checks) retry on transient 5xx errors, ensuring charged operations execute exactly once.
What is the credit conversion rate for DataForSEO services?
OpenSEO uses a fixed rate of 1000 DataForSEO credits per 1 USD. Hosted customers see approximately 1.28× this base cost after markup. Self-hosted users pay the raw DataForSEO rate directly without OpenSEO's credit pooling system.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →