# How OpenSEO Meters Billing for DataForSEO API Calls: A Technical Breakdown

> Understand OpenSEO's billing for DataForSEO API calls. Learn how credits, markups, and user balances work for hosted deployments in this technical breakdown.

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

---

**OpenSEO tracks DataForSEO API costs through an internal credit pool, converting raw USD prices to credits at 1000 credits per dollar and applying a 28% markup for hosted deployments before deducting from user balances.**

The `every-app/open-seo` repository implements a metered-billing pipeline that ensures every third-party DataForSEO request is accurately accounted for and billed. Understanding how OpenSEO meter billing for DataForSEO API calls works is essential for developers customizing the platform or integrating their own usage-tracking systems. The system bridges raw DataForSEO pricing with user-facing balances through a series of deterministic conversion and markup stages.

## Credit Conversion and the Internal Pool

OpenSEO abstracts raw USD costs into an internal **usage-credit pool** to simplify balance tracking. When DataForSEO returns a price for any endpoint—whether keyword research, backlink analysis, or rank checking—the system immediately converts that value.

**The conversion constant** `AUTUMN_SEO_DATA_CREDITS_PER_USD` is set to `1000` in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts). This means a DataForSEO call costing $0.05 consumes 50 credits from the user's pool. The raw USD value returned by the API (stored as `rawUsd`) is multiplied by this constant to determine the credit deduction.

```ts
/* src/shared/billing.ts */
export const AUTUMN_SEO_DATA_CREDITS_PER_USD = 1000;

/** Convert internal credits back to USD (e.g., for balance display) */
export function autumnSeoDataCreditsToUsd(credits: number) {
  return credits / AUTUMN_SEO_DATA_CREDITS_PER_USD;
}

```

This abstraction allows the platform to display balances in whole numbers while maintaining precision for fractional cent transactions.

## Hosted vs. Self-Hosted Billing Markup

The billing pipeline diverges based on deployment mode. **Hosted OpenSEO customers** pay a 28% markup over raw DataForSEO costs to cover platform infrastructure, while self-hosted deployments pass through raw pricing directly.

The markup is controlled by the constant `SEO_DATA_COST_MARKUP = 1.28` and applied through the `applyBillingMarkupUsd()` function:

```ts
/* src/shared/billing.ts */
export const SEO_DATA_COST_MARKUP = 1.28;

/** Round to 5 decimal places for Stripe */
export function roundUsdForBilling(value: number) {
  return Math.round(value * 100_000) / 100_000;
}

/** Convert raw DataForSEO USD → displayed USD (hosted only) */
export function applyBillingMarkupUsd(rawUsd: number): number {
  return roundUsdForBilling(rawUsd * SEO_DATA_COST_MARKUP);
}

```

The `roundUsdForBilling()` function ensures Stripe compatibility by rounding to five decimal places, preventing floating-point arithmetic errors during checkout.

## Balance Checking and Credit Thresholds

Before executing any DataForSEO call, OpenSEO validates available funds against two distinct balance types:

- **`usage_credits`**: The monthly free quota allocated by subscription tier
- **`topup_credits`**: Additional credits purchased via one-time top-ups

The system checks balances in sequence, drawing from free credits first, then top-up credits. If the combined balance falls below `LOW_CREDITS_THRESHOLD_USD = 0.25`, the UI triggers a low-credit warning.

```ts
/* src/shared/billing.ts */
export const LOW_CREDITS_THRESHOLD_USD = 0.25;
export const AUTUMN_SEO_DATA_BALANCE_FEATURE_ID = "usage_credits";
export const AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID = "topup_credits";

```

When credits are exhausted, the request is blocked and the user is redirected to purchase additional credits or upgrade their subscription.

## Implementing Billing Display in Frontend Components

The `applyBillingMarkupUsd()` helper ensures consistent pricing display across the React frontend. In [`src/client/features/ai-search/components/BrandLookupSearchCard.tsx`](https://github.com/every-app/open-seo/blob/main/src/client/features/ai-search/components/BrandLookupSearchCard.tsx), the component conditionally applies the markup based on authentication mode:

```tsx
/* src/client/features/ai-search/components/BrandLookupSearchCard.tsx */
import { applyBillingMarkupUsd } from "@/shared/billing";

function CostTag({ rawUsd }: { rawUsd: number }) {
  // Hosted customers see the markup, self-hosted see the raw cost.
  const displayedUsd = isHostedClientAuthMode()
    ? applyBillingMarkupUsd(rawUsd)
    : rawUsd;

  return <span>${displayedUsd.toFixed(2)}</span>;
}

```

This pattern ensures that self-hosted users see raw DataForSEO costs while hosted users see the marked-up price that will actually be deducted from their balance.

## CLI Cost Profiling and Reporting

For administrative and auditing purposes, the repository includes CLI scripts that aggregate DataForSEO costs before applying markup. The [`scripts/brand-lookup-cost-profile.ts`](https://github.com/every-app/open-seo/blob/main/scripts/brand-lookup-cost-profile.ts) file demonstrates how to calculate both raw and billed costs for reporting:

```ts
/* scripts/brand-lookup-cost-profile.ts */
import { applyBillingMarkupUsd } from "@/shared/billing";

function computeCostReport(rawUsd: number) {
  // Raw cost from DataForSEO
  const totalRawUsd = rawUsd;

  // Hosted cost after markup
  const totalBilledUsd = applyBillingMarkupUsd(totalRawUsd);

  console.log(`Raw cost: $${totalRawUsd}`);
  console.log(`Billed (with markup): $${totalBilledUsd}`);
}

```

This separation allows finance teams to reconcile external DataForSEO invoices against internal credit consumption.

## Stripe Integration and Checkout Flow

When balances deplete, the system initiates Stripe checkout sessions using predefined plan identifiers. The constants `AUTUMN_PAID_PLAN_ID` and `AUTUMN_SEO_DATA_TOP_UP_PLAN_ID` map to Stripe products:

- **`base-plan`**: The recurring subscription tier
- **`credit-top-up`**: One-time credit purchases

These identifiers, along with `AUTUMN_CHECKOUT_SESSION_PARAMS`, configure tax collection and billing information requirements during the checkout process. The `/billing` and `/subscribe` routes expose this logic to the frontend, ensuring seamless payment flow when `usage_credits` and `topup_credits` are insufficient.

## Handling Concurrent API Workflows

Complex operations like site audits or batch rank checks may trigger multiple concurrent DataForSEO calls. The workflows in [`src/server/workflows/RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/RankCheckWorkflow.ts) and [`src/server/workflows/siteAuditWorkflowPhases.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/siteAuditWorkflowPhases.ts) demonstrate how costs are batched and aggregated before being deducted from the credit pool.

Each task in these workflows calculates its individual DataForSEO cost, applies the markup if running in hosted mode, and then submits the total credit deduction to the balance system. This ensures that even with parallel execution, every API call is metered accurately without race conditions or double-charging.

## Summary

- **OpenSEO meter billing for DataForSEO API calls** converts raw USD costs to internal credits at a fixed rate of 1000 credits per dollar using `AUTUMN_SEO_DATA_CREDITS_PER_USD`.
- Hosted deployments apply a 28% markup via `applyBillingMarkupUsd()` in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts), while self-hosted instances use raw DataForSEO pricing.
- The system checks balances against `usage_credits` (monthly allowance) and `topup_credits` (purchased balance), warning users when funds drop below $0.25.
- All prices are rounded to five decimal places using `roundUsdForBilling()` to ensure Stripe compatibility.
- When credits are exhausted, the system blocks requests and redirects to Stripe checkout using plan IDs `base-plan` or `credit-top-up`.

## Frequently Asked Questions

### How does OpenSEO convert DataForSEO prices to internal credits?

OpenSEO multiplies the raw USD price returned by DataForSEO by the constant `AUTUMN_SEO_DATA_CREDITS_PER_USD` (set to 1000) to determine credit consumption. This conversion happens immediately upon receiving the API response, allowing the system to deduct precise amounts from the user's balance pool.

### What is the difference between hosted and self-hosted billing?

Hosted customers pay a 28% markup over raw DataForSEO costs to cover platform infrastructure, calculated by multiplying the raw price by `SEO_DATA_COST_MARKUP` (1.28). Self-hosted deployments skip this markup entirely, displaying and charging only the raw DataForSEO price returned in the API response.

### When does OpenSEO warn users about low credit balances?

The system triggers a low-credit warning when the combined balance of `usage_credits` and `topup_credits` falls below `LOW_CREDITS_THRESHOLD_USD`, which is set to $0.25. This check occurs before executing any new DataForSEO request, giving users opportunity to top up before service interruption.

### What happens when a user's credit balance reaches zero?

When both `usage_credits` and `topup_credits` are depleted, OpenSEO blocks the DataForSEO request and prompts the user to purchase additional credits or upgrade their subscription. The frontend redirects to a Stripe checkout session configured with `AUTUMN_CHECKOUT_SESSION_PARAMS`, accepting payment for either the `base-plan` subscription or `credit-top-up` one-time purchases.