# How Billing Credit Tracking and Usage Metering Work in OpenSEO: A Technical Deep Dive

> Learn how OpenSEO tracks billing credits and meters usage via a centralized system, BillingCustomerContext objects, and credit deduction envelopes for API calls.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: deep-dive
- Published: 2026-08-07

---

**OpenSEO implements a unified billing credit and usage-metering system that tracks organizational credit balances through a centralized database schema, propagates billing context via `BillingCustomerContext` objects, and wraps expensive external API calls in billing envelopes that deduct credits upon task completion.**

OpenSEO (from the `every-app/open-seo` repository) handles premium feature access through a sophisticated credit-based system that ensures accurate usage tracking across rank checking, keyword research, and site audits. The architecture separates concerns between database storage, context propagation, and feature-level metering to maintain accurate financial records while supporting both SQLite and PostgreSQL backends. This system ensures that every DataForSEO API call translates into precise credit deductions from the organization's balance.

## Database Schema for Credit Storage

The foundation of OpenSEO's billing system rests in the `billing_customer_status` table, defined in [`src/db/billing.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/billing.schema.ts) for SQLite and mirrored in [`src/db/pg/billing.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/pg/billing.schema.ts) for PostgreSQL deployments.

This table stores the ground truth for each organization's financial state:

- **`credits`** or **`remaining_credits`**: The current available balance for the organization
- **`usage_usd`**: Cumulative spending across all features
- **`plan`**: The subscription tier (e.g., "free", "pro", "enterprise")

The schema supports multi-tenant isolation through `organizationId`, ensuring that credit balances remain strictly separated between different organizations using the platform.

## The BillingCustomerContext Propagation Pattern

Every authenticated request in OpenSEO initializes a billing context through the `buildBillingCustomer` function in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts). This function queries the `billing_customer_status` table and constructs a **`BillingCustomerContext`** object containing:

- `userId`: The authenticated user's identifier
- `organizationId`: The organization scope for the request
- `remainingCredits`: Current credit balance fetched from the database
- `plan`: The organization's subscription tier

This context object propagates through the entire request lifecycle, passing from server functions into workflows like [`RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/RankCheckWorkflow.ts) and [`SiteAuditWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/SiteAuditWorkflow.ts). By threading the billing context through function signatures rather than relying on global state, OpenSEO ensures that every credit-consuming operation has explicit access to the organization's current financial standing.

## Feature-Level Metering with Billing Envelopes

OpenSEO meters usage at the feature level through a **billing envelope** pattern implemented in [`src/server/lib/dataforseo/serp.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/serp.ts). When a premium feature (such as rank checking or brand lookup) initiates a DataForSEO API call, the system constructs a billing envelope containing:

- **`costUsd`**: The calculated cost based on the feature's cost profile
- **`feature`**: The feature identifier (e.g., "serp", "brand-lookup")
- **`path`**: Hierarchical identifiers for the specific operation

Cost calculations reference configurable profiles defined in scripts like [`scripts/brand-lookup-cost-profile.ts`](https://github.com/every-app/open-seo/blob/main/scripts/brand-lookup-cost-profile.ts) and [`scripts/backlinks-cost-profile.ts`](https://github.com/every-app/open-seo/blob/main/scripts/backlinks-cost-profile.ts), which map DataForSEO endpoints to per-unit USD costs. The envelope attaches to the task payload under the `billing` key, ensuring that cost metadata travels with the request through external API execution.

The internal **`autumn`** billing service (referenced throughout the workflow files) consumes these envelopes to record usage events before and after expensive operations complete.

## Credit Deduction and Persistence Flow

The billing credit tracking system follows a strict five-step workflow to maintain accurate balances:

1. **Context Creation**: `buildBillingCustomer` in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts) fetches the current `billing_customer_status` row and creates the `BillingCustomerContext`.

2. **Feature Invocation**: Premium server functions (such as those in [`src/server/workflows/RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/RankCheckWorkflow.ts)) receive the billing context and initiate metered operations.

3. **Envelope Construction**: The DataForSEO wrapper computes costs using `buildTaskBilling` in [`src/server/lib/dataforseo/serp.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/serp.ts), calculating `costUsd` by multiplying the task's item count by the unit cost from the relevant cost profile.

4. **Post-Task Accounting**: Upon completion, [`src/serverFunctions/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/billing.ts) validates the usage range and executes SQL updates to increment `usage_usd` and decrement `remaining_credits` in the `billing_customer_status` table.

5. **UI Exposure**: The front-end queries the `/billing` endpoint (defined in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts)) to retrieve current credit balances, usage history, and plan limits for display in the user interface.

## Code Implementation Examples

### Creating the Billing Context

The authentication layer constructs the billing context by querying the organization's current status:

```typescript
// src/server/mcp/project-auth.ts
export async function buildBillingCustomer(
  auth: AuthInfo,
  projectId: string,
) {
  const organizationId = await getOrgIdFromAuth(auth);
  const status = await db
    .select()
    .from(billingCustomerStatus)
    .where(eq(billingCustomerStatus.organizationId, organizationId))
    .then(row => row[0]);

  return {
    userId: auth.userId,
    organizationId,
    remainingCredits: status?.credits ?? 0,
    plan: status?.plan ?? "free",
  };
}

```

### Wrapping Tasks with Billing Metadata

The DataForSEO integration attaches cost calculations to each outgoing request:

```typescript
// src/server/lib/dataforseo/serp.ts
function buildTaskBilling(task: DataForSeoTask): BillingEnvelope {
  const costUsd = costProfile[task.type] * task.itemCount;
  return {
    costUsd,
    path: [task.type, ...task.params],
    feature: task.type,
  };
}

// When sending the request:
const client = createDataforseoClient(billingCustomer);
const result = await client.runSerp({
  ...payload,
  billing: buildTaskBilling(payload),
});

```

### Recording Usage After Completion

The billing endpoint updates the database atomically to reflect consumed credits:

```typescript
// src/serverFunctions/billing.ts
export async function recordUsage(
  ctx: BillingCustomerContext,
  usage: { costUsd: number; feature: string },
) {
  await db
    .update(billingCustomerStatus)
    .set({
      usage_usd: sql`usage_usd + ${usage.costUsd}`,
      remaining_credits: sql`remaining_credits - ${usage.costUsd}`,
    })
    .where(eq(billingCustomerStatus.organizationId, ctx.organizationId));
}

```

### Exposing Billing Data to the Frontend

Server functions return current balances for UI rendering:

```typescript
// src/serverFunctions/billing.ts
export async function getBillingInfo(ctx: BillingCustomerContext) {
  const status = await db
    .select()
    .from(billingCustomerStatus)
    .where(eq(billingCustomerStatus.organizationId, ctx.organizationId));

  return {
    credits: status[0].remaining_credits,
    usageUsd: status[0].usage_usd,
    plan: status[0].plan,
  };
}

```

### Frontend Consumption

The React frontend consumes billing data through the shared route constant:

```typescript
// Frontend usage with TanStack Query
const { data } = useQuery(['billing', orgId], () =>
  fetch('/billing').then(r => r.json())
);

```

## Summary

- **Unified Schema**: The `billing_customer_status` table in [`src/db/billing.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/billing.schema.ts) stores credit balances, usage totals, and plan tiers for each organization.
- **Context Propagation**: `BillingCustomerContext` objects created in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts) thread billing metadata through every server function and workflow.
- **Envelope Pattern**: [`src/server/lib/dataforseo/serp.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/serp.ts) wraps external API calls in billing envelopes that calculate USD costs using configurable profiles from the `scripts/` directory.
- **Atomic Updates**: [`src/serverFunctions/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/billing.ts) executes SQL operations to decrement `remaining_credits` and increment `usage_usd` after task completion.
- **Configurable Features**: [`src/shared/billing-credit-features.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing-credit-features.ts) defines which features consume credits and at what rates, allowing flexible plan configurations.

## Frequently Asked Questions

### How does OpenSEO calculate the cost for each API request?

OpenSEO calculates costs using **cost profiles** defined in scripts like [`scripts/brand-lookup-cost-profile.ts`](https://github.com/every-app/open-seo/blob/main/scripts/brand-lookup-cost-profile.ts) and [`scripts/backlinks-cost-profile.ts`](https://github.com/every-app/open-seo/blob/main/scripts/backlinks-cost-profile.ts). When building a DataForSEO task in [`src/server/lib/dataforseo/serp.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/dataforseo/serp.ts), the `buildTaskBilling` function multiplies the task's `itemCount` by the unit cost defined in the profile for that specific endpoint. This calculated `costUsd` value travels with the request in the billing envelope and gets persisted upon completion.

### What happens if an organization runs out of credits during a workflow?

The `buildBillingCustomer` function in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts) fetches the current `remaining_credits` value at the start of each request. Workflows like [`RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/RankCheckWorkflow.ts) receive this context and can validate available credits before initiating expensive operations. If credits are insufficient, the system prevents the task from entering the billing envelope stage, effectively blocking the operation before incurring external API costs.

### Can the billing system handle both SQLite and PostgreSQL deployments?

Yes. OpenSEO maintains schema definitions in both [`src/db/billing.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/billing.schema.ts) (SQLite) and [`src/db/pg/billing.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/pg/billing.schema.ts) (PostgreSQL). The billing utilities in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts) and [`src/serverFunctions/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/billing.ts) use Drizzle ORM queries that abstract the underlying database dialect, ensuring consistent credit tracking behavior regardless of the chosen persistence layer.

### How does the frontend know when to display credit warnings?

The frontend queries the `/billing` endpoint—defined via the `BILLING_ROUTE` constant in [`src/shared/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing.ts)—which executes `getBillingInfo` from [`src/serverFunctions/billing.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/billing.ts). This endpoint returns the organization's `remaining_credits`, `usage_usd`, and `plan` tier. The UI can then compare current usage against plan limits using the feature definitions in [`src/shared/billing-credit-features.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/billing-credit-features.ts) to trigger warning states or upgrade prompts.