How the Autumn Billing Credits System Integrates with SEO Data Costs in OpenSEO
OpenSEO uses the Autumn platform to manage a shared pool of usage credits that pay for all SEO-related provider costs through a deterministic pipeline of markup conversion, credit calculation, and balance deduction.
The Autumn billing credits system in OpenSEO provides a unified mechanism for tracking and charging against third-party SEO data costs, such as DataForSEO API calls. By implementing a credit-based abstraction layer, the platform decouples raw provider pricing from customer billing while maintaining transparent cost accounting. This integration ensures that every dollar spent on SEO data flows through a consistent conversion and markup process before deducting from user balances.
Credit Conversion and Markup Constants
The foundation of the integration rests on two critical constants defined in src/shared/billing.ts that establish the exchange rate and operational margin.
USD-to-Credit Conversion Rate
The constant AUTUMN_SEO_DATA_CREDITS_PER_USD defines the baseline exchange rate where 1 USD equals 1,000 credits. The helper function autumnSeoDataCreditsToUsd performs the inverse calculation by dividing the credit count by this factor to display monetary values:
// Located in src/shared/billing.ts
export const AUTUMN_SEO_DATA_CREDITS_PER_USD = 1000;
export function autumnSeoDataCreditsToUsd(credits: number): number {
return credits / AUTUMN_SEO_DATA_CREDITS_PER_USD;
}
This conversion rate ensures granular billing for micro-transactions while keeping the mental model simple for users.
Platform Markup Application
OpenSEO applies a platform markup to cover operational overhead before converting costs to credits. The constant SEO_DATA_COST_MARKUP is set to 1.28, representing a 28% markup on raw provider costs:
// Located in src/shared/billing.ts
export const SEO_DATA_COST_MARKUP = 1.28;
This markup is applied in the billing workflow to ensure sustainable pricing while remaining transparent about raw data costs.
The Billing Workflow: From Provider Cost to Credit Deduction
The core integration logic resides in src/server/billing/subscription.ts, specifically within the trackUsageCreditSpend function. This orchestrates the entire flow from raw API cost to final credit deduction.
Applying Operational Markup
When a customer uses SEO data features, the system first applies the markup to the raw USD cost. If DataForSEO charges $2.50 for a SERP request, the platform calculates the marked-up amount before credit conversion:
// Simplified logic from trackUsageCreditSpend in src/server/billing/subscription.ts
const markedUpCost = rawUsd * SEO_DATA_COST_MARKUP; // $2.50 * 1.28 = $3.20
Converting to Credits and Rounding
After markup, the USD amount converts to credits using the exchange rate. The system rounds up to the nearest integer to prevent fractional credit tracking:
// From src/server/billing/subscription.ts (approximate line 38)
const creditsToCharge = Math.ceil(markedUpCost * AUTUMN_SEO_DATA_CREDITS_PER_USD);
// $3.20 * 1000 = 3200 credits
This ensures deterministic billing where partial cents always round up to the next whole credit.
Deducting from Monthly and Top-Up Balances
The Autumn billing credits system follows a strict deduction priority to preserve user benefits. Credits deduct first from the monthly usage_credits balance, then from any rolled-over topup_credits:
- Primary Balance:
AUTUMN_SEO_DATA_BALANCE_FEATURE_ID(monthly allotment) - Secondary Balance:
AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID(purchased credits)
This tiered approach ensures that free-plan monthly allowances exhaust before touching purchased top-ups. The trackUsageCreditSpend function handles this dual-balance logic internally, checking the monthlyRemaining parameter passed from prior balance checks.
Lazy-Loaded Autumn Client Architecture
To minimize cold-start impact in serverless environments, OpenSEO implements a lazy-loaded façade for the Autumn SDK in src/server/billing/autumn.ts. The autumn object proxies calls to .check, .track, and customer helpers only when invoked:
// Located in src/server/billing/autumn.ts (line 38)
export const autumn = new Proxy({} as AutumnClient, {
get: (_target, prop) => {
const client = getAutumnClient(); // Lazy initialization
return client[prop as keyof AutumnClient];
},
});
This pattern keeps the worker bundle size small while exposing a synchronous-looking API such as autumn.check(customerId, features).
Balance Verification and Audit Trails
Before deducting credits, the system verifies available balances through getUsageCreditsRemaining in src/server/billing/subscription.ts. This function reads both the monthly and top-up balances in parallel via autumn.check, implements a single retry for missing monthly balances, and throws an error if the monthly balance remains absent. This prevents false-zero scenarios where the system might incorrectly report unlimited credits.
Every credit spend generates an audit trail through captureServerEvent (PostHog) as defined in src/server/lib/posthog.ts. The event payload includes the raw USD cost, markup multiplier, final credit amount, and provider metadata, enabling precise usage analytics and cost attribution.
Practical Implementation Examples
Charging a DataForSEO Request
To charge against SEO data usage, first ensure the customer exists in Autumn, check remaining credits, then record the spend:
import { trackUsageCreditSpend, getUsageCreditsRemaining, getOrCreateOrganizationCustomer } from "@/server/billing/subscription";
async function chargeDataForSeo(customerCtx: BillingCustomerContext, rawUsd: number) {
// Ensure customer exists in Autumn
const { id: customerId } = await getOrCreateOrganizationCustomer(customerCtx);
// Check monthly credits remaining
const { monthlyRemaining } = await getUsageCreditsRemaining(customerId);
// Record the spend with markup and provider metadata
await trackUsageCreditSpend({
customer: customerCtx,
customerId,
creditFeature: "seo_data_usage", // Defined in shared/billing-credit-features
costUsd: rawUsd,
monthlyRemaining,
properties: { provider: "dataforseo", endpoint: "/v3/serp" },
});
}
Displaying Credit Balance in USD
For UI components displaying remaining credit values, convert credits back to USD using the shared helper:
import { autumnSeoDataCreditsToUsd } from "@/shared/billing";
function displayRemainingCredits(credits: number): string {
const usd = autumnSeoDataCreditsToUsd(credits);
return `You have $${usd.toFixed(2)} of SEO data credits remaining.`;
}
Summary
- Fixed Exchange Rate: The Autumn billing credits system uses a constant rate of 1,000 credits per USD, defined in
src/shared/billing.ts. - Operational Markup: A 28% markup (
SEO_DATA_COST_MARKUP = 1.28) covers platform costs before credit conversion. - Tiered Deduction: Credits deduct first from monthly allowances (
AUTUMN_SEO_DATA_BALANCE_FEATURE_ID), then from top-up balances (AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID). - Lazy Initialization: The Autumn SDK loads on-demand via a Proxy façade in
src/server/billing/autumn.tsto optimize cold starts. - Audit Compliance: Every transaction logs to PostHog via
captureServerEventinsrc/server/lib/posthog.tsfor complete usage analytics.
Frequently Asked Questions
How does OpenSEO calculate SEO data costs in credits?
OpenSEO applies a 28% markup to the raw provider cost, then multiplies the resulting USD amount by 1,000 and rounds up to the nearest integer. For example, a $2.50 DataForSEO request becomes $3.20 after markup, converting to 3,200 credits. This calculation occurs in trackUsageCreditSpend within src/server/billing/subscription.ts.
What happens if a user exhausts their monthly credit allowance?
When monthly credits deplete, the Autumn billing credits system automatically draws from the user's top-up balance (AUTUMN_SEO_DATA_TOPUP_BALANCE_FEATURE_ID). The trackUsageCreditSpend function handles this fallback transparently, ensuring continuous service while preserving the monthly allotment for future billing cycles.
Why does OpenSEO use a lazy-loaded façade for the Autumn SDK?
The Proxy-based façade in src/server/billing/autumn.ts delays SDK initialization until first use, reducing cold-start latency in serverless functions. This approach keeps the worker bundle size minimal while exposing a synchronous API like autumn.check() and autumn.track().
How does the system prevent billing errors from missing balance data?
The getUsageCreditsRemaining function implements a retry mechanism for missing monthly balances and throws an explicit error if the balance remains unretrievable. This prevents the system from assuming zero credits or unlimited usage when Autumn's API returns transient failures.
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 →