Why `getOrCreateOrganizationCustomer` Must Run Before the SAM Chat DO Balance Check

The SAM chat Durable Object requires a valid Autumn customer ID to check credit balances, and getOrCreateOrganizationCustomer ensures that ID exists before the balance verification runs to prevent false "insufficient credits" errors.

The open-seo repository implements a credit-gated SAM chat system using Cloudflare Durable Objects and the Autumn billing platform. Before the SamChatAgent can verify if an organization has remaining usage credits, it must first guarantee that a corresponding billing customer exists in Autumn. The getOrCreateOrganizationCustomer function in src/server/billing/subscription.ts serves as this critical prerequisite, creating or validating the customer record that all subsequent balance checks depend upon.

The Autumn Billing Dependency

SAM chat functionality is protected by a usage-credits gate that prevents access when an organization's balance is depleted. This gate relies on the Autumn billing service to retrieve real-time credit balances using the organization's customer ID.

In src/server/billing/subscription.ts, the balance verification assumes the presence of a valid Autumn customer record. The checkUsageCreditsDepleted function (lines 58-66) calls autumn.check({ customerId, ... }) to read current balances. Without a pre-existing customer ID, this call would either error out or return a null balance interpreted as zero credits, incorrectly blocking user access to SAM chat features.

How getOrCreateOrganizationCustomer Works

The getOrCreateOrganizationCustomer function guarantees that a valid billing customer exists before any credit checks occur. Located at lines 34-63 in src/server/billing/subscription.ts, this function performs two critical operations:

  • Customer creation or retrieval – It calls autumn.customers.getOrCreate to either fetch an existing customer or create a new one in the Autumn platform, returning an object with an id property that serves as the customerId for subsequent operations.
  • Caching – It stores the customer existence validation in KV storage via env.KV.put, allowing subsequent requests to skip redundant round-trips to the Autumn API.

This caching mechanism ensures that the relatively expensive customer lookup operation happens only once per organization, while guaranteeing that the customerId required by balance checks is always available.

The SAM Chat Credit Gate Execution Flow

The SamChatAgent Durable Object orchestrates SAM chat interactions and enforces credit limits through a specific execution sequence. According to the source code in src/server/features/sam/SamChatAgent.ts and src/server/features/sam/samChatTools.ts, the chat runtime follows this dependency chain:

  1. Context preparation – The system calls getOrCreateOrganizationCustomer to obtain a valid customerId.
  2. Balance verification – Only after securing the customer ID does the code invoke checkUsageCreditsDepleted to query autumn.check for current usage statistics.
  3. Access gating – If the balance check returns depleted: true, the chat turn throws an OUT_OF_CREDITS error; otherwise, it proceeds to deduct credits and process the chat request.

This ordering ensures that the balance check at lines 58-66 of src/server/billing/subscription.ts always operates against a valid customer record, avoiding the false-negative scenario where a missing customer appears as a zero-balance user.

Consequences of Incorrect Ordering

If checkUsageCreditsDepleted were to execute before getOrCreateOrganizationCustomer, the system would encounter a critical failure mode:

  • Resolution errors – The autumn.check call would fail to resolve the customerId, potentially throwing an API error that disrupts the chat flow.
  • False zero-balance blocks – More dangerously, Autumn might interpret a missing customer as a zero-balance account, causing the gate to return depleted: true and block legitimate users who actually have available credits.

By enforcing the creation of the customer record first, the open-seo codebase ensures that balance checks reflect the organization's true credit status rather than artifacts of missing billing data.

Implementation Example

The following pattern from the open-seo repository demonstrates the correct execution order:

// Step 1: Ensure the org has a billing customer (creates if missing)
import { getOrCreateOrganizationCustomer } from "@/server/billing/subscription";

async function prepareContext(context: BillingCustomerContext) {
  // Guarantees a valid Autumn customer ID for later balance checks
  const { id: customerId } = await getOrCreateOrganizationCustomer(context);
  return { ...context, organizationId: customerId };
}

// Step 2: SAM chat turn – balance gate runs after the above step
import { checkUsageCreditsDepleted } from "@/server/billing/subscription";

async function samChatTurn(context: BillingCustomerContext) {
  // Balance check assumes the customer exists
  const { depleted, monthlyRemaining } = await checkUsageCreditsDepleted(context);
  if (depleted) {
    throw new Error("OUT_OF_CREDITS");
  }
  // …proceed with the chat turn, spend credits, etc.
}

In this implementation, prepareContext must complete successfully before samChatTurn can safely verify credits. The checkUsageCreditsDepleted function relies on the organizationId established by the prior customer creation step to query Autumn's billing API accurately.

Summary

  • getOrCreateOrganizationCustomer creates or validates the Autumn billing customer and caches the result in src/server/billing/subscription.ts (lines 34-63).
  • checkUsageCreditsDepleted performs the credit balance verification in the same file (lines 58-66) and requires a valid customer ID to function correctly.
  • Execution order matters because missing customer records cause Autumn to return errors or zero balances, resulting in false "out of credits" rejections.
  • SAM chat reliability depends on the SamChatAgent Durable Object calling the customer creation function before any balance gates in samChatTools.ts.

Frequently Asked Questions

What happens if checkUsageCreditsDepleted runs before getOrCreateOrganizationCustomer?

The balance check would either error out due to an unresolvable customerId or return a null balance interpreted as zero credits. This would trigger an immediate "insufficient credits" rejection, blocking users from SAM chat even when they have available usage credits.

Where does the customer ID caching occur in the open-seo codebase?

The caching happens in src/server/billing/subscription.ts within the getOrCreateOrganizationCustomer function (lines 34-63). After creating or retrieving the customer from Autumn, the function stores the customer existence validation in KV storage via env.KV.put, allowing subsequent calls to skip the API round-trip.

Why does SAM chat use a Durable Object for credit checking?

The SamChatAgent Durable Object maintains stateful coordination of chat sessions while enforcing strict credit gates. Using a Durable Object ensures that credit balance checks and deductions happen atomically within the same execution context, preventing race conditions where multiple chat turns might incorrectly pass the balance gate before credits are deducted.

Can the customer creation and balance check be combined into a single function?

While technically possible, separating these concerns improves reliability and performance. The getOrCreateOrganizationCustomer function includes KV caching that persists across requests, whereas checkUsageCreditsDepleted performs real-time balance queries against Autumn's API. Keeping them separate allows the system to cache the customer lookup while always checking fresh balance data.

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 →