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

> Learn why getOrCreateOrganizationCustomer must run before the SAM chat DO balance check to prevent insufficient credits errors and ensure valid customer IDs for accurate balance verification.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-09-12

---

**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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/src/server/features/sam/SamChatAgent.ts) and [`src/server/features/sam/samChatTools.ts`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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:

```typescript
// 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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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.