# How the Billing Subscription System Works with Autumn for Credit Management

> Discover how the Open-SEO billing subscription system uses Autumn's API to manage credits and provision customers for seamless access to third-party data services.

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

---

**The Open‑SEO platform uses Autumn’s feature‑check API to provision customers, validate paid subscription tiers, and enforce dual credit balances (monthly allotments plus top‑ups) that gate access to third‑party data services.**

The `every-app/open-seo` repository implements a robust billing layer that synchronizes subscription state between Stripe and Autumn while maintaining low‑latency access controls through Cloudflare KV caching. By treating credits as **feature entitlements** checked in real time, the system prevents unauthorized usage of paid APIs such as DataForSEO and LLM providers.

## Customer Creation and Caching Strategy

When an organization first attempts to access a paid feature, the platform invokes `getOrCreateOrganizationCustomer` defined in [`src/server/billing/subscription.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/subscription.ts) (lines 34‑64). This function implements a two‑tier lookup to minimize external API calls:

1. **Cache check:** It first queries Cloudflare KV for the key `autumn:customer-ensured:<orgId>`. A hit returns the cached customer ID immediately.
2. **Autumn provisioning:** On cache miss, it calls `autumn.customers.getOrCreate` using the organization ID as the unique key, then stores the result in KV with a 24‑hour TTL.

This pattern ensures that high‑traffic operations—such as verifying plan status or deducting credits—do not trigger redundant network requests to Autumn’s servers.

## Verifying Subscription Plans with Autumn

The system distinguishes between two subscription tiers by checking specific **feature IDs** stored in Autumn. Both checks use the `autumn.check` method and are implemented in [`src/server/billing/subscription.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/subscription.ts) (lines 66‑82):

- **`customerHasPaidPlan`** queries the feature ID defined by `AUTUMN_PAID_PLAN_FEATURE_ID` (`"paid_plan"`). The boolean `allowed` field in the response determines whether the organization holds an active paid subscription.
- **`customerHasManagedAccess`** queries `AUTUMN_MANAGED_ACCESS_FEATURE_ID` (`"managed_service_access"`) to verify eligibility for the managed‑service tier, which may include additional support or higher rate limits.

These checks run server‑side before executing any billable operation, ensuring that free‑tier organizations cannot trigger costly API calls.

## Credit Balance Management

Autumn tracks usage credits as **entitlement features** rather than simple counters. The platform maintains two distinct balance types defined in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts) (lines 13‑15):

- **`"usage_credits"`** (`AUTUMN_SEO_DATA_BALANCE_FEATURE_ID`): The monthly recurring credit pool included with the subscription.
- **`"topup_credits"`** (`AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID`): An optional pay‑as‑you‑go balance purchased separately.

### Fetching Remaining Credits

The function `getUsageCreditsRemaining` (lines 84‑114 in [`subscription.ts`](https://github.com/every-app/open-seo/blob/main/subscription.ts)) executes parallel `autumn.check` calls to read both balances. If the monthly balance is missing—indicating a data inconsistency—the function throws an error because every organization should inherit a default free‑tier allocation. The function returns exact remaining amounts for both pools, allowing the UI to display granular usage statistics.

### Deducting Credits After Usage

When a DataForSEO or LLM API call completes successfully, the platform records the cost via `autumn.track`. The deduction logic (located in [`src/server/billing/autumn.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/autumn.ts), lines 55‑56) applies a conversion formula:

```typescript
const creditsToDeduct = usdCost * AUTUMN_SEO_DATA_CREDITS_PER_USD * SEO_DATA_COST_MARKUP;

```

Where `AUTUMN_SEO_DATA_CREDITS_PER_USD` equals **1000** and `SEO_DATA_COST_MARKUP` equals **1.28**. The `track` call includes `AUTUMN_TRACK_RETRY_OPTIONS` to guarantee eventual consistency even during transient network failures.

## Frontend Integration and Stripe Synchronization

The client application reads the cached Autumn customer object to render subscription status and remaining credits without blocking on external API latency. When a user upgrades via Stripe, the webhook triggers an asynchronous update in Autumn; the frontend polls the [`/routes/_authenticated.subscribe.tsx`](https://github.com/every-app/open-seo/blob/main//routes/_authenticated.subscribe.tsx) endpoint (lines 60‑79) until the `autumn.check` responses reflect the new plan tier, ensuring immediate UI consistency after payment.

## Practical Implementation Example

The following TypeScript snippet demonstrates the complete lifecycle of a billable operation:

```typescript
// 1️⃣ Ensure an Autumn customer exists (creates if needed)
const { id: autumnCustomerId } = await getOrCreateOrganizationCustomer({
  organizationId: org.id,
  userEmail: user.email,
  userId: user.id,
});

// 2️⃣ Verify the org has a paid plan before calling a paid API
if (await customerHasPaidPlan(autumnCustomerId)) {
  // OK – proceed with the operation
}

// 3️⃣ Read the remaining usage credits
const { monthlyRemaining, topupRemaining } =
  await getUsageCreditsRemaining(autumnCustomerId);

// 4️⃣ After a successful DataForSEO request, deduct credits
await autumn.track(
  {
    customerId: autumnCustomerId,
    event: "seo_data_usage",
    properties: { amount: usdSpent },
  },
  AUTUMN_TRACK_RETRY_OPTIONS,
);

```

## Summary

- **Autumn acts as the single source of truth** for subscription entitlements and credit balances, checked via `autumn.check` and updated via `autumn.track`.
- **Cloudflare KV caching** eliminates redundant customer creation calls by storing the `autumn:customer-ensured:<orgId>` key for 24 hours.
- **Dual credit pools** (monthly `"usage_credits"` and `"topup_credits"`) provide flexible billing, with strict validation ensuring the monthly balance always exists.
- **Cost markup and conversion** happen at the point of tracking, applying a 1.28× multiplier to raw USD costs converted at 1000 credits per dollar.
- **Retry logic** wraps non‑idempotent `track` calls to prevent data loss during network interruptions.

## Frequently Asked Questions

### How does Open‑SEO prevent unnecessary API calls to Autumn?

The platform caches the Autumn customer ID in Cloudflare KV under the key `autumn:customer-ensured:<orgId>` for 24 hours. The `getOrCreateOrganizationCustomer` function checks this cache before invoking `autumn.customers.getOrCreate`, ensuring that high‑frequency operations like credit checks do not hit Autumn’s rate limits.

### What happens if an organization exhausts its usage credits?

When `getUsageCreditsRemaining` detects zero available credits in both the monthly (`"usage_credits"`) and top‑up (`"topup_credits"`) pools, the application layer blocks the operation before issuing the costly third‑party API call. The user receives an error indicating insufficient balance, and the request is never executed.

### How are credit costs calculated for DataForSEO and LLM usage?

The system calculates credits using the formula `usdCost × 1000 × 1.28`, where `1000` is the `AUTUMN_SEO_DATA_CREDITS_PER_USD` constant and `1.28` represents the `SEO_DATA_COST_MARKUP`. This value is then passed to `autumn.track` with `AUTUMN_TRACK_RETRY_OPTIONS` to record the deduction.

### How does the frontend synchronize subscription changes after a Stripe payment?

After a successful Stripe checkout, the frontend polls the subscription status endpoint (defined in [`_authenticated.subscribe.tsx`](https://github.com/every-app/open-seo/blob/main/_authenticated.subscribe.tsx)) until `customerHasPaidPlan` returns `true`. This polling bridges the eventual consistency gap between Stripe’s webhook delivery and Autumn’s feature‑check API, updating the UI immediately once the subscription is active.