How Billing is Managed in OpenSEO Using Autumn: A Complete Technical Guide

OpenSEO uses a thin wrapper around the autumn-js SDK to check entitlements, track usage events, and enforce quota limits via Autumn's billing platform, with automatic webhook synchronization and self-hosted fallback support.

The OpenSEO repository (every-app/open-seo) implements a robust billing layer that leverages Autumn as its central quota-management platform. By wrapping the official autumn-js SDK in a lazy-loaded client at src/server/billing/autumn.ts, the application enforces usage limits, tracks consumption, and maintains customer state without coupling business logic to the underlying billing provider.

Architecture of the Autumn Integration

The SDK Wrapper Pattern

OpenSEO does not import the Autumn SDK directly throughout the codebase. Instead, it uses a thin wrapper module (src/server/billing/autumn.ts) that lazily initializes the client and exposes only the four operations the application requires:

  • autumn.check – Wraps client.check() to verify entitlements and remaining balances
  • autumn.track – Wraps client.track() to record usage events
  • autumn.getOrCreate – Wraps client.customers.getOrCreate() to initialize customer records
  • autumn.get – Wraps client.customers.get() to fetch existing customer data

This abstraction ensures that the Org ID (used as the Autumn customer ID) is handled consistently across all billing operations.

Customer Lifecycle and Quota Enforcement

Ensuring Customer Existence

For every authenticated request, OpenSEO runs a billing-ensure middleware step that guarantees an Autumn customer exists. The ensureAutumnCustomer function in src/server/billing/subscription.ts invokes autumn.getOrCreate using the organization's ID as the customer identifier. This creates a deterministic mapping between OpenSEO organizations and Autumn billing entities.

Checking Monthly Balances

The subscription module (src/server/billing/subscription.ts) enforces quota limits by calling autumn.check with the feature ID AUTUMN_SEO_DATA_BALANCE_FEATURE_ID. This retrieves the current monthly balance for the organization.

The implementation normalizes edge cases gracefully:

  • Missing balances or negative values are treated as zero
  • 404 responses (customer not yet created) are handled without throwing
  • Results are cached for a short period to prevent repeated round-trips

If the balance is zero, the system throws a billing error code (BILLING_EXCEEDED) which bubbles to the UI, preventing further API calls that would incur costs.

import { ensureAutumnCustomer } from "@/server/billing/subscription";

const billingCtx = await ensureAutumnCustomer(orgId);
// billingCtx.balance contains the remaining quota (in cents)

Usage Tracking and Event Recording

All billable operations—such as rank checks, site audits, and DataForSEO requests—record consumption via autumn.track. The wrapper configures AUTUMN_TRACK_RETRY_OPTIONS with exponential backoff to survive transient network failures.

When the track call receives a 429 (rate-limited) response, the retry logic backs off before attempting again. This ensures that quotas are only decremented once the request finally succeeds, preventing under-counting during high-traffic periods.

await autumn.track(
  {
    event: "rank_check",
    customer_id: billing.customerId,
    properties: { keywordsChecked: result.keywordsChecked },
  },
  AUTUMN_TRACK_RETRY_OPTIONS
);

Real-Time Synchronization via Webhooks

To maintain consistency with external subscription changes, OpenSEO listens to Autumn webhooks through src/server/billing/autumn-webhook.ts. When the webhook receives a billing.updated event, it triggers syncAutumnCustomerStatus to re-read the latest customer record and update the internal cache. This ensures that plan upgrades, downgrades, or administrative adjustments reflect immediately without waiting for cache expiration.

import { handleAutumnWebhookRequest } from "@/server/billing/autumn-webhook";

export async function POST(req: Request) {
  return handleAutumnWebhookRequest(req);
}

Self-Hosted Deployment Support

For self-hosted deployments, OpenSEO disables the billing gate entirely. The code detects the "self-hosted" environment flag and skips all Autumn checks, treating every organization as having an infinite balance. This architectural decision allows the same codebase to operate as a metered SaaS platform (with Autumn enforcing quotas) or as an unrestricted private installation.

End-to-End Implementation Example

The following pattern demonstrates the complete billing flow for a protected endpoint:

export async function rankCheckHandler(req: Request) {
  // 1. Verify quota exists
  const { billing } = await ensureAutumnCustomer(req.orgId);
  if (billing.balance <= 0) {
    throw new Error("Billing quota exhausted"); // UI shows billing screen
  }

  // 2. Perform the operation
  const result = await runRankCheck(...);

  // 3. Record consumption
  await autumn.track(
    {
      event: "rank_check",
      customer_id: billing.customerId,
      properties: { keywordsChecked: result.keywordsChecked },
    },
    AUTUMN_TRACK_RETRY_OPTIONS
  );

  return result;
}

Summary

  • Autumn SDK Wrapper: Located at src/server/billing/autumn.ts, it exposes check, track, getOrCreate, and get methods with lazy initialization.
  • Quota Enforcement: The ensureAutumnCustomer function in src/server/billing/subscription.ts checks AUTUMN_SEO_DATA_BALANCE_FEATURE_ID and throws BILLING_EXCEEDED when limits are reached.
  • Reliable Tracking: Usage events use autumn.track with AUTUMN_TRACK_RETRY_OPTIONS for exponential backoff and 429 handling.
  • Webhook Sync: src/server/billing/autumn-webhook.ts processes billing events to keep cached customer data fresh.
  • Deployment Flexibility: Self-hosted mode bypasses all billing checks, enabling unlimited usage without code changes.

Frequently Asked Questions

What is Autumn in the context of OpenSEO?

Autumn is the third-party billing and quota-management platform that OpenSEO uses to enforce usage limits, track consumption events, and manage customer subscription states. According to the OpenSEO source code, it serves as the central authority for determining whether an organization has sufficient balance to perform billable operations like rank checks and site audits.

How does OpenSEO handle billing quota exceeded errors?

When ensureAutumnCustomer detects a zero or negative balance via autumn.check, it returns a BILLING_EXCEEDED error code. This error bubbles up through the API layer to the frontend, which displays a billing screen to the user, preventing any further API calls that would incur additional costs until the quota is replenished.

Can OpenSEO run without connecting to Autumn?

Yes. In self-hosted deployments, OpenSEO detects the self-hosted configuration flag and bypasses all Autumn SDK calls entirely. The system treats every organization as having infinite quota, allowing the application to function without any external billing provider connection while using the same codebase.

How does usage tracking handle network failures or rate limiting?

The autumn.track wrapper implements retry logic through AUTUMN_TRACK_RETRY_OPTIONS, which configures exponential backoff. If Autumn returns a 429 (rate-limited) status, the client waits and retries the request. This ensures that usage is only recorded once the request succeeds, maintaining accurate quota accounting even during network instability.

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 →