autumn

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Autumn Billing Integration Guide

Autumn计费集成指南

Reference Repositories

参考仓库



When to Apply This Skill

适用场景

Use this when you need to:
  • Define or modify features, credit systems, or plans in
    autumn.config.ts
    .
  • Add credit checks or usage tracking via the
    autumn-js
    SDK.
  • Gate API endpoints behind billing (free tier limits, paid plan access).
  • Push/pull billing config with the
    atmn
    CLI.
  • Debug billing issues (insufficient credits, customer sync, refunds).

当你需要完成以下操作时可使用本指南:
  • autumn.config.ts
    中定义或修改功能、额度体系、套餐规则
  • 通过
    autumn-js
    SDK添加额度校验或使用量追踪能力
  • 为API接口添加计费管控(免费版额度限制、付费套餐访问权限)
  • 通过
    atmn
    CLI推送/拉取计费配置
  • 排查计费相关问题(额度不足、客户数据同步、退款处理)

Naming Conventions (CRITICAL)

命名规范(重要)

All IDs use
snake_case
.
This is Autumn's explicit convention.
Feature IDs should be descriptive (not abstract tier numbers) and ecosystem-scoped (not tied to a single app feature like "chat"). The metered features represent model cost tiers that any AI feature can consume.
typescript
// CORRECT — descriptive, ecosystem-scoped
feature({ id: 'ai_fast', ... })
feature({ id: 'ai_standard', ... })
feature({ id: 'ai_premium', ... })
plan({ id: 'pro', ... })
plan({ id: 'credit_top_up', ... })

// WRONG — tied to a single feature ("chat")
feature({ id: 'ai_chat_fast', ... })

// WRONG — abstract tier numbers (Autumn convention prefers descriptive)
feature({ id: 'ai_tier_1', ... })

// WRONG — kebab-case
feature({ id: 'ai-fast', ... })

所有ID必须使用
snake_case
命名
,这是Autumn明确要求的规范。
功能ID需要具备描述性(不要使用抽象的层级编号)且全局生态适用(不要绑定单一应用功能比如"chat")。计量功能代表所有AI功能都可消耗的模型成本层级。
typescript
// 正确 — 描述清晰、全局适用
feature({ id: 'ai_fast', ... })
feature({ id: 'ai_standard', ... })
feature({ id: 'ai_premium', ... })
plan({ id: 'pro', ... })
plan({ id: 'credit_top_up', ... })

// 错误 — 绑定了单一功能("chat")
feature({ id: 'ai_chat_fast', ... })

// 错误 — 使用了抽象层级编号(Autumn规范要求使用描述性命名)
feature({ id: 'ai_tier_1', ... })

// 错误 — 使用了kebab-case命名
feature({ id: 'ai-fast', ... })

Feature Types

功能类型

Type
consumable
Use CaseExample
metered
true
Usage that resets periodically (messages, API calls)AI model invocations
metered
false
Persistent allocation (seats, storage)Team seats
credit_system
Pool that maps to metered features via
creditSchema
AI credits
boolean
Feature flag on/offAdvanced analytics
Credit systems require linked
metered
features with
consumable: true
. Each linked feature has a
creditCost
defining how many credits one unit consumes.
typescript
export const aiUsage = feature({
  id: 'ai_usage',
  name: 'AI Usage',
  type: 'metered',
  consumable: true,
});

export const aiCredits = feature({
  id: 'ai_credits',
  name: 'AI Credits',
  type: 'credit_system',
  creditSchema: [
    { meteredFeatureId: 'ai_usage', creditCost: 1 },
  ],
});
类型
consumable
适用场景示例
metered
true
定期重置的用量(消息数、API调用次数)AI模型调用
metered
false
永久分配的资源(席位、存储空间)团队席位
credit_system
通过
creditSchema
映射到计量功能的额度池
AI额度
boolean
开关型功能标记高级分析功能
额度体系需要关联
consumable: true
的计量功能,每个关联的功能都需要定义
creditCost
,说明每单位消耗的额度数量。
typescript
export const aiUsage = feature({
  id: 'ai_usage',
  name: 'AI Usage',
  type: 'metered',
  consumable: true,
});

export const aiCredits = feature({
  id: 'ai_credits',
  name: 'AI Credits',
  type: 'credit_system',
  creditSchema: [
    { meteredFeatureId: 'ai_usage', creditCost: 1 },
  ],
});

Proportional Billing

比例计费

Instead of multiple metered features with fixed
creditCost
per tier, use a single metered feature with
creditCost: 1
and vary the
requiredBalance
at runtime.
This gives per-model cost precision without cluttering the Autumn dashboard with dozens of features.
How it works: Autumn's
check()
with
sendEvent: true
uses
requiredBalance
as the deduction amount. With
creditCost: 1
, passing
requiredBalance: 5
deducts exactly 5 credits from the pool.
typescript
// Runtime cost table (in model-costs.ts, not autumn.config.ts)
const MODEL_CREDITS: Record<string, number> = {
  'gpt-4o-mini': 1,      // cheap model = 1 credit
  'claude-sonnet-4': 5,  // mid-range = 5 credits
  'claude-opus-4': 30,   // expensive = 30 credits
};

// Dynamic deduction
const credits = MODEL_CREDITS[model];
await autumn.check({
  customerId,
  featureId: 'ai_usage',      // single feature for all models
  requiredBalance: credits,    // varies per model
  sendEvent: true,
});
Refund on error: Use
track({ featureId: 'ai_usage', value: -credits })
to refund the exact amount.
Blocking expensive models: Omit them from
MODEL_CREDITS
. Unknown models →
getModelCredits()
returns
undefined
→ 400.

不要创建多个计量功能为每个层级设置固定
creditCost
,而是使用单个计量功能设置
creditCost: 1
,在运行时动态修改
requiredBalance
的值。
这种方式可以实现按模型精确计费,不会在Autumn控制台中创建大量冗余功能。
实现逻辑:Autumn的
check()
方法如果设置了
sendEvent: true
,会将
requiredBalance
作为扣除金额。当
creditCost: 1
时,传入
requiredBalance: 5
就会从额度池中精准扣除5个额度。
typescript
// 运行时成本表(存放在model-costs.ts中,不是autumn.config.ts)
const MODEL_CREDITS: Record<string, number> = {
  'gpt-4o-mini': 1,      // 低成本模型 = 1额度
  'claude-sonnet-4': 5,  // 中端模型 = 5额度
  'claude-opus-4': 30,   // 高成本模型 = 30额度
};

// 动态扣除额度
const credits = MODEL_CREDITS[model];
await autumn.check({
  customerId,
  featureId: 'ai_usage',      // 所有模型共用同一个功能ID
  requiredBalance: credits,    // 按模型动态变化
  sendEvent: true,
});
错误场景退款:调用
track({ featureId: 'ai_usage', value: -credits })
来退还对应额度。
限制高成本模型使用:将其从
MODEL_CREDITS
中移除即可,未知模型会导致
getModelCredits()
返回
undefined
,进而返回400错误。

Plan Structure

套餐结构

Groups

分组

Plans in the same
group
are mutually exclusive. Subscribing to a new plan in the same group replaces the old one. Autumn handles the Stripe subscription swap automatically.
  • Upgrade (free → pro): Immediate swap with proration.
  • Downgrade (pro → free): Scheduled for end of billing cycle.
同一个
group
下的套餐是互斥的,订阅同分组下的新套餐会自动替换旧套餐,Autumn会自动处理Stripe订阅的切换逻辑。
  • 升级(免费版→专业版):立即切换并按使用比例结算
  • 降级(专业版→免费版):在当前计费周期结束后生效

Add-ons

附加项

Plans with
addOn: true
stack on top of any plan. No group conflict.
设置了
addOn: true
的套餐可以叠加在任意套餐之上,不会产生分组冲突。

autoEnable

autoEnable

Plans with
autoEnable: true
are auto-assigned when a customer is created via
customers.getOrCreate()
. Use for free tiers. Only allowed on plans with no
price
.
设置了
autoEnable: true
的套餐会在通过
customers.getOrCreate()
创建客户时自动分配,适用于免费套餐,仅可用于无
price
配置的套餐。

Plan items:
reset.interval
vs
price.interval

套餐项:
reset.interval
vs
price.interval

The intervals are mutually exclusive, not
reset
and
price
themselves. A
PlanItem
is one of three variants:
PlanItemWithReset
— Has
reset.interval
. If
price
is also present, it CANNOT have
price.interval
. Use for free allocations that reset periodically, optionally with one-time overage pricing.
PlanItemWithPriceInterval
— Has
price.interval
. CANNOT have
reset
. The
price.interval
determines BOTH the billing cycle AND when the
included
balance resets for consumable features. Use for paid plans with usage-based overage.
PlanItemNoReset
— No
reset
. Use for continuous-use features like seats, or boolean features.
typescript
// Free plan — reset only, no price
// `reset.interval` controls when the 50 included credits refresh
item({ featureId: aiCredits.id, included: 50, reset: { interval: 'month' } })

// Paid plan — price.interval handles both billing AND reset
// The 2000 included credits reset monthly via `price.interval: 'month'`
// Overage beyond 2000 billed at $1/100 credits
item({
  featureId: aiCredits.id,
  included: 2000,
  price: { amount: 1, billingUnits: 100, billingMethod: 'usage_based', interval: 'month' },
})
Key insight: For paid plans,
included
+
price.interval
implies monthly reset. The
included
field's Zod description: "Balance resets to this each interval for consumable features." You do NOT need a separate
reset
field on paid plan items.

两个interval参数是互斥的,不是
reset
price
本身互斥。
PlanItem
分为三种类型:
PlanItemWithReset
— 包含
reset.interval
。如果同时配置了
price
,则
price
不能包含
interval
。适用于定期重置的免费额度,可搭配一次性超额定价使用。
PlanItemWithPriceInterval
— 包含
price.interval
,不能配置
reset
price.interval
同时决定计费周期和可消耗功能的
included
额度重置时间,适用于有超额用量计费的付费套餐。
PlanItemNoReset
— 无
reset
配置,适用于席位等持续使用的功能或开关型功能。
typescript
// 免费套餐 — 仅配置reset,无价格
// `reset.interval`控制50个赠送额度每月刷新一次
item({ featureId: aiCredits.id, included: 50, reset: { interval: 'month' } })

// 付费套餐 — price.interval同时处理计费和额度重置
// 2000个赠送额度通过`price.interval: 'month'`每月重置
// 超出2000的部分按每100额度1美元计费
item({
  featureId: aiCredits.id,
  included: 2000,
  price: { amount: 1, billingUnits: 100, billingMethod: 'usage_based', interval: 'month' },
})
核心要点:付费套餐中
included
+
price.interval
就代表每月重置额度,
included
字段的Zod描述为:"可消耗功能的额度每个周期重置为该值",不需要在付费套餐项中额外配置
reset
字段。

SDK:
autumn-js

SDK:
autumn-js

Initialization

初始化

typescript
import { Autumn } from 'autumn-js';

const autumn = new Autumn({ secretKey: env.AUTUMN_SECRET_KEY });
Stateless—safe to create per-request. No connection pooling needed.
typescript
import { Autumn } from 'autumn-js';

const autumn = new Autumn({ secretKey: env.AUTUMN_SECRET_KEY });
无状态,每个请求创建实例也安全,不需要连接池。

Customer Sync (MUST be blocking)

客户同步(必须阻塞执行)

typescript
await autumn.customers.getOrCreate({
  customerId: userId,
  name: userName ?? undefined,
  email: userEmail ?? undefined,
});
This call MUST be awaited (blocking). Autumn's
/check
endpoint does not auto-create customers. The customer must exist before any
check()
call.
typescript
await autumn.customers.getOrCreate({
  customerId: userId,
  name: userName ?? undefined,
  email: userEmail ?? undefined,
});
该调用必须使用await阻塞执行,Autumn的
/check
接口不会自动创建客户,任何
check()
调用前必须确保客户已存在。

Credit Check

额度校验

typescript
const credits = getModelCredits(data.model);
const { allowed, balance } = await autumn.check({
  customerId: userId,
  featureId: 'ai_usage',
  requiredBalance: credits,
  sendEvent: true,
  properties: { model, provider },
});

if (!allowed) {
  // Return 402 with balance info
}
featureId is always 'ai_usage'. The credit cost varies per model via the dynamic requiredBalance.
typescript
const credits = getModelCredits(data.model);
const { allowed, balance } = await autumn.check({
  customerId: userId,
  featureId: 'ai_usage',
  requiredBalance: credits,
  sendEvent: true,
  properties: { model, provider },
});

if (!allowed) {
  // 返回402状态码和额度信息
}
featureId固定为'ai_usage',通过动态的requiredBalance实现按模型差异化扣减额度。

Refund on Error

错误场景退款

typescript
await autumn.track({
  customerId: userId,
  featureId: 'ai_usage',
  value: -credits,  // Negative value = refund
});
Use when the operation fails after credits were already deducted (e.g., AI stream errors). Typically pushed to an
afterResponse
queue to avoid blocking the error response.

typescript
await autumn.track({
  customerId: userId,
  featureId: 'ai_usage',
  value: -credits,  // 负值代表退款
});
适用于额度扣除后操作失败的场景(比如AI流式响应出错),通常推送到
afterResponse
队列处理,避免阻塞错误响应返回。

CLI:
atmn

CLI:
atmn

Setup

环境配置

bash
bunx atmn login        # OAuth login, saves keys to .env
bunx atmn env          # Verify org and environment
bash
bunx atmn login        # OAuth登录,将密钥保存到.env
bunx atmn env          # 验证组织和环境配置

Config File

配置文件

autumn.config.ts
at the project root. Defines features and plans using
atmn
builders:
typescript
import { feature, item, plan } from 'atmn';
项目根目录下的
autumn.config.ts
,使用
atmn
提供的构建器定义功能和套餐:
typescript
import { feature, item, plan } from 'atmn';

Push/Pull

配置推送/拉取

bash
bunx atmn preview      # Dry run — shows what would change
bunx atmn push         # Push to sandbox (interactive confirmation)
bunx atmn push --prod  # Push to production
bunx atmn push --yes   # Auto-confirm (for CI/CD)
bunx atmn pull         # Pull remote config, generate SDK types
bash
bunx atmn preview      #  dry run — 展示会变更的内容
 bunx atmn push         # 推送到沙箱环境(需要交互确认)
bunx atmn push --prod  # 推送到生产环境
bunx atmn push --yes   # 自动确认(适用于CI/CD场景)
bunx atmn pull         # 拉取远程配置,生成SDK类型定义

Data Inspection

数据查看

bash
bunx atmn customers    # Browse customers
bunx atmn plans        # Browse plans
bunx atmn features     # Browse features
bunx atmn events       # Browse usage events

bash
bunx atmn customers    # 查看客户列表
bunx atmn plans        # 查看套餐列表
bunx atmn features     # 查看功能列表
bunx atmn events       # 查看用量事件列表

Environment & Secrets

环境与密钥

KeyEnvironmentPrefix
AUTUMN_SECRET_KEY
Sandbox (test)
am_sk_test_...
AUTUMN_SECRET_KEY
Production
am_sk_prod_...
Use the same key name in both environments. Let your secrets manager (Infisical, etc.) swap the value per environment. Don't create separate key names for sandbox vs prod.
For Cloudflare Workers:
wrangler secret put AUTUMN_SECRET_KEY
For local dev with Infisical: secrets are auto-injected via
infisical run --path=/api -- wrangler dev

密钥名称环境前缀
AUTUMN_SECRET_KEY
沙箱(测试)
am_sk_test_...
AUTUMN_SECRET_KEY
生产
am_sk_prod_...
两个环境使用相同的密钥名称,通过密钥管理工具(Infisical等)按环境切换值即可,不要为沙箱和生产创建不同的密钥名称。
Cloudflare Workers配置:
wrangler secret put AUTUMN_SECRET_KEY
本地使用Infisical开发:通过
infisical run --path=/api -- wrangler dev
自动注入密钥

Middleware Pattern (Cloudflare Workers + Hono)

中间件模式(Cloudflare Workers + Hono)

Ensure Customer Exists

确保客户存在

Run after
authGuard
, before any billing-gated routes:
typescript
app.use('/ai/*', async (c, next) => {
  const autumn = createAutumn(c.env);
  await autumn.customers.getOrCreate({
    customerId: c.var.user.id,
    name: c.var.user.name ?? undefined,
    email: c.var.user.email ?? undefined,
  });
  await next();
});
Why inline? Cloudflare Workers don't expose
env
at module scope. The Autumn client must be created inside the request handler.
authGuard
之后、所有计费管控路由之前执行:
typescript
app.use('/ai/*', async (c, next) => {
  const autumn = createAutumn(c.env);
  await autumn.customers.getOrCreate({
    customerId: c.var.user.id,
    name: c.var.user.name ?? undefined,
    email: c.var.user.email ?? undefined,
  });
  await next();
});
为什么要内联创建? Cloudflare Workers在模块作用域无法获取
env
,必须在请求处理函数内部创建Autumn客户端。

Credit Gate in Handler

处理函数中的额度管控

typescript
const credits = getModelCredits(data.model);
if (!credits) return c.json(error, 400);

const { allowed, balance } = await autumn.check({
  customerId: c.var.user.id,
  featureId: 'ai_usage',
  requiredBalance: credits,
  sendEvent: true,
});

if (!allowed) return c.json(error, 402);

typescript
const credits = getModelCredits(data.model);
if (!credits) return c.json(error, 400);

const { allowed, balance } = await autumn.check({
  customerId: c.var.user.id,
  featureId: 'ai_usage',
  requiredBalance: credits,
  sendEvent: true,
});

if (!allowed) return c.json(error, 402);

Stripe Integration

Stripe集成

  • Sandbox: Built-in Stripe test account. No setup needed.
  • Production: Connect via Dashboard → Integrations → Stripe (OAuth recommended).
  • Autumn creates Stripe products/prices automatically when you
    atmn push
    .
  • Autumn is the source of truth for customer state; Stripe handles payments.

  • 沙箱环境:内置Stripe测试账号,无需额外配置
  • 生产环境:通过控制台→集成→Stripe连接(推荐使用OAuth)
  • 执行
    atmn push
    时Autumn会自动创建Stripe商品/价格
  • Autumn是客户状态的唯一可信源,Stripe负责处理支付逻辑

Common Gotchas

常见注意事项

  1. getOrCreate
    must be awaited
    — Fire-and-forget will cause
    check()
    to fail with "customer not found."
  2. featureId
    in
    check()
    is always 'ai_usage'
    — The credit cost varies per model via dynamic
    requiredBalance
    , not featureId.
  3. reset.interval
    and
    price.interval
    are mutually exclusive
    — not
    reset
    and
    price
    themselves. A
    PlanItemWithReset
    CAN have a
    price
    , but that price cannot have an
    interval
    . For paid plans,
    price.interval
    handles both billing and balance reset.
  4. sendEvent: true
    deducts atomically
    — Don't call
    track()
    separately for the happy path. Only use
    track({ value: -1 })
    for refunds.
  5. All IDs are snake_case — Autumn's pricing agent convention. Don't use kebab-case.
  6. autoEnable
    triggers on customer creation
    — Not on first
    check()
    . Ensure the middleware calls
    getOrCreate
    before checking.
  7. Multiple keys per environment — Autumn supports multiple active secret keys for rotation. Generate new key → update secrets → revoke old key.
  8. Use proportional billing — One metered feature (
    ai_usage
    ) with
    creditCost: 1
    and dynamic
    requiredBalance
    per model. Per-model costs live in model-costs.ts, not autumn.config.ts. This avoids cluttering the dashboard with dozens of features.

  1. getOrCreate
    必须使用await阻塞
    :如果不等待执行完成,
    check()
    会报"客户不存在"错误
  2. check()
    中的
    featureId
    固定为'ai_usage'
    :通过动态
    requiredBalance
    实现按模型差异化扣费,不是通过featureId区分
  3. reset.interval
    price.interval
    互斥
    :不是
    reset
    price
    本身互斥,
    PlanItemWithReset
    可以有
    price
    ,但该price不能有
    interval
    ,付费套餐通过
    price.interval
    同时处理计费和额度重置
  4. sendEvent: true
    会原子性扣减额度
    :正常流程不需要单独调用
    track()
    ,只有退款场景使用
    track({ value: -1 })
  5. 所有ID使用snake_case命名:这是Autumn定价Agent的规范,不要使用kebab-case
  6. autoEnable
    在客户创建时触发
    :不是在第一次
    check()
    时触发,确保中间件在校验前调用
    getOrCreate
  7. 单环境支持多密钥:Autumn支持多个活跃密钥用于轮换,生成新密钥→更新密钥配置→撤销旧密钥即可
  8. 使用比例计费模式:创建单个计量功能(
    ai_usage
    )设置
    creditCost: 1
    ,按模型动态设置
    requiredBalance
    ,模型成本存放在model-costs.ts中,不是autumn.config.ts,避免控制台出现大量冗余功能

Project Files

项目文件

FilePurpose
apps/api/autumn.config.ts
Feature, credit system, and plan definitions
apps/api/src/autumn.ts
createAutumn(env)
factory for per-request SDK client
apps/api/src/model-costs.ts
Model string → proportional credit cost mapping
apps/api/src/ai-chat.ts
Credit check + refund logic for AI chat handler
apps/api/src/app.ts
Middleware wiring (ensureAutumnCustomer)

文件路径用途
apps/api/autumn.config.ts
功能、额度体系、套餐定义
apps/api/src/autumn.ts
按请求创建SDK客户端的
createAutumn(env)
工厂函数
apps/api/src/model-costs.ts
模型名称→比例计费额度映射表
apps/api/src/ai-chat.ts
AI聊天处理函数的额度校验+退款逻辑
apps/api/src/app.ts
中间件配置(ensureAutumnCustomer)

Resources

参考资源