# How the Autumn Billing Credits System Integrates with SEO Data Costs in OpenSEO

> Learn how OpenSEO integrates Autumn billing credits with SEO data costs using a deterministic pipeline for efficient provider cost management. Discover credit calculation and balance deduction.

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

---

**OpenSEO uses the Autumn platform to manage a shared pool of usage credits that pay for all SEO-related provider costs through a deterministic pipeline of markup conversion, credit calculation, and balance deduction.**

The Autumn billing credits system in OpenSEO provides a unified mechanism for tracking and charging against third-party SEO data costs, such as DataForSEO API calls. By implementing a credit-based abstraction layer, the platform decouples raw provider pricing from customer billing while maintaining transparent cost accounting. This integration ensures that every dollar spent on SEO data flows through a consistent conversion and markup process before deducting from user balances.

## Credit Conversion and Markup Constants

The foundation of the integration rests on two critical constants defined in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts) that establish the exchange rate and operational margin.

### USD-to-Credit Conversion Rate

The constant `AUTUMN_SEO_DATA_CREDITS_PER_USD` defines the baseline exchange rate where **1 USD equals 1,000 credits**. The helper function `autumnSeoDataCreditsToUsd` performs the inverse calculation by dividing the credit count by this factor to display monetary values:

```typescript
// Located in src/shared/billing.ts
export const AUTUMN_SEO_DATA_CREDITS_PER_USD = 1000;

export function autumnSeoDataCreditsToUsd(credits: number): number {
  return credits / AUTUMN_SEO_DATA_CREDITS_PER_USD;
}

```

This conversion rate ensures granular billing for micro-transactions while keeping the mental model simple for users.

### Platform Markup Application

OpenSEO applies a platform markup to cover operational overhead before converting costs to credits. The constant `SEO_DATA_COST_MARKUP` is set to `1.28`, representing a 28% markup on raw provider costs:

```typescript
// Located in src/shared/billing.ts
export const SEO_DATA_COST_MARKUP = 1.28;

```

This markup is applied in the billing workflow to ensure sustainable pricing while remaining transparent about raw data costs.

## The Billing Workflow: From Provider Cost to Credit Deduction

The core integration logic resides in [`src/server/billing/subscription.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/subscription.ts), specifically within the `trackUsageCreditSpend` function. This orchestrates the entire flow from raw API cost to final credit deduction.

### Applying Operational Markup

When a customer uses SEO data features, the system first applies the markup to the raw USD cost. If DataForSEO charges $2.50 for a SERP request, the platform calculates the marked-up amount before credit conversion:

```typescript
// Simplified logic from trackUsageCreditSpend in src/server/billing/subscription.ts
const markedUpCost = rawUsd * SEO_DATA_COST_MARKUP; // $2.50 * 1.28 = $3.20

```

### Converting to Credits and Rounding

After markup, the USD amount converts to credits using the exchange rate. The system rounds up to the nearest integer to prevent fractional credit tracking:

```typescript
// From src/server/billing/subscription.ts (approximate line 38)
const creditsToCharge = Math.ceil(markedUpCost * AUTUMN_SEO_DATA_CREDITS_PER_USD);
// $3.20 * 1000 = 3200 credits

```

This ensures deterministic billing where partial cents always round up to the next whole credit.

### Deducting from Monthly and Top-Up Balances

The Autumn billing credits system follows a strict deduction priority to preserve user benefits. Credits deduct first from the monthly `usage_credits` balance, then from any rolled-over `topup_credits`:

1. **Primary Balance**: `AUTUMN_SEO_DATA_BALANCE_FEATURE_ID` (monthly allotment)
2. **Secondary Balance**: `AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID` (purchased credits)

This tiered approach ensures that free-plan monthly allowances exhaust before touching purchased top-ups. The `trackUsageCreditSpend` function handles this dual-balance logic internally, checking the `monthlyRemaining` parameter passed from prior balance checks.

## Lazy-Loaded Autumn Client Architecture

To minimize cold-start impact in serverless environments, OpenSEO implements a lazy-loaded façade for the Autumn SDK in [`src/server/billing/autumn.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/autumn.ts). The `autumn` object proxies calls to `.check`, `.track`, and customer helpers only when invoked:

```typescript
// Located in src/server/billing/autumn.ts (line 38)
export const autumn = new Proxy({} as AutumnClient, {
  get: (_target, prop) => {
    const client = getAutumnClient(); // Lazy initialization
    return client[prop as keyof AutumnClient];
  },
});

```

This pattern keeps the worker bundle size small while exposing a synchronous-looking API such as `autumn.check(customerId, features)`.

## Balance Verification and Audit Trails

Before deducting credits, the system verifies available balances through `getUsageCreditsRemaining` in [`src/server/billing/subscription.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/subscription.ts). This function reads both the monthly and top-up balances in parallel via `autumn.check`, implements a single retry for missing monthly balances, and throws an error if the monthly balance remains absent. This prevents false-zero scenarios where the system might incorrectly report unlimited credits.

Every credit spend generates an audit trail through `captureServerEvent` (PostHog) as defined in [`src/server/lib/posthog.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/posthog.ts). The event payload includes the raw USD cost, markup multiplier, final credit amount, and provider metadata, enabling precise usage analytics and cost attribution.

## Practical Implementation Examples

### Charging a DataForSEO Request

To charge against SEO data usage, first ensure the customer exists in Autumn, check remaining credits, then record the spend:

```typescript
import { trackUsageCreditSpend, getUsageCreditsRemaining, getOrCreateOrganizationCustomer } from "@/server/billing/subscription";

async function chargeDataForSeo(customerCtx: BillingCustomerContext, rawUsd: number) {
  // Ensure customer exists in Autumn
  const { id: customerId } = await getOrCreateOrganizationCustomer(customerCtx);
  
  // Check monthly credits remaining
  const { monthlyRemaining } = await getUsageCreditsRemaining(customerId);
  
  // Record the spend with markup and provider metadata
  await trackUsageCreditSpend({
    customer: customerCtx,
    customerId,
    creditFeature: "seo_data_usage", // Defined in shared/billing-credit-features
    costUsd: rawUsd,
    monthlyRemaining,
    properties: { provider: "dataforseo", endpoint: "/v3/serp" },
  });
}

```

### Displaying Credit Balance in USD

For UI components displaying remaining credit values, convert credits back to USD using the shared helper:

```typescript
import { autumnSeoDataCreditsToUsd } from "@/shared/billing";

function displayRemainingCredits(credits: number): string {
  const usd = autumnSeoDataCreditsToUsd(credits);
  return `You have $${usd.toFixed(2)} of SEO data credits remaining.`;
}

```

## Summary

- **Fixed Exchange Rate**: The Autumn billing credits system uses a constant rate of 1,000 credits per USD, defined in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts).
- **Operational Markup**: A 28% markup (`SEO_DATA_COST_MARKUP = 1.28`) covers platform costs before credit conversion.
- **Tiered Deduction**: Credits deduct first from monthly allowances (`AUTUMN_SEO_DATA_BALANCE_FEATURE_ID`), then from top-up balances (`AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID`).
- **Lazy Initialization**: The Autumn SDK loads on-demand via a Proxy façade in [`src/server/billing/autumn.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/autumn.ts) to optimize cold starts.
- **Audit Compliance**: Every transaction logs to PostHog via `captureServerEvent` in [`src/server/lib/posthog.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/posthog.ts) for complete usage analytics.

## Frequently Asked Questions

### How does OpenSEO calculate SEO data costs in credits?

OpenSEO applies a 28% markup to the raw provider cost, then multiplies the resulting USD amount by 1,000 and rounds up to the nearest integer. For example, a $2.50 DataForSEO request becomes $3.20 after markup, converting to 3,200 credits. This calculation occurs in `trackUsageCreditSpend` within [`src/server/billing/subscription.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/subscription.ts).

### What happens if a user exhausts their monthly credit allowance?

When monthly credits deplete, the Autumn billing credits system automatically draws from the user's top-up balance (`AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID`). The `trackUsageCreditSpend` function handles this fallback transparently, ensuring continuous service while preserving the monthly allotment for future billing cycles.

### Why does OpenSEO use a lazy-loaded façade for the Autumn SDK?

The Proxy-based façade in [`src/server/billing/autumn.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/autumn.ts) delays SDK initialization until first use, reducing cold-start latency in serverless functions. This approach keeps the worker bundle size minimal while exposing a synchronous API like `autumn.check()` and `autumn.track()`.

### How does the system prevent billing errors from missing balance data?

The `getUsageCreditsRemaining` function implements a retry mechanism for missing monthly balances and throws an explicit error if the balance remains unretrievable. This prevents the system from assuming zero credits or unlimited usage when Autumn's API returns transient failures.