Skip to main content
For operations where you don’t know the final cost upfront — like AI completions, batch processing, or long-running jobs — you can reserve balance before the work starts, then finalize the reservation when it’s done. This is a three-step flow:
  1. Check with lock — atomically check access and hold balance
  2. Do work — run your operation
  3. Finalize — confirm the deduction, adjust it, or release the hold

Step 1: Check with lock

Pass the lock parameter to the check endpoint. This atomically checks if the customer has enough balance and reserves it in a single call.
const response = await autumn.check({
  customerId: "user_123",
  featureId: "ai-tokens",
  requiredBalance: 1000,
  sendEvent: true,
  lock: {
    enabled: true,
    lockId: "completion_abc123",
    expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
  },
});

if (!response.allowed) {
  // Customer doesn't have enough balance
}

// Balance is now held — proceed with the operation

Lock parameters

ParameterTypeDescription
enabledbooleanMust be true to enable locking
lock_idstringA unique identifier for this lock. You’ll use this to finalize later. If omitted, Autumn generates one.
expires_atnumberUnix timestamp (ms) when the lock auto-expires and releases the held balance. Max 24 hours from now.
Always set an expires_at to prevent balance from being held indefinitely if your finalize call fails. If a lock expires, the held balance is automatically released back to the customer.

Step 2: Do your work

Run whatever operation you reserved balance for. The held balance is guaranteed to be available — no other concurrent request can consume it.

Step 3: Finalize the lock

When the operation completes, call balances.finalize to resolve the held balance.

Confirm the full amount

If the operation used exactly the amount you reserved, confirm the lock:
await autumn.balances.finalize({
  lockId: "completion_abc123",
  action: "confirm",
});

Release the hold

If the operation failed or was canceled, release the lock to return the held balance:
await autumn.balances.finalize({
  lockId: "completion_abc123",
  action: "release",
});

Adjust the final amount

If the actual usage differs from the reserved amount (common with AI tokens), pass overrideValue to adjust:
// Reserved 1000 tokens, but only used 743
await autumn.balances.finalize({
  lockId: "completion_abc123",
  action: "confirm",
  overrideValue: 743,
});
Autumn will reconcile the difference — returning the unused 257 tokens back to the customer’s balance.

Use cases

AI completions

Reserve a token budget before starting generation, then finalize with the actual token count:
const lockId = `completion_${generateId()}`;

const { allowed } = await autumn.check({
  customerId: "user_123",
  featureId: "ai-tokens",
  requiredBalance: 4000, // max_tokens
  sendEvent: true,
  lock: {
    enabled: true,
    lockId,
    expiresAt: Date.now() + 60_000,
  },
});

if (!allowed) return showUpgradePrompt();

const completion = await openai.chat.completions.create({
  model: "gpt-4",
  max_tokens: 4000,
  messages: [{ role: "user", content: prompt }],
});

await autumn.balances.finalize({
  lockId,
  action: "confirm",
  overrideValue: completion.usage.total_tokens,
});

Long-running jobs

Reserve credits before queuing a job, release if the job fails:
const lockId = `job_${jobId}`;

const { allowed } = await autumn.check({
  customerId: "user_123",
  featureId: "compute-credits",
  requiredBalance: 10,
  sendEvent: true,
  lock: {
    enabled: true,
    lockId,
    expiresAt: Date.now() + 30 * 60_000, // 30 min timeout
  },
});

if (!allowed) throw new Error("Insufficient credits");

try {
  await runJob(jobId);
  await autumn.balances.finalize({ lockId, action: "confirm" });
} catch (error) {
  await autumn.balances.finalize({ lockId, action: "release" });
  throw error;
}

Compared to check + track

For simple operations where you know the cost upfront and the operation is fast, check with sendEvent is simpler — it deducts immediately in one call. Use reservations when:
  • The final usage amount is unknown at check time (e.g., AI token counts)
  • The operation can fail after balance is deducted
  • The operation takes significant time and you don’t want another request to consume the same balance