# How Autumn Billing Enforces Per-Organization Credit Balances in Open-SEO

> Learn how Autumn billing enforces per-organization credit balances in Open-SEO. Discover its unique approach to credit management and usage control for enhanced security.

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

---

**Autumn billing enforces per-organization credit balances by mapping each organization to a distinct Autumn customer and storing monthly and top-up credits as separate feature balances, then atomically deducting credits through a gating pipeline that verifies balances before allowing usage-consuming operations.**

The *open-seo* repository implements a robust billing system using Autumn to manage **per-organization credit balances** independently. Every organization operates as an isolated billing entity with separate credit pools for monthly allocations and rolled-over top-ups. The enforcement mechanism relies on distinct Autumn feature IDs and a strict verification pipeline defined in the billing modules.

## Credit Balance Architecture

Autumn stores **per-organization credit balances** as two distinct feature IDs defined in **[src/shared/billing.ts]**:

- **`usage_credits`** (`AUTUMN_SEO_DATA_BALANCE_FEATURE_ID`): Represents monthly shared usage credits allocated to the organization
- **`topup_credits`** (`AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID`): Represents rolled-over top-up credits purchased separately

This dual-balance approach allows the system to prioritize monthly credits before consuming top-up reserves. Each organization ID maps directly to an Autumn `customerId`, ensuring complete isolation between organizations' financial data.

## The Enforcement Pipeline

The billing system implements a four-stage enforcement pipeline within **[src/server/billing/subscription.ts]** to gate credit-consuming operations like DataForSEO lookups and LLM queries.

### Step 1: Customer Provisioning

Before any balance checks occur, the system ensures the organization exists as an Autumn customer using `getOrCreateOrganizationCustomer` (lines 34-64). This function lazily creates the customer record and caches the existence check using a `customerEnsuredKey` to eliminate redundant round-trips on subsequent calls.

```typescript
const { id: customerId } = await getOrCreateOrganizationCustomer({
  organizationId: orgId,
  userEmail: user.email,
  userId: user.id,
});

```

### Step 2: Balance Verification with Double-Read Protection

The system retrieves current balances through `getUsageCreditsRemaining` (lines 98-44), which executes parallel `autumn.check` calls for both feature IDs. If the monthly balance read fails transiently, the system retries once, treating missing monthly balances as internal errors since every hosted organization must maintain this feature.

To prevent stale read errors from locking out paying customers, `checkUsageCreditsDepleted` (lines 58-93) implements a **double-read pattern**. The function first sums the remaining credits; if the sum is ≤ 0, it performs a second authoritative read via `autumn.customers.getOrCreate` before confirming depletion. The `assertUsageCreditsAvailable` wrapper (lines 106-22) throws `INSUFFICIENT_CREDITS` when both balances exhaust.

```typescript
const { depleted } = await checkUsageCreditsDepleted({
  organizationId: orgId,
  userEmail: user.email,
  userId: user.id,
});
if (depleted) {
  throw new Error("Your credit balance is exhausted.");
}

```

### Step 3: Atomic Credit Deduction

Upon successful usage, `trackUsageCreditSpend` (lines 23-104) deducts credits atomically using `autumn.track`. The function converts USD costs to credits using `AUTUMN_SEO_DATA_CREDITS_PER_USD`, applies the platform markup (`SEO_DATA_COST_MARKUP`), and rounds via `roundUsdForBilling`.

The deduction logic prioritizes monthly credits:
1. Deduct from monthly credits up to the current remaining balance (clamped to 0)
2. Apply remainder to top-up credits

Two separate `autumn.track` calls execute the deductions, utilizing retry options defined in **[src/server/billing/autumn.ts]**. After tracking, the system emits a `usage:credits_consume` PostHog event for analytics.

```typescript
await trackUsageCreditSpend({
  customer: { organizationId: orgId, userEmail: user.email, userId: user.id },
  customerId,
  creditFeature: "seo_data_usage",
  costUsd: rawDataforseoCost,
  monthlyRemaining: monthlyCreditsLeft,
});

```

### Error Handling and Observability

When balance reads return unexpected states, the system logs `billing.credits-gate disagreement` errors and proceeds with the authoritative customer object. All denial events route through `captureServerEvent` for operational observability (lines 80-90).

## Key Implementation Files

The billing system spans several critical files that maintain **per-organization credit balances**:

| File | Role |
|------|------|
| **[src/shared/billing.ts]** | Defines `AUTUMN_SEO_DATA_BALANCE_FEATURE_ID`, `AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID`, and conversion constants |
| **[src/server/billing/autumn.ts]** | Lazy-loads the `autumn-js` SDK and provides the façade interface (`check`, `track`, `customers.getOrCreate`) |
| **[src/server/billing/subscription.ts]** | Core logic for customer creation, balance gating, and credit tracking (lines 23-104, 106-22) |
| **[src/server/billing/subscription.test.ts]** | Unit tests mocking Autumn interactions to verify balance enforcement |
| **[src/server/workflows/RankCheckWorkflow.ts]** | Production workflow example implementing credit gates before rank checks |

## Summary

- **Autumn billing** maintains isolated credit pools for each organization by mapping organization IDs to unique Autumn customer records.
- Two distinct feature balances—`usage_credits` and `topup_credits`—store monthly and rolled-over credits respectively in **[src/shared/billing.ts]**.
- The enforcement pipeline uses `getOrCreateOrganizationCustomer` to provision customers lazily while caching the result.
- **Double-read protection** in `checkUsageCreditsDepleted` prevents false depletion errors by verifying negative balances against the authoritative customer object.
- **Atomic deductions** via `trackUsageCreditSpend` prioritize monthly credits before consuming top-up balances, applying platform markup and USD-to-credit conversions.
- All credit consumption routes through **[src/server/billing/subscription.ts]** with comprehensive error handling and PostHog analytics integration.

## Frequently Asked Questions

### How does the system prevent race conditions when deducting credits?

The system relies on Autumn's atomic `track` operations through the `autumn.track` API calls in `trackUsageCreditSpend`. While the application calculates the split between monthly and top-up credits locally, each deduction executes as a separate atomic operation against Autumn's backend, ensuring that concurrent requests for the same organization resolve correctly according to Autumn's internal consistency guarantees.

### What happens if the monthly credit balance read fails?

The `getUsageCreditsRemaining` function implements a single retry mechanism for transient read errors. If the monthly balance remains unreadable after retry, the system treats this as an internal error rather than assuming zero balance, because every hosted organization must maintain the `usage_credits` feature. The error propagates up to prevent the request from proceeding on stale data.

### Why are credits deducted from monthly balances before top-up balances?

The deduction priority ensures that organizations consume their time-bound monthly allocations before depleting permanently stored top-up credits. This expiration-style logic encourages usage of monthly recurring credits while preserving purchased top-up reserves for future billing cycles. The `trackUsageCreditSpend` function explicitly clamps monthly deductions to the current remaining balance before routing overflow to top-up credits.

### Where is the USD-to-credits conversion rate defined?

The conversion logic resides in **[src/shared/billing.ts]** via the constant `AUTUMN_SEO_DATA_CREDITS_PER_USD`. The `trackUsageCreditSpend` function applies this rate after adding the platform markup percentage (`SEO_DATA_COST_MARKUP`) and rounding the USD value, ensuring consistent credit valuation across all organizations in the Open-SEO platform.