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– Wrapsclient.check()to verify entitlements and remaining balancesautumn.track– Wrapsclient.track()to record usage eventsautumn.getOrCreate– Wrapsclient.customers.getOrCreate()to initialize customer recordsautumn.get– Wrapsclient.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 exposescheck,track,getOrCreate, andgetmethods with lazy initialization. - Quota Enforcement: The
ensureAutumnCustomerfunction insrc/server/billing/subscription.tschecksAUTUMN_SEO_DATA_BALANCE_FEATURE_IDand throwsBILLING_EXCEEDEDwhen limits are reached. - Reliable Tracking: Usage events use
autumn.trackwithAUTUMN_TRACK_RETRY_OPTIONSfor exponential backoff and 429 handling. - Webhook Sync:
src/server/billing/autumn-webhook.tsprocesses 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →