How the Billing Subscription System Works with Autumn for Credit Management

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 (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 (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 (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) 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, lines 55‑56) applies a conversion formula:

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 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:

// 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) 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →