How OpenSEO Handles Billing and Credits for API Calls: A Complete Technical Guide
OpenSEO uses a dual-pool credit system powered by the Autumn subscription service, where organizations spend from monthly usage credits first, then top-up credits, with every API call tracked through trackUsageCreditSpend in src/server/billing/subscription.ts.
OpenSEO's billing infrastructure is tightly integrated with its API consumption model. Whether you're running DataForSEO requests or LLM-powered onboarding calls, every paid operation draws from a shared usage-credit pool. This guide explains exactly how the system manages customer accounts, feature flags, credit balances, and spend tracking based on the every-app/open-seo source code.
Autumn Customer Representation
Every organization using OpenSEO is represented as an Autumn customer. The organization ID serves directly as the Autumn customerId, creating a 1:1 mapping between your OpenSEO org and the billing system.
The helper getOrCreateOrganizationCustomer in src/server/billing/subscription.ts handles this lazily—creating the customer on first use and caching the existence flag for 24 hours to minimize API round-trips:
import { getOrCreateOrganizationCustomer } from "@/server/billing/subscription";
const { id: customerId } = await getOrCreateOrganizationCustomer({
organizationId: orgId,
userEmail: user.email,
userId: user.id,
});
// Customer created only if not exists; cached for 24h
This pattern ensures that billing operations never fail due to missing customer records while keeping latency low for repeated calls.
Billing Feature Flags and Constants
All billing-related identifiers are centralized in src/shared/billing.ts. These constants define which Autumn features control access and credit pools:
| Constant | Purpose |
|---|---|
AUTUMN_PAID_PLAN_FEATURE_ID |
Indicates active paid subscription |
AUTUMM_MANAGED_ACCESS_FEATURE_ID |
Grants managed service access |
AUTUMN_SEO_DATA_BALANCE_FEATURE_ID |
Monthly usage credits pool |
AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID |
Purchased top-up credits pool |
The file also defines the credit economics:
// src/shared/billing.ts
export const AUTUMN_SEO_DATA_CREDITS_PER_USD = 1000; // 1000 credits = $1
export const SEO_DATA_COST_MARKUP = 1.28; // 28% platform markup
The markup is applied to raw provider costs (like DataForSEO) before credit conversion, ensuring platform sustainability.
Credit Pool Architecture
OpenSEO maintains two distinct credit pools per organization:
- Monthly balance (
usage_credits) – Refills automatically each billing cycle; primary spending source - Top-up balance (
topup_credits) – Purchased manually; acts as overflow when monthly credits deplete
This dual-pool design provides predictable baseline usage with flexible overflow protection.
Checking Credit Availability
Before any paid API call, the system verifies sufficient credits exist. The checkUsageCreditsDepleted function (lines 58–92 in src/server/billing/subscription.ts) implements defensive logic:
// Simplified flow from source
const check = await autumn.check({
customerId: orgId,
featureId: AUTUMN_SEO_DATA_BALANCE_FEATURE_ID,
});
const monthly = check?.qty || 0;
const topupCheck = await autumn.check({
customerId: orgId,
featureId: AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID,
});
const topup = topupCheck?.qty || 0;
if (monthly + topup <= 0) {
// Double-check with full customer read to guard against stale cache
const fullCustomer = await autumn.customers.get(orgId);
// ...verify depletion and emit usage:credits_gate_refused event
}
If the initial read shows zero or negative balance, a second full-customer read guards against transient read errors before blocking the operation.
For simpler cases, assertUsageCreditsAvailable (lines 10–21) provides a throwing wrapper:
import { assertUsageCreditsAvailable } from "@/server/billing/subscription";
await assertUsageCreditsAvailable(orgId);
// Throws INSUFFICIENT_CREDITS if both pools are empty
Recording API Spend with trackUsageCreditSpend
When an API call completes, trackUsageCreditSpend handles the actual credit deduction. This is the core billing integration point called throughout the codebase.
The Spend Tracking Flow
- Apply markup –
applyBillingMarkupUsdmultiplies raw cost bySEO_DATA_COST_MARKUP - Convert to credits – USD amount ×
AUTUMN_SEO_DATA_CREDITS_PER_USD - Deduct from monthly first – Primary pool depletion
- Deduct remainder from top-up – Only if monthly exhausted
- Emit tracking events – PostHog
usage:credits_consumefor analytics
import { trackUsageCreditSpend } from "@/server/billing/subscription";
import { AUTUMN_SEO_DATA_BALANCE_FEATURE_ID } from "@/shared/billing";
await trackUsageCreditSpend({
customer: {
organizationId: orgId,
userId: user.id,
userEmail: user.email
},
customerId: orgId,
creditFeature: AUTUMN_SEO_DATA_BALANCE_FEATURE_ID,
costUsd: rawProviderCost, // e.g., $0.05 from DataForSEO
monthlyRemaining: monthlyBalance, // From prior checkUsageCreditsDepleted
properties: {
requestId: "abc-123",
endpoint: "serp/google/organic"
},
});
The function automatically handles split deductions—charging monthly credits first, then top-up credits—making multiple autumn.track calls as needed.
Real-World Integration Points
DataForSEO Client
The DataForSEO integration in src/server/lib/dataforseo/client.ts (lines 8–12, 210–218) demonstrates production usage. After receiving API response costs, it:
- Receives
BillingCustomerContextcontaining org/user info - Calls
trackUsageCreditSpendwith actual provider charges - Passes through request metadata for audit trails
Workflow Integrations
Site audit and rank-check workflows pass billing context through their execution phases:
src/server/workflows/siteAuditWorkflowPhases.ts– Gates audit runs onassertUsageCreditsAvailablesrc/server/workflows/RankCheckWorkflow.ts– Deducts credits per keyword checked
Both receive the same BillingCustomerContext shape, ensuring consistent attribution across all API-consuming operations.
Summary
- Autumn integration: Organizations map 1:1 to Autumn customers via
getOrCreateOrganizationCustomerwith 24-hour caching - Dual credit pools: Monthly usage credits spend first; top-up credits provide overflow protection
- Defensive checking:
checkUsageCreditsDepleteddouble-reads on depletion to prevent false blocks - Markup and conversion: 28% platform markup applied before converting USD to credits at 1000:1 ratio
- Centralized spend tracking:
trackUsageCreditSpendhandles all deductions, split spending, and analytics emission
Frequently Asked Questions
How does OpenSEO prevent API calls when credits run out?
The assertUsageCreditsAvailable function throws INSUFFICIENT_CREDITS before any paid operation begins. For additional safety, checkUsageCreditsDepleted performs a second full-customer read if the initial balance check shows zero credits, protecting against stale cache reads that might incorrectly allow spending.
What happens when monthly credits are exhausted but top-up credits exist?
trackUsageCreditSpend automatically deducts from the monthly balance first, then charges the remainder against top-up credits. Both deductions trigger separate autumn.track calls with their respective feature IDs (AUTUMN_SEO_DATA_BALANCE_FEATURE_ID and AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID).
Where is the billing markup configured?
The 28% platform markup is defined as SEO_DATA_COST_MARKUP = 1.28 in src/shared/billing.ts. This constant is applied via applyBillingMarkupUsd before credit conversion, ensuring consistent pricing across all API providers.
How is billing context passed through workflows?
All server-side API consumers receive a BillingCustomerContext object containing organizationId, userId, and userEmail. This context flows from workflow triggers through to trackUsageCreditSpend calls, enabling per-organization accounting with full user attribution for analytics.
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 →