atmn 1.1.25 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/dist/bin.js +23102 -0
- package/dist/index.js +2906 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -74
- package/src/actions/api/callApi.ts +280 -0
- package/src/actions/api/registerApiCommands.ts +121 -0
- package/src/actions/env/fetchOrgInfo.ts +32 -0
- package/src/actions/env/types/orgInfo.ts +18 -0
- package/src/actions/env.ts +94 -0
- package/src/actions/init/runInit.ts +401 -0
- package/src/actions/login/keyless.ts +135 -0
- package/src/actions/login.ts +144 -0
- package/src/actions/pull/appendPlanVersionFixture.ts +373 -0
- package/src/actions/pull/applyPreview.ts +553 -0
- package/src/actions/pull/applySettingsPreview.ts +114 -0
- package/src/actions/pull/changedFixtureKeys.ts +88 -0
- package/src/actions/pull/listSourceFiles.ts +29 -0
- package/src/actions/pull/locateFixture.ts +78 -0
- package/src/actions/pull/resolveCollectionTarget.ts +198 -0
- package/src/actions/pull/rewriteConfig.ts +57 -0
- package/src/actions/pull/scaffoldConfig.ts +118 -0
- package/src/actions/pull.ts +399 -0
- package/src/actions/push/backfillInternalIds.ts +394 -0
- package/src/actions/push/deprecatedFields.ts +55 -0
- package/src/actions/push.ts +294 -0
- package/src/actions/reset/runReset.ts +58 -0
- package/src/actions/sandbox/createSandbox.ts +83 -0
- package/src/actions/sandbox/deleteSandbox.ts +91 -0
- package/src/actions/sandbox/listSandboxes.ts +28 -0
- package/src/actions/sandbox/types/sandboxClient.ts +16 -0
- package/src/actions/sandbox/useSandbox.ts +157 -0
- package/src/actions/sandbox/withSandboxScopeHint.ts +27 -0
- package/src/actions/skills/skills.ts +246 -0
- package/src/auth/announceAuthorizationUrl.ts +26 -0
- package/src/auth/browser/openSystemBrowser.ts +7 -0
- package/src/auth/browser/tryOpenBrowser.ts +17 -0
- package/src/auth/browser/watchLauncher.ts +46 -0
- package/src/auth/buildAuthorizationUrl.ts +36 -0
- package/src/auth/callbackPages.ts +126 -0
- package/src/auth/createOrgApiKeys.ts +46 -0
- package/src/auth/keyless.ts +119 -0
- package/src/auth/oauthConfig.ts +63 -0
- package/src/auth/runOAuthFlow.ts +230 -0
- package/src/auth/types/browserOpener.ts +5 -0
- package/src/auth/types/impersonationTokens.ts +23 -0
- package/src/auth/types/oauthTokens.ts +13 -0
- package/src/auth/types/orgApiKeys.ts +6 -0
- package/src/bin.ts +9 -0
- package/src/cli.ts +648 -0
- package/src/config/configPackageName.ts +9 -0
- package/src/config/legacyConfig.ts +25 -0
- package/src/config/loadConfig.ts +231 -0
- package/src/env/assertSandboxTarget.ts +20 -0
- package/src/env/loadEnv.ts +184 -0
- package/src/env/resolveTarget.ts +134 -0
- package/src/env/sandboxKeyName.ts +18 -0
- package/src/generated/apiRoutes.ts +3787 -0
- package/src/generated/client.ts +51564 -0
- package/src/generated/emit.ts +1163 -0
- package/src/generated/emitRuntime.ts +522 -0
- package/src/generated/features.ts +146 -0
- package/src/generated/labels.ts +29 -0
- package/src/generated/licenses.ts +287 -0
- package/src/generated/lintRules.ts +2207 -0
- package/src/generated/lintRuntime.ts +865 -0
- package/src/generated/plans.ts +1628 -0
- package/src/generated/referralPrograms.ts +22 -0
- package/src/generated/rewards.ts +58 -0
- package/src/generated/settings.ts +21 -0
- package/src/generated/skills.ts +305 -0
- package/src/generated/variants.ts +934 -0
- package/src/generated/wire.ts +334 -0
- package/src/http/autumnFetch.ts +30 -0
- package/src/index.ts +20 -0
- package/src/project/chooseConfigDir.ts +41 -0
- package/src/project/resolveProject.ts +115 -0
- package/src/project/rootMarker.ts +40 -0
- package/src/prompt/prompt.ts +186 -0
- package/src/prompt/select.ts +162 -0
- package/src/render/renderEnv.ts +77 -0
- package/src/render/renderPreview.ts +945 -0
- package/src/render/renderSandboxes.ts +92 -0
- package/src/render/stripTerminalControls.ts +19 -0
- package/src/repo/findRepoRoot.ts +79 -0
- package/src/surgery/appendPropertyEdit.ts +67 -0
- package/src/surgery/appendToArray.ts +87 -0
- package/src/surgery/appendToBinding.ts +19 -0
- package/src/surgery/appendToCollection.ts +40 -0
- package/src/surgery/appendToFixtureArray.ts +71 -0
- package/src/surgery/arrayBinding.ts +30 -0
- package/src/surgery/deleteFixtureLiteral.ts +81 -0
- package/src/surgery/deleteReference.ts +48 -0
- package/src/surgery/ensureBuilderImport.ts +65 -0
- package/src/surgery/findFixture.ts +238 -0
- package/src/surgery/fixtureEdit.ts +156 -0
- package/src/surgery/fixtureLocation.ts +32 -0
- package/src/surgery/insertCollection.ts +86 -0
- package/src/surgery/insertFirstProperty.ts +73 -0
- package/src/surgery/patchFixtureProperty.ts +152 -0
- package/src/surgery/patchSingletonProperty.ts +221 -0
- package/src/surgery/replaceFixture.ts +28 -0
- package/src/surgery/setFixtureProperty.ts +55 -0
- package/src/surgery/staticFixtureRule.ts +48 -0
- package/src/version.ts +5 -0
- package/dist/cli.js +0 -146296
- package/dist/compose/index.js +0 -122
- package/dist/src/compose/builders/builderFunctions.d.ts +0 -84
- package/dist/src/compose/builders/rewardFunctions.d.ts +0 -5
- package/dist/src/compose/builders/variantFunctions.d.ts +0 -2
- package/dist/src/compose/index.d.ts +0 -19
- package/dist/src/compose/models/featureModels.d.ts +0 -262
- package/dist/src/compose/models/index.d.ts +0 -3
- package/dist/src/compose/models/planModels.d.ts +0 -562
- package/dist/src/compose/models/rewardModels.d.ts +0 -52
- package/dist/src/compose/models/variantModels.d.ts +0 -34
- package/readme.md +0 -186
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// Generated by @autumn/atmn-generator from packages/agent-docs/src/generated/skills.generated.ts.
|
|
2
|
+
// Do not edit — run `bun generate` in packages/atmn-generator instead.
|
|
3
|
+
|
|
4
|
+
export type BundledSkill = {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly description: string;
|
|
7
|
+
/** The CLI version the skill was frozen with; the frontmatter says the same. */
|
|
8
|
+
readonly version: string;
|
|
9
|
+
/** Full SKILL.md text, frontmatter included. */
|
|
10
|
+
readonly markdown: string;
|
|
11
|
+
readonly references: readonly {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly contents: string;
|
|
14
|
+
}[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const SKILLS_VERSION = "2.0.0";
|
|
18
|
+
|
|
19
|
+
export const SKILLS: readonly BundledSkill[] = [
|
|
20
|
+
{
|
|
21
|
+
name: "autumn-setup",
|
|
22
|
+
description:
|
|
23
|
+
"First-time Autumn setup — install the atmn CLI, connect to an org (sign in, or keyless with no account), turn the user's pricing into autumn.config.ts, and push it to a sandbox org. Use when the user is new to Autumn, pastes an Autumn setup prompt, or asks to set up Autumn, add billing, monetization, subscriptions, usage-based billing, pricing, or plans. If Autumn is already set up, use autumn-catalog instead.",
|
|
24
|
+
version: "2.0.0",
|
|
25
|
+
markdown:
|
|
26
|
+
"---\nname: autumn-setup\ndescription: First-time Autumn setup — install the atmn CLI, connect to an org (sign in, or keyless with no account), turn the user's pricing into autumn.config.ts, and push it to a sandbox org. Use when the user is new to Autumn, pastes an Autumn setup prompt, or asks to set up Autumn, add billing, monetization, subscriptions, usage-based billing, pricing, or plans. If Autumn is already set up, use autumn-catalog instead.\nversion: 2.0.0\n---\n\n# Setup\n\nTake the user from \"I want billing\" to pricing that is live in a sandbox org and working in their app — with as little between those two points as possible. You run the flow; two other skills do the heavy parts. `autumn-catalog` turns their pricing into `autumn.config.ts`. `autumn-integrate` puts the first calls in their code. Never do either of those jobs yourself.\n\n## Ground rules\n\nThese apply the whole time, not just in one phase.\n\n- CLI-first: everything happens in `autumn.config.ts` and `atmn`. The only browser moments are signing in, or claiming a keyless org later. Never send the user to the dashboard to do the work.\n- Never invent a price, limit, or plan name. A missing number is a question, never a guess.\n- Push only after the user approves the pricing (Phase 4), or when they already told you to go ahead without a review.\n- Sandbox by default: `AUTUMN_SECRET_KEY` is the sandbox key. Don't touch production during setup.\n- Keys: check that a key exists by its name only. Never read, print, or ask the user to paste a key into the chat. Same for a keyless org's claim token. A one-time email code is not a key — that one does come through the chat.\n- Two tries max to fix any failing command, then stop and show the error.\n- Never run `atmn reset`, and never remove existing plans unless the user clearly asked.\n- If a step is already done, say so in one line (\"Already signed in — skipping login\") and move on.\n\n## How to talk\n\nYou are a competent engineer pairing with the user, not an installer wizard and not a marketer.\n\n- Simple, everyday words: \"plans\", \"what's included\", \"extra usage\". No jargon in chat — schema words like `consumable`, `prepaid`, `usage_based` stay in the config. If a simpler word says the same thing, use it.\n- 1–3 sentences per message. The pricing summary is the only large thing you send.\n- One message, one purpose: a status, a question, or the pricing summary.\n- Don't ask permission for harmless work — reading the repo, drafting the config, building the summary. Ask only for decisions and approvals.\n- Say what's about to happen before it does: one line before the browser opens, one before the push.\n- When you need input, ask at most three short numbered questions — the ones that unblock you, nothing more. If your platform has a built-in way to ask questions with options, use it. Never re-ask something they answered.\n- Always end a message with something: the question, what you're doing next, or that you're done.\n- Don't paste the config or command output into chat; name the file and summarize. Errors are the exception — quote those exactly.\n- If you're stuck, send three lines: what failed, the exact error, what you need to continue.\n- No emoji, no hype, no \"Great question\". Plain and concrete: \"Connected to Acme (sandbox).\"\n\n## Progress\n\nCopy this checklist into your first message and keep it up to date. If you skip an item, say why in one line.\n\n- [ ] 1 Skills installed; checked for an existing config, key, and pricing\n- [ ] 2 Connected to Autumn — `atmn` installed, signed in or keyless\n- [ ] 3 Got the user's pricing — rough plans and prices to build from\n- [ ] 4 Pricing modeled and approved (the `autumn-catalog` skill runs this part)\n- [ ] 5 Pushed to Autumn and verified\n- [ ] 6 Working in the app — one plan bought, one feature gated (the `autumn-integrate` skill runs this part)\n- [ ] 7 Account linked (keyless only — drop this line if they signed in)\n- [ ] 8 Done\n\nFor items 4 and 6, another skill owns the conversation and its checklist replaces this one for the duration — show theirs, not this one. Come back here when they're finished.\n\n## Phase 1 — Check the project (silent)\n\nDon't message the user yet — just find out where things stand.\n\nFour skills share this job, and they ship inside the `atmn` CLI: `atmn init` writes them next to the config and prints the `npx skills add <that folder> -y` line that registers them with your agent, and `atmn skills update` refreshes them when `push` or `pull` say they are older than the CLI. If any is missing here, that is Phase 2's job — never fetch them from anywhere else.\n\n`autumn-setup` (this file) is the flow. `autumn-catalog` is how to build the pricing, plus the exact `atmn` commands — load it in Phase 4. `autumn-integrate` is how the app calls Autumn — load it in Phase 6. `autumn-concepts` explains Autumn's objects — the other two load it themselves.\n\nThen check three things:\n\n- `autumn.config.ts` exists → Autumn is already set up here. Say so, treat the file as the truth, and use `autumn-catalog` for the changes; come back at Phase 5 (push) when there's something to push.\n- An `AUTUMN_SECRET_KEY` exists (shell env, `.env`, `.env.local`) → already connected; skip the connect step in Phase 2.\n- They already told you the pricing (their message, a pricing page, the README) → Phase 3 is a quick confirm, not a list of questions.\n\n## Phase 2 — Introduce and connect\n\nStart with two or three sentences: what's going to happen (connect this project to an Autumn org → write the pricing into `autumn.config.ts` → approve the pricing → push). Something like this, in your own words:\n\n> Setting up Autumn. I'll connect this project to an Autumn org, write your pricing into `autumn.config.ts`, and show it to you to approve before anything is pushed.\n\nThen:\n\n1. Run `atmn init` from the project root with the user's package manager (`bunx atmn init`, `pnpm exec atmn init`, `yarn atmn init`, `npx atmn init` — read it off the lockfile). Every `atmn …` command below means that run command. One command does the whole connect step: it adds `atmn` as a dependency, places the config in `autumn/` (its own package in a monorepo — it asks where, or takes `--path` and `--name`), pulls whatever the org already holds, and installs these skills beside the config. Each run prints what it did and, when it needs an answer, the flag to pass; run it again with the flag.\n2. Key already there → `init` says who it's connected to and moves on. Say so in one line.\n3. No key → `init` stops and asks how to connect. Ask the user the same thing, one question, two options, plain words:\n\n > Two ways to start: sign in to an Autumn account (I'll open a browser), or go keyless — I set up a sandbox for you right now and you link an account later. Which do you want?\n\n Always ask this one, even if the user told you to handle everything yourself — a keyless org has no owner until they link it, and that is their call, not yours. Pick keyless without asking only when there is genuinely nobody in the chat to answer (an unattended run), and say in one line that you did.\n4. Connect the way they chose, by running `init` again with the flag:\n\n - **Sign in** → say a browser window is coming, then `atmn init --login`. It opens the browser to sign in and create or pick an org, prints the sign-in URL, and waits — that's normal, it's not stuck. If the browser doesn't open (SSH, sandbox), that's not a failure: send the user the printed URL as-is and wait. Keys get saved to `.env`. Fails → retry once, then stop and tell the user what failed. Offer the ways forward — go keyless, or they copy their own sandbox key from app.useautumn.com into `.env` as `AUTUMN_SECRET_KEY` — and wait for their answer. Never switch to keyless on your own.\n - **Keyless** → `atmn init --keyless`. It provisions a sandbox org and saves its key to `.env` as `AUTUMN_SECRET_KEY`. No account, no browser, nothing for the user to do. The org is a real one: pushing, customers, and billing all work the same. It has no owner until Phase 7 links one, and the key doesn't change when that happens. `init` prints the deadline for linking; note it for Phase 7. For what provisioning and linking do underneath, and their limits, read `references/keyless.md`.\n\n5. `init` pulled the org's catalog into the config. If plans showed up (\"Pulled N entries\"), say so and go through them with the user before changing anything. A brand-new or keyless org is empty; the config is a scaffold for Phase 4 to fill.\n\nDone when `init` finished and you know whether the org already has plans. Say so in one line — including whether it's keyless, since that decides how you finish.\n\n## Phase 3 — Get a starting point\n\nYou need the pricing in the user's own words — not the details, just what to build.\n\n- They already told you (their message, a pricing page, the README) → repeat it back in one or two sentences and move on. If they gave you everything, ask nothing.\n- They haven't → ask what they're building and what they want to charge. A pricing page, or a competitor's page they like, is a full answer too.\n\nDon't dig into details here — what to ask, what to assume, and how to handle unclear pricing is `autumn-catalog`'s job, next phase.\n\nDone when you have rough plans and prices to build from.\n\n## Phase 4 — Model, write, approve\n\nLoad `autumn-catalog` now — from here it owns the conversation: its Shape/Fill flow, its checklist (shown instead of this skill's), its questions, its catalog display, and the approval. Your speaking rules above still apply to its questions.\n\nThis whole phase is the catalog skill's: structure agreed → config written and valid → pricing shown in its format → user approved. Only come back here when that's done; don't push yet.\n\n## Phase 5 — Push\n\nOnly after the yes. Push with `atmn` using `autumn-catalog`'s atmn reference — it has the exact commands, flags, and versioning choices. If the push asks for decisions (new version of a live plan, deleting things), bring them to the user; never decide alone. Check the push output shows every plan and feature made it — a push that errored is not done, and after two failed fixes you stop and show the error.\n\n## Phase 6 — Get it working in the app\n\nPricing in a sandbox is invisible. The user believes Autumn works when their own app creates a customer, blocks something, and takes money — so go there next, in one short pass, before anything else gets built.\n\nSay what's now live (plans and features, which org, sandbox) and where the config file is, then offer it:\n\n> Plans are live. Want me to wire up the basics now — customers created on signup, buying a plan, one feature gated / tracked — so you can see it running in your app?\n\nThey say no → skip to Phase 7. They say yes, or already asked for the integration up front → this phase runs. Either way it's their call; never start editing app code unasked.\n\nScope it before handing over. The first pass is the thinnest thing that proves the loop, and nothing else:\n\n- a customer created where the app already knows who the user is\n- buying a plan: the main free → paid move, so real money moves once\n- `check` and `track` on the **one** feature that matters most — ask which if it isn't obvious, don't gate everything\n\nAnything else — every remaining feature, entities, seats, billing controls, the billing page — waits for a second pass. Say that out loud when you scope, so the user doesn't read a small first pass as a small integration.\n\nLoad `autumn-integrate` now with that scope — from here it owns the conversation and the code: its order of operations, its checklist, its verification. Your speaking rules above still apply.\n\nCome back when its verification passes: a customer that exists in sandbox, a check that denied, usage that landed, a plan that attached.\n\n## Phase 7 — Link the account (keyless only)\n\nSkip this entirely if the user signed in — they already own their org.\n\nOffer once, right after they've seen the integration work, because that's when there's something worth logging in to look at:\n\n> Want to link this to your account? I’ll create a secure sign-in link so you can claim the org and open everything that just ran in the dashboard.\n\nNo → fine, drop it and say the offer stands whenever. Yes → ask which email to send the link to, run `atmn login --claim <email>`, and give them the returned claim URL. Autumn also emails that same URL — say so, naming the address, so they know where to find it later (\"Also sent to you@example.com\"). Whoever opens the link and signs in becomes the owner — the email is only where the link is delivered, so treat the link like the key and give it to the user alone. Linking makes them the owner: same key, same plans, same customers, plus the dashboard. An account that already exists works the same way; the new org is added beside their existing ones and becomes active after confirmation.\n\nUnclaimed orgs don't wait forever, so mention the window `init` printed when you offer — as a fact, not a threat. If linking fails, nothing is lost: the key keeps working and they can try again, or sign up normally and push the same config. What the commands do underneath: `references/keyless.md`.\n\n## Phase 8 — Done\n\nThree lines: what's live and working, where it lives (`autumn.config.ts`, and the handlers if Phase 6 ran), and the obvious next thing — the rest of the features, or going to production when they're ready. A linked or signed-in user can see it all at app.useautumn.com; a keyless user who declined can't, so don't send them there.\n\nThen stop. Don't keep building, don't tour the dashboard, don't deploy anything.",
|
|
27
|
+
references: [
|
|
28
|
+
{
|
|
29
|
+
path: "references/keyless.md",
|
|
30
|
+
contents:
|
|
31
|
+
"# Keyless orgs\n\nA keyless org is a real sandbox org with no owner yet. One call creates it and hands back a secret key; pushing a catalog, creating customers, and billing all work normally. The user links their account later, and the key stays the same.\n\nThe CLI does both: `atmn init --keyless` (or `atmn login --keyless`) provisions, and `atmn login --claim <email>` creates the browser claim flow. Use those. The HTTP calls below are what they do underneath, for when you need to know the fields or limits. Base URL is `https://api.useautumn.com`. The same flow, in the [auth.md](https://workos.com/auth-md) convention agents discover on their own, is at `https://useautumn.com/auth.md`.\n\n## Provision\n\n`POST /agent.provision`\n\nSend `name` and `slug`, both derived from the project (repo name, or the `name` in package.json). The slug is lowercase letters and numbers, with `-` or `_` between words.\n\nBack comes `organization_id`, `organization_slug`, `api_key`, `claim_token`, and `claim_expires_at`.\n\n- Write `api_key` into `.env` as `AUTUMN_SECRET_KEY`. It is a sandbox secret key — never print it or read it back into the chat.\n- `claim_token` works only on `/agent.start_claim`, as an alternative to the key for that one call. It is not an API credential — it can't read or write catalogs, customers, or anything else. Claiming with the key is simpler, so normally you can ignore it. It is still as secret as the key.\n- `claim_expires_at` is the deadline for linking — a few days out. Read it from the response instead of assuming; after it passes, the org can't be linked to anyone.\n- Provisioning is rate limited per machine. If it fails, tell the user and offer sign-in — never loop on it.\n\n## Claim the org\n\n`POST /agent.start_claim` with an `Authorization: Bearer <AUTUMN_SECRET_KEY>` header and an `email` in the body. Autumn returns `claim_url` and `expires_at`, and emails the same URL to that address.\n\nGive the URL to the user in the chat as well, and tell them which address it was emailed to. They open it, sign in with any account, review the organization, and confirm the claim. Autumn then adds that account as owner and switches their dashboard to the claimed organization.\n\nThe email is only where the link is delivered — it is not checked at claim time. The link itself is the credential: anyone who opens it and signs in owns the org, so share it only with the user.\n\nIf you're using the claim token instead of the key, send `claim_token` in the body and no `Authorization` header. Send exactly one of the two — both, or neither, is refused.\n\nNotes that matter:\n\n- The browser link expires in minutes. If it expires, call `/agent.start_claim` again to replace it. Only the newest link works.\n- Never ask the user for a verification code; completion happens in the browser.\n- The same URL is emailed, so the user still has it if the agent session is lost.\n- The provisioned key keeps working after linking. Don't rotate it, don't provision a second org.\n- Linking an org that's already linked, or past its deadline, fails on purpose. These errors are deliberately vague so they can't be probed — don't guess at what went wrong, just tell the user it didn't go through and what you'll try next.",
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "autumn-catalog",
|
|
37
|
+
description:
|
|
38
|
+
"Modeling a user's pricing into an Autumn catalog — deciding the structure (plans, variants, add-ons, licenses, credit systems, pooled balances) before writing config, then filling in the numbers. Use when the user describes their pricing or asks to model, change, or push a catalog.",
|
|
39
|
+
version: "2.0.0",
|
|
40
|
+
markdown:
|
|
41
|
+
'---\nname: autumn-catalog\ndescription: Modeling a user\'s pricing into an Autumn catalog — deciding the structure (plans, variants, add-ons, licenses, credit systems, pooled balances) before writing config, then filling in the numbers. Use when the user describes their pricing or asks to model, change, or push a catalog.\nversion: 2.0.0\n---\n\n# Catalog\n\nBefore using this skill, first load the `autumn-concepts` skill — it defines Autumn\'s data model — features, plans, plan items, balances — which every modeling decision builds on.\n\n## STRICT RULES — re-read before every config write\n\n1. **Amounts are in major units (e.g. dollars), never minor units (cents).** $180/month is `amount: 180`. $0.01 per credit is `amount: 0.01`. If any amount you wrote is 100× the user\'s number, it is wrong.\n2. **Never invent a price, limit, or plan name** — a missing number is a question.\n3. **One definition per real thing.** One feature per resource, one child plan per license pattern (parents customize their license, never get their own copy), one add-on per offer (sizes are tiers, not plans).\n\n**Building, or iterating on a live catalog?** What matters is whether customers are on these plans — not whether a config file exists. Still setting up (even across sessions, with a half-built `autumn.config.ts`) → the workflow below; edit the draft freely. Already running Autumn with customers, now changing prices/plans → that\'s an update with real stakes (versioning, migrations, grandfathering) — read `references/catalog-update.md` first. Unsure → check for customers (`atmn pull` / the org) or ask.\n\nTurning pricing into a catalog is two jobs:\n\n- **Shape** — decide the structure: which plans exist, what\'s a variant, what\'s an add-on, where balances live. Decided by relationships in their pricing, not by amounts.\n- **Fill** — put in the numbers and per-item details, then write and validate the config.\n\nDo Shape fully before Fill. Take numbers whenever the user mentions them, but never chase numbers during Shape — the one exception is "is this the same on every plan?", which is a structure question.\n\n## Progress (what the user sees)\n\nCopy this checklist into your first message and keep it up to date. It is the **only** structure the user ever sees — never say "step", "pass", "decide", or "fork" to them. If you skip an item, say why in one line.\n\n- [ ] 1 Your plans and prices\n- [ ] 2 What\'s included in each plan\n- [ ] 3 How billing behaves (signup, trials, limits)\n- [ ] 4 Licenses — paid seats, workspaces, projects (if any)\n- [ ] 5 Structure agreed\n- [ ] 6 Config written and checked\n\n## How to ask\n\n- One topic per message. Two questions at most, and only if both belong to that topic. Never mix topics in one message.\n- If one ambiguity changes which other questions apply, resolve it first before asking those.\n- Attach your guess to each question — "does the trial need a card? I\'d guess no" — a wrong guess gets corrected faster than a blank gets answered.\n- Assert the obvious instead of asking. "500 messages a month" resets monthly — state it as an assumption, don\'t ask. Questions are only for facts that change the structure and can\'t be guessed.\n- Skip anything they already told you. A good message to answer is a nod, not homework.\n- Never announce what you\'ll do next ("once I get those three, I\'ll restate…") — just work the current topic.\n- The restate (step 5) is the safety net: wrong assumptions get caught there cheaply, which is what makes fewer questions safe.\n\n## Shape: collect, then decide\n\nWork the four steps below in order, one at a time — each says when it\'s done. While collecting, make only the small calls each step allows; leave the big ones (marked ↦ Decide) for after.\n\n### Step 1 — Plans\n\n- What are the plans? ("Free, Pro $20/mo, Growth $50/mo")\n- Free tier? → a plan with no price that every new customer starts on automatically.\n- Anything bought *alongside* a plan rather than instead of it (packs, extra storage)? → note it ↦ Decide (add-on).\n- Enterprise tier? Ask. If it exists, model the base enterprise plan in the catalog, and tell the user: custom terms per customer (special prices, custom limits) are applied later when attaching, not modeled here.\n\nDone when you can list every plan they sell, including free, add-ons, and enterprise.\n\n### Step 2 — What each plan includes\n\nHave them describe what they charge for or limit, in their own words. Pick a type per feature:\n\n| They say | Feature type |\n|---|---|\n| "Pro has SSO" | boolean (on/off) |\n| "500 messages a month" | metered, resets |\n| "10 team members" | metered, no reset (held, not used up) |\n| "credits" / "tokens" / "wallet" | credit system — always, even if only one action uses it today. They will add more actions; a single mapped action is fine. Their app tracks the actions (chat, image), never the credit balance itself. |\n\nNote top-ups ("buy more when you run out", "auto-recharge") ↦ Decide (top-up placement).\n\n### Step 3 — How billing behaves\n\nThis step usually means explaining Autumn to the user in plain words. Do.\n\n- **Signup**: every new customer automatically gets the default plan — usually the free one. One default per group. A default can carry no prices at all — a "$0" plan with paid items (per-seat charges, prepaid packs) is a paid plan, not a default. If every plan bills something, there is no default; customers subscribe.\n- **Trials**: ask "does the trial need a card?" (guess from their motion — PLG usually no). Card → `free_trial` on the paid plan. No card → a **separate free trial plan** (`pro_trial`: no price, the paid plan\'s items, auto-enabled when everyone starts on it); the paid plan stays untouched, since a default plan can never be paid. For trial modeling details — trial-behavior on plans, read `references/plan.md` in the `autumn-concepts` skill.\n- **What is a plan attached to?** The customer, or each thing they own (workspace, project, site)? "Pro is $200 per workspace" → attached per entity. Pin this down — it changes everything downstream.\n- **Who uses each metered feature?** The customer as a whole, or each entity? If entities: one shared balance or separate ones? "Shared across…" → shared ↦ Decide (balances).\n\nDone when you know what attaches where, who consumes what, and how trials and signup work.\n\n### Step 4 — Licenses\n\nTriggered whenever the customer pays per unit of some entity — seats, workspaces, projects, sites, members: "each X is $10/month", "comes with 3 X". When you see one, always ask: **what does one X come with?** Never skip this because the user didn\'t say "seat". For modeling a license — the concept: child plan, license link, customize, read `references/licenses.md` in the `autumn-concepts` skill.\n\n- Nothing of its own ("$10 per seat", just a count) → a per-unit priced item on the plan. No entities. The common case.\n- The unit gets something of its own ("each seat gets 100 credits", "every workspace has its own allowance") → a **license**: a small plan of its own that the parent plan hands out per unit.\n- Units must be assigned, reassigned, or sit empty → also licenses. Rare — confirm they need it.\n\nFor deciding between a per-unit item, licenses, and entity-attached plans for a countable paid unit, read `references/fork-licenses.md`.\n\n### Before deciding: restate\n\nOnly after your questions are answered — never announce it in advance, and never restate facts nobody has confirmed yet. One short message: the plans, what\'s metered, what attaches where, who shares what. Let the user correct it. A wrong fact here is much cheaper than a wrong structure later.\n\nIf the user gave you everything up front and you asked nothing, skip the separate restate — the structure message (checklist item 5) does that job.\n\n## Decide\n\nResolve these with all facts in hand. Each has a default — when the facts genuinely don\'t settle it, show both options in one line each and ask.\n\n**Variant or separate plan?**\nA variant can change the price, swap items in or out, and change the trial — nothing else.\n\n- "Pro monthly / Pro annual, same features" → variant. (Annual usually still resets allowances monthly — billing and reset intervals are independent. Confirm.)\n- Volume buckets/tiers — one question decides: **is each bucket the subscription itself (→ a variant per bucket) or a purchase on top of one (→ a prepaid item)?**\n- Different features per tier → separate plans. One plan per tier is normal, not a smell.\n\nFor deciding whether volume buckets/tiers are variants of the plan or one prepaid volume-tiered item — the tells, the trap, when variants are forced, read `references/fork-variants.md`.\n\n**Add-on, or part of the plan?** Two independent questions: is the purchase priced/bundled per plan (→ item on each plan) or one offer across plans (→ one add-on plan, sizes as tiers on its prepaid item)? And does a plan exist at the level its balance is shared at (→ item there) or are all plans entity-attached (→ a customer-level add-on is forced)? Auto-recharge needs the prepaid item to exist — add it in Shape, it\'s structural.\n\nFor modeling packs, top-ups, or any purchase bought on top of a plan — add-on vs plan item, and what level it sits at, read `references/fork-addon.md`.\n\n**Where do balances and purchases live?**\nThe rule: **purchases and balance at the customer; usage tracking and caps at the entity.** Grants "shared across…" entities → pooled (`pooled: true` on the item); separate per-entity balances → no pooling; overage stays on the entity\'s plan either way.\n\nFor deciding where balances and purchases live — pooled grants, per-entity balances, customer-level packs, overage placement, read `references/fork-pooled.md`.\n\n**Groups?**\nCan one customer hold two plans at once from different lines (a support plan AND a sales plan)? → one group per line. Within a group, attaching a plan replaces the current one; that\'s what makes upgrades work.\n\n**One-off?**\n"Lifetime deal", "one-time pack" → a plan with a one-off price: single invoice, no subscription, balances never reset.\n\n## Check\n\nCompare the structure against the shapes in `references/cases.md`. If their pricing matches a known shape but your structure differs, either say why or fix it.\n\nShortcuts that are usually wrong — catch yourself before Show:\n\n| You\'re thinking | Check first |\n|---|---|\n| "seats + credits → licenses" | Are the credits per seat, or one shared pot? Shared → per-unit seats + pooled balance, no licenses. |\n| "annual pricing → separate plan" | Same features? → variant. |\n| "tiers → one plan with tiers" | Does overage or anything else differ per tier? → plan per tier. |\n| "they said credits but it\'s one action → plain meter" | Credits are always a credit system. |\n| "packs belong on the plan" | Are they shared across entities or plans? → add-on. |\n| "everyone starts on a Pro trial → trial + auto-enable on Pro" | A default plan can never be paid. → separate free `pro_trial` plan (Pro\'s items, no price, auto-enabled); Pro untouched. |\n| "several top-up sizes → one add-on plan per size" | Do the sizes differ only in quantity and price? → volume tiers on one prepaid item, one add-on plan. |\n| "the pack is priced per plan → an add-on per plan" | Per-plan pricing IS plan differentiation → a prepaid item on each base plan, no add-ons. Add-ons are for one offer shared across plans, or when no plan exists at the shared level. |\n| "the seat differs per plan → one seat plan per parent" | ONE child plan carrying the mainline take; each differing parent\'s license carries its own diff via `customize`. Never a `<parent>_seat` plan per parent. |\n\nFor checking the derived structure against known-good shapes, read `references/cases.md`.\n\n## Show\n\nPresent the structure in the "Showing the catalog" format below — the same one used for every catalog display, catalog inside a fenced code block. Numbers you don\'t have yet stay open, never invented: write the line without them ("AI messages per month — amount TBD"). Structure notes go in parentheses on the line they describe. The whole message:\n\n````\nHere\'s the structure I\'d build:\n\n```\nFeatures: AI credits (credit system — chat and image messages draw from it) · SSO (on/off)\n\nFree — no price, everyone starts here\n - 100 AI credits per month\nPro — $20/month, or annual (same features)\n - AI credits per month — amount TBD\n - SSO\nCredit pack (add-on) — price TBD, shared across all workspaces\n```\n\nI assumed: credits reset monthly, no rollover. Anything wrong?\n````\n\nList every assumption. Get a clear yes — "sounds good" without reading is not a yes. But if the user already told you to go ahead without review ("no need to ask", "just build it"), show the structure and keep going — don\'t stop to wait. If a late fact changes the structure, redo the affected decision, update the structure, and show what changed in one line.\n\n## Fill\n\nStructure agreed — now finish it. Four moves, in order.\n\n**1 — Fill in what\'s known.** Everything the user already said goes straight into the draft. Never re-ask a confirmed fact.\n\n**2 — Ask for missing essentials.** Values with no sane default: base prices, included amounts, per-unit prices, tier boundaries, trial length. Never invent one — a missing number is a question. Batch per the how-to-ask rules.\n\n**3 — Sweep the options.** Ask each of these **once for the whole catalog**, in plain words — never item by item. The answer distributes to every item it touches ("carry over on both plans, or just Growth?" only if they hint at a difference). Raise a bucket only when the structure makes it relevant; skip anything already answered.\n\n- **Carry-over** — any allowance that resets: "Should unused messages carry over month to month, or reset clean?"\n- **Running out** — any metered feature: "When they run out — hard stop, or keep going and bill the extra?" (often settled in Shape; skip if so)\n- **Top-ups** — credits present or buying-more mentioned: "Can they buy more before the reset?" → a one-off prepaid item\n- **Guardrails** — any plan with overage or heavy usage: "Any caps on how fast or how far usage can run — like a daily limit, or a ceiling on overage? Want customers warned as they approach limits?" → plan-level billing controls\n- **Anything else on/off** — always, it\'s cheap: "Any other on/off differences between plans — SSO, priority support, API access?"\n\nThe sweep exists so the user hears what\'s configurable without being marched through every item. Behind it, check every knob yourself — this list is internal, never show it:\n\n- per plan: base price · trial (length, unit, card — all explicit) · default plan · group · billing controls (guardrails answered → `billingControls` on the plan)\n- per item: billing method · included · price or tiers (tier behavior explicit) · reset · rollover · pooled (if Shape chose shared balances) · purchase caps · the one-off item auto-recharge needs\n\nLanding a guardrail answer means picking the right control — windowed cap vs overage ceiling vs alert vs allow-past-balance vs auto-recharge are different knobs with different fields. What each one is, the three overage knobs, and the plan→customer inheritance are the `autumn-concepts` skill\'s billing-controls reference — read it before writing `billingControls`. The flow rules here:\n\n- Plan-level controls are defaults every subscriber inherits — the right home for anything true of the whole plan ("free users max 20 emails/day"). Per-customer exceptions are a billing/customer operation, not catalog work.\n- A cap stated alongside an allowance ("1,000 a month but never more than 20 a day") is a usage limit on the plan, not a second item or a smaller allowance.\n- "Track it but don\'t bill it" / "let them run over, we\'ll invoice manually" → overage knobs on the plan, not a $0 price.\n- Auto-recharge needs its one-off prepaid item (already on the per-item list) AND the `autoTopups` control.\n\n**4 — Propose, then finalize.** One message: the full catalog in the format below, then "I assumed:" listing every knob you defaulted. Fold corrections in. Then write the config — and before saving, re-read every amount in it: dollars, never cents ($600 is `600`, not `60000`). Validate with `atmn push` (a preview; nothing is applied until `--yes`), fix what it flags, and show the final catalog — same format, no assumptions list. **Done means the config is written and valid — a summary is not done.**\n\n### Showing the catalog\n\nUse **exactly this format** every time you show the catalog — the Show structure message, the Fill proposal, and the done message alike. Never a markdown table, never a different layout per message. Send it as a fenced code block (```) so the indentation survives markdown rendering — plan names on bare lines otherwise get folded into the previous plan\'s list. It\'s the same grammar the dashboard renders. Features first with their kind, then plans, one line per item:\n\n```\nFeatures: AI messages (usage, resets) · Seats (held, not used up) · Credits (credit system — 1 message = 1 credit) · SSO (on/off)\n\nPro — $20/month\n - 500 AI messages per month\n · unused carry over, up to 500, for 1 month\n - 500 AI messages per month, then $0.01 per message\n - $10 per 1,000 credits\n - 5,000 credits for $50 per month\n - 3 seats included, then $10 per seat\n - $18 - $15 per seat\n - Unlimited projects\n - SSO\n - 100 credits per seat per month\nTeam — $500/month\n - 5 seats included, then $40 per seat per month\n each seat gets:\n · 100 summaries per month\n · SSO\nCredit pack (add-on) — $10 for 1,000 credits, buy anytime\n```\n\nThe Features line names every feature and its kind in plain words: `(usage, resets)` for consumable meters, `(held, not used up)` for non-consumable ones like seats, `(credit system — …)` with its action mappings, `(on/off)` for boolean. Item lines, top to bottom: plain allowance · allowance with overage · pure usage price (per billing unit) · prepaid bucket · included + prepaid per-unit · tiered rate shown first-to-last · unlimited · boolean (name only, never "enabled") · per-entity grant · one-off add-on. Numbers get commas; "then …" says what happens after the included runs out; annual variants go inline ("or $200/year — messages still reset monthly").\n\nConfigured item properties — carry-over, purchase caps, top-up behavior — are never their own `-` lines: indent them under the item they belong to as `·` lines, one behavior each, so the hierarchy is visible. Only show what\'s configured; defaults (like usage simply stopping when no overage price is set) get no line.\n\nBilling controls follow the same rule: `·` lines under the item they guard, in plain words — `· max 200 per day` · `· overage capped at 20% over` · `· overage tracked, not billed` · `· warned at 80%` · `· auto-buys 1,000 credits when below 100`. Never the schema names (usage_limits, spend_limits) in user-facing text.\n\n### Config gotchas\n\n- Amounts are plain dollars everywhere — base prices, tier `flatAmount`s, unit prices: $20 is `20`, never `2000`, and a $100 tier is `flatAmount: 100`, never `10000`. Re-check every number before writing; cents is the most common wrong config.\n- A default/auto-enabled plan can\'t be paid: no base price, no paid items.\n- A prepaid quantity **includes** the included amount, and so does each tier\'s `to` — the first tier\'s `to` must exceed `included`.\n- `billingUnits` rounds usage **up** when billing.\n- Set explicitly, never lean on defaults: `billingMethod` and `interval` on every item price, `tierBehavior` on tiered prices, and every trial field (`durationLength`, `durationType`, `cardRequired`, `onEnd`). Explicit defaults cause no spurious diffs.\n- Volume tiers charge the flat amount of the reached tier and are prepaid-only; graduated (the default) sums across brackets.\n- Rollover needs a resetting allowance; `max` and `maxPercentage` are mutually exclusive; `expiryDurationType` is required.\n- `billingControls` is a plain object on the plan with camelCase fields like the rest of the config (`featureId`, `overageLimit`); each control list replaces wholesale on update.\n- Pooled balances are config: `pooled: true` on the entity plan\'s item. Concluding "shared across workspaces" in Shape and then omitting the flag is the classic miss.\n- Pooled grant + overage = two items on the plan: the pooled grant carries no price; a separate usage-priced item (`included: 0`) carries the overage. A pooled item can\'t itself be usage-priced.\n- Don\'t write `proration` — leave it out and take server defaults.\n- Trial end behavior is `freeTrial.onEnd`: `"bill"` (default) charges when the trial ends, `"revert"` expires it and restores the previous plan.\n- Every plan row carries `versionSlug` and `active`, and every variant row and license link `versionSlug`. A plan\'s rows are its versions — exactly one `active: true` — and a version row left out of `plans` is deleted. How rows express versions, renames and drafts: `references/atmn.md`.\n\nThe config uses the builders `feature`, `plan`, `variant`, `license` as plain function calls with object arguments; items are plain objects inside a plan, and the file\'s default export is `atmn({...})` naming every collection. Never guess other functions or fields; the full shapes are in `references/atmn.md`.\n\n```ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const credits = feature({\n featureId: "credits",\n name: "Credits",\n type: "credit_system",\n creditSchema: [{ meteredFeatureId: "messages", creditCost: 1 }],\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n { featureId: credits.featureId, included: 500, reset: { interval: "month" } },\n ],\n});\n\nexport default atmn({ features: [credits], plans: [pro] });\n```\n\nPattern deep-dives, split one file per pattern under `references/` — read the matching one when filling that pattern\'s details:\n\nFor usage overage pricing, read `references/usage-based-pricing.md`.\nFor prepaid quantities (seats, packs, buckets), read `references/prepaid-pricing.md`.\nFor volume-tiered prices, read `references/volume-based-tiers.md`.\nFor $X per unit, read `references/per-unit-pricing.md`.\nFor billing vs reset intervals, read `references/recurring.md`.\nFor one-off purchases / top-ups, read `references/one-off-purchases.md`.\nFor auto-recharge, read `references/auto-top-ups.md`.\nFor carry-over, read `references/rollovers.md`.\nFor trial details, read `references/trials.md`.\nFor entity plans and license attach flows, read `references/entity-plans.md`.\nFor credit schemas and mappings, read `references/credit-systems.md`.\nFor default plans / auto-enable, read `references/free-plans.md`.\nFor add-on balance stacking, read `references/add-ons.md`.\nFor what variants can change, read `references/plan-variants.md`.\n\n## Conduct\n\n- In an existing config, match its patterns: if sibling plans carry their prepaid purchases as items, the new plan does too — don\'t introduce a different structure for the same kind of thing.\n- Stable lowercase IDs with underscores: `pro_plan`, `chat_messages`.\n- `entityFeatureId` is deprecated. Never mention or use it unless the user\'s existing config already has it.\n- Per-unit pricing pairs a base fee with the per-unit item ("$X/seat" plans still have a base price, even $0).\n- Speak plainly: "plans", "what\'s included", "extra usage". Schema words stay in the config — say "carry over" not "rollover", "shared across workspaces" not "pooled", "paid upfront" / "billed at month end" not "prepaid" / "usage_based".\n- Never volunteer what Autumn can or can\'t do. Don\'t offer options Autumn can\'t model, and don\'t explain limitations unprompted — only address one when the user directly asks to model that specific thing, and even then lead with the closest thing that works.\n- Start simple: the most important features first, confirm before adding more.\n\nBefore finishing: re-check the STRICT RULES at the top against the config you wrote.\n\n## Catalog operations\n\nFor using atmn, autumn.config.ts, or headless push flows, read `references/atmn.md`.\n\nFor changing an existing catalog: previewing, versioning, migrations, variant propagation, read `references/catalog-update.md`.\n\nFor creating or changing coupons, promo codes, feature grants, or referral programs, read `references/rewards.md`.',
|
|
42
|
+
references: [
|
|
43
|
+
{
|
|
44
|
+
path: "references/usage-based-pricing.md",
|
|
45
|
+
contents:
|
|
46
|
+
'## Usage-Based Pricing\n\nPay-per-use (usage-based) pricing charges customers based on how much of a feature they actually consume, billed at the end of each billing period. This is ideal for products where usage varies significantly between customers.\n\n> **Example** <br />\n> A notification service charges $1 per 1,000 notifications sent. A customer who sends 5,000 notifications in a month pays $5 at the end of that month.\n\n## Setting up\n\nCreate a consumable feature with a `usage_based` price:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const notifications = feature({\n featureId: "notifications",\n name: "Notifications",\n type: "metered",\n consumable: true,\n});\n\nexport const payAsYouGo = plan({\n planId: "pay_as_you_go",\n versionSlug: "v1",\n active: true,\n name: "Pay As You Go",\n group: "main",\n items: [\n {\n featureId: notifications.featureId,\n included: 1000,\n reset: { interval: "month" },\n price: {\n amount: 1,\n interval: "month",\n billingUnits: 1000,\n billingMethod: "usage_based",\n },\n },\n ],\n});\n\nexport default atmn({ features: [notifications], plans: [payAsYouGo] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## How it works\n\n1. A customer\'s usage is tracked via the [track](/documentation/customers/tracking-usage) endpoint throughout the billing period\n2. Usage first draws down from the **included** amount (if any) at no charge\n3. Usage beyond the included amount is **overage** — billed at the configured rate\n4. At the end of the billing period, Autumn generates a Stripe invoice for the total overage\n\nUsage-based features allow overage by default. The `check` endpoint will return `allowed: true` even if the customer has exceeded their included balance, as long as a usage-based price is configured.\n\n## Tracking usage\n\nTrack usage as it occurs — Autumn accumulates it over the billing period:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nawait autumn.track({\n customer_id: "user_123",\n feature_id: "notifications",\n value: 500,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nawait autumn.track(\n customer_id="user_123",\n feature_id="notifications",\n value=500,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "notifications",\n "value": 500\n }\'\n```\n\n</CodeGroup>\n\n## Checking access\n\nCheck if the customer can use the feature. For usage-based features with overage, `allowed` is `true` as long as the feature exists on the customer\'s plan:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst { data } = await autumn.check({\n customer_id: "user_123",\n feature_id: "notifications",\n});\n\nconsole.log(data.allowed); // true (overage allowed)\nconsole.log(data.balance);\n```\n\n```python Python\nresponse = await autumn.check(\n customer_id="user_123",\n feature_id="notifications",\n)\n\nprint(response.allowed) # True (overage allowed)\nprint(response.balance)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "notifications"\n }\'\n```\n\n</CodeGroup>\n\n```json\n{\n "allowed": true,\n "customerId": "user_123",\n "balance": {\n "featureId": "notifications",\n "granted": 1000,\n "remaining": -500,\n "usage": 1500,\n "unlimited": false,\n "overageAllowed": true,\n "nextResetAt": 1757192635393\n }\n}\n```\n\n## Combining with free tiers\n\nA common pattern is pairing usage-based pricing with a [free plan](/documentation/modelling-pricing/free-plans). Free users are blocked when they exceed their limit, while paying users are billed for overages.\n\n| Plan | Over limit | Result |\n|------|------------|--------|\n| Free | Yes | Blocked (`allowed: false`) |\n| Pay-as-you-go | Yes | Allowed, billed at end of period |',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: "references/prepaid-pricing.md",
|
|
50
|
+
contents:
|
|
51
|
+
'## Prepaid Pricing\n\nPrepaid pricing lets customers pay for a fixed quantity of a feature upfront. They select how many units they want at purchase time, pay immediately, and their balance is decremented as they use it.\n\nThis is in contrast to [usage-based pricing](/documentation/modelling-pricing/usage-based-pricing), where customers are billed for actual usage at the end of a billing cycle.\n\n> **Example** <br />\n> An AI platform has a Pro plan at $20/month that includes:\n> - **API Credits**: 500 included for free, then $10 per 1,000 credits per month (consumable)\n> - **Seats**: 3 included for free, then $5 per seat per month (non-consumable)\n>\n> A customer selects 3,000 credits and 10 seats. They pay $20 base + $25 for 2,500 extra credits + $35 for 7 extra seats = $80/month.\n\n## Setting up\n\nCreate your features and add them to a plan with `prepaid` prices:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const apiCredits = feature({\n featureId: "api_credits",\n name: "API Credits",\n type: "metered",\n consumable: true,\n});\n\nexport const seats = feature({\n featureId: "seats",\n name: "Seats",\n type: "metered",\n consumable: false,\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: apiCredits.featureId,\n included: 500,\n price: {\n amount: 10,\n billingUnits: 1000,\n billingMethod: "prepaid",\n interval: "month",\n },\n },\n {\n featureId: seats.featureId,\n included: 3,\n price: {\n amount: 5,\n billingMethod: "prepaid",\n interval: "month",\n },\n },\n ],\n});\n\nexport default atmn({ features: [apiCredits, seats], plans: [pro] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## How it works\n\nWhen a plan has prepaid features, customers select a **quantity** at purchase time. This quantity determines:\n\n- **How many units are granted** as their balance\n- **How much they\'re charged**, based on the price and billing units\n\nThe `quantity` is the **total** number of feature units the customer will receive, including any included amount.\n\nUsing our example plan:\n- A customer selects **3,000 API credits**. 500 are included, so they pay for 2,500 → $10 × (2,500 / 1,000) = **$25/month** for credits.\n- The same customer selects **10 seats**. 3 are included, so they pay for 7 → $5 × 7 = **$35/month** for seats.\n\nIf you pass a `quantity` equal to or less than the included amount, the customer gets the included amount and pays nothing extra for that feature.\n\n## Passing `feature_quantities`\n\nWhen attaching a plan or updating a subscription that contains prepaid features, use the `feature_quantities` parameter to specify how many units the customer wants.\n\n### Attaching a plan\n\nPass a `feature_quantities` entry for each prepaid feature on the plan:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nconst { data } = await autumn.billing.attach({\n customerId: "user_123",\n planId: "pro",\n featureQuantities: [\n { featureId: "api_credits", quantity: 3000 },\n { featureId: "seats", quantity: 10 },\n ],\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nresponse = await autumn.billing.attach(\n customer_id="user_123",\n plan_id="pro",\n feature_quantities=[\n { "feature_id": "api_credits", "quantity": 3000 },\n { "feature_id": "seats", "quantity": 10 },\n ],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing/attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "feature_quantities": [\n { "feature_id": "api_credits", "quantity": 3000 },\n { "feature_id": "seats", "quantity": 10 }\n ]\n }\'\n```\n\n</CodeGroup>\n\n### Updating a subscription\n\nTo change prepaid quantities on an existing subscription, use `billing.update`. For example, to add more seats mid-cycle:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.billing.update({\n customerId: "user_123",\n planId: "pro",\n featureQuantities: [\n { featureId: "api_credits", quantity: 3000 },\n { featureId: "seats", quantity: 15 },\n ],\n});\n```\n\n```python Python\nawait autumn.billing.update(\n customer_id="user_123",\n plan_id="pro",\n feature_quantities=[\n { "feature_id": "api_credits", "quantity": 3000 },\n { "feature_id": "seats", "quantity": 15 },\n ],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "feature_quantities": [\n { "feature_id": "api_credits", "quantity": 3000 },\n { "feature_id": "seats", "quantity": 15 }\n ]\n }\'\n```\n\n</CodeGroup>\n\nSee [Updating Subscriptions](/documentation/customers/updating-subscriptions) for more on previewing changes. When quantities change mid-cycle, Autumn can prorate the charge — see [Proration](/documentation/modelling-pricing/proration) for configuration options.\n\n## Understanding prepaid balances\n\nOnce a customer is attached to a plan with prepaid features, their balance `breakdown` distinguishes between what was included for free and what was purchased.\n\n| Field | Description |\n|-------|-------------|\n| `included_grant` | The amount granted by the plan for free — the "included" amount configured on the plan item. |\n| `prepaid_grant` | The amount purchased via `feature_quantities` — the quantity minus the included amount. |\n| `granted` | Top-level total: `included_grant + prepaid_grant` summed across all breakdown items. |\n| `remaining` | How much is left to use. |\n| `usage` | How much has been consumed. |\n\nUsing the plan from our setup, a customer who attaches with 3,000 credits and 10 seats will have:\n\n```json expandable\n{\n "api_credits": {\n "feature_id": "api_credits",\n "granted": 3000,\n "remaining": 3000,\n "usage": 0,\n "unlimited": false,\n "overage_allowed": false,\n "breakdown": [\n {\n "id": "cus_ent_abc123",\n "plan_id": "pro",\n "included_grant": 500,\n "prepaid_grant": 2500,\n "remaining": 3000,\n "usage": 0,\n "reset": {\n "interval": "month",\n "resets_at": 1773851121437\n },\n "price": {\n "amount": 10,\n "billing_units": 1000,\n "billing_method": "prepaid"\n },\n "expires_at": null\n }\n ]\n },\n "seats": {\n "feature_id": "seats",\n "granted": 10,\n "remaining": 10,\n "usage": 0,\n "unlimited": false,\n "overage_allowed": false,\n "breakdown": [\n {\n "id": "cus_ent_def456",\n "plan_id": "pro",\n "included_grant": 3,\n "prepaid_grant": 7,\n "remaining": 10,\n "usage": 0,\n "reset": null,\n "price": {\n "amount": 5,\n "billing_units": 1,\n "billing_method": "prepaid"\n },\n "expires_at": null\n }\n ]\n }\n}\n```\n\nUse the [check](/documentation/customers/check) endpoint before allowing a customer to use a prepaid feature, and [track](/documentation/customers/tracking-usage) usage afterwards to decrement their balance.\n\n## Prepaid vs usage-based\n\n| | Prepaid | Usage-based |\n|---|---|---|\n| **When charged** | Upfront at purchase | End of billing cycle |\n| **Customer selects quantity** | Yes, via `feature_quantities` | No |\n| **Balance behavior** | Decremented as usage occurs | Accumulated and billed |\n| **Best for** | Credits, top-ups, seat licenses | Metered APIs, storage, bandwidth |',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: "references/volume-based-tiers.md",
|
|
55
|
+
contents:
|
|
56
|
+
'## Volume-Based Tiers\n\nVolume-based pricing uses tiers to determine a single flat charge based on the total usage volume. Unlike [graduated pricing](/documentation/modelling-pricing/graduated-pricing), where each tier has its own rate, volume-based pricing charges a single flat amount based on which tier the total usage falls into.\n\n> **Example** <br />\n> A data platform charges:\n> - 0–1,000 records: $100 flat\n> - 1,001–10,000 records: $500 flat\n> - 10,001+: $1,000 flat\n>\n> A customer who processes 15,000 records falls into the 10,001+ tier and pays a flat **$1,000**\n>\n> Compare this to graduated pricing, where each tier is charged separately and summed together\n\n## Setting up\n\nUse the `tiers` array with `tierBehavior: \'volume\'` on a plan item price:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const records = feature({\n featureId: "records",\n name: "Records Processed",\n type: "metered",\n consumable: true,\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 50, interval: "month" },\n items: [\n {\n featureId: records.featureId,\n reset: { interval: "month" },\n price: {\n tiers: [\n { to: 1000, flatAmount: 100 },\n { to: 10000, flatAmount: 500 },\n { to: "inf", flatAmount: 1000 },\n ],\n tierBehavior: "volume",\n billingMethod: "prepaid",\n interval: "month",\n },\n },\n ],\n});\n\nexport default atmn({ features: [records], plans: [pro] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## How volume-based pricing works\n\nAutumn:\n\n1. Looks at the total volume for the feature\n2. Finds the tier the total falls into\n3. Charges the flat amount for that tier\n\nVolume tiers are prepaid-only.\n\n| Total volume | Matching tier | Charge |\n|-------------|---------------|--------|\n| 500 | 0–1,000 | **$100** |\n| 5,000 | 1,001–10,000 | **$500** |\n| 15,000 | 10,001+ | **$1,000** |\n\n## Tier configuration\n\nEach tier has the following fields:\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `to` | number or `"inf"` | The upper boundary of this tier |\n| `flatAmount` | number | Flat fee charged when the total volume falls in this tier (`flat_amount` over the API) |\n| `amount` | number | Optional per-unit price applied to the total volume when this tier is the matching tier |\n\nTiers must be in ascending order by `to`. The final tier should use `"inf"`.\n\n## Combining flat and per-unit amounts\n\nEach tier can include both `flatAmount` and `amount`: a fixed fee plus a per-unit charge when that tier is the matching tier. This is useful for combining a base fee with per-unit volume pricing.\n\n```ts\nprice: {\n tiers: [\n { to: 1000, amount: 0.10, flatAmount: 0 },\n { to: 10000, amount: 0.08, flatAmount: 50 },\n { to: "inf", amount: 0.05, flatAmount: 100 },\n ],\n tierBehavior: "volume",\n billingMethod: "prepaid",\n interval: "month",\n}\n```\n\nA customer with 5,000 records would pay: (5,000 × $0.08) + $50 = **$450**\n\n## Graduated vs volume-based\n\n| | Graduated | Volume-based |\n|---|-----------|--------------|\n| **Rate applied** | Each tier at its own rate | Single flat amount for the matching tier |\n| **Total charge** | Sum of each tier\'s charge | Flat amount of the matching tier |\n| **Best for** | Rewarding growth with lower marginal rates | Simpler pricing with volume discounts |\n\nSee [Graduated Pricing](/documentation/modelling-pricing/graduated-pricing) for the alternative model.',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
path: "references/per-unit-pricing.md",
|
|
60
|
+
contents:
|
|
61
|
+
'## Per-Unit Pricing\n\nPer-unit pricing charges customers based on the quantity of a resource they use — seats, workspaces, environments, or any other non-consumable feature. Customers either commit to a quantity upfront (prepaid) or are billed based on actual usage at the end of each billing cycle (usage-based).\n\n> **Example** <br />\n> A collaboration tool charges $10/seat/month. The plan includes 5 seats for free, and each additional seat costs $10.\n\n## Setting up\n\nCreate a `non-consumable` metered feature and add it to a plan with a per-unit price:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const seats = feature({\n featureId: "seats",\n name: "Seats",\n type: "metered",\n consumable: false,\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: seats.featureId,\n included: 5,\n price: {\n amount: 10,\n interval: "month",\n billingMethod: "usage_based",\n },\n },\n ],\n});\n\nexport default atmn({ features: [seats], plans: [pro] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## Billing methods\n\n| Method | When charged | Quantity | Best for |\n|--------|-------------|----------|----------|\n| **Prepaid** | Upfront at purchase | Customer selects a fixed quantity | Seat licenses with committed counts |\n| **Usage-based** | End of billing cycle (prorated on changes) | Automatic — tracks actual usage | Seats that fluctuate frequently |\n\n### Prepaid per-unit\n\nWith prepaid, the customer selects a **total quantity** when purchasing. The `quantity` includes any free included amount — Autumn subtracts the included amount and charges for the remainder.\n\nFor example, with 5 included seats at $10/extra seat, a customer who selects `quantity: 10` gets 10 seats total and pays for 5 extra seats ($50/month).\n\nPass the quantity via `featureQuantities`:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nconst { data } = await autumn.billing.attach({\n customerId: "user_123",\n planId: "pro",\n featureQuantities: [{\n featureId: "seats",\n quantity: 10,\n }],\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nresponse = await autumn.billing.attach(\n customer_id="user_123",\n plan_id="pro",\n feature_quantities=[{\n "feature_id": "seats",\n "quantity": 10,\n }],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing/attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "feature_quantities": [{\n "feature_id": "seats",\n "quantity": 10\n }]\n }\'\n```\n\n</CodeGroup>\n\nThe customer\'s balance is set to the total quantity (10). If they\'re upgrading and already have seats in use, the existing usage is carried over — so a customer with 3 seats in use would see a remaining balance of 7.\n\nAutumn does not prevent you from passing a `quantity` lower than the customer\'s current usage. If the customer has 5 seats in use and you pass `quantity: 3`, the balance goes negative (-2). The `check` endpoint will return `allowed: false`, preventing new seats from being added, but existing seats are not forcibly removed.\n\n### Usage-based per-unit\n\nWith usage-based billing, no quantity is needed at purchase time. Track seat additions and removals as they happen, and Autumn bills for the actual number of seats in use.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\n// Add a seat\nawait autumn.track({\n customer_id: "user_123",\n feature_id: "seats",\n value: 1,\n});\n\n// Remove a seat\nawait autumn.track({\n customer_id: "user_123",\n feature_id: "seats",\n value: -1,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\n# Add a seat\nawait autumn.track(\n customer_id="user_123",\n feature_id="seats",\n value=1,\n)\n\n# Remove a seat\nawait autumn.track(\n customer_id="user_123",\n feature_id="seats",\n value=-1,\n)\n```\n\n```bash cURL\n# Add a seat\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "seats",\n "value": 1\n }\'\n```\n\n</CodeGroup>\n\nWhen a customer purchases the plan, any seats already in use are **automatically reflected** in their subscription from day one. For example, if a customer has 3 seats in use and purchases a plan with 5 included seats at $10/extra seat:\n\n- Their balance starts at 5 (the included amount)\n- The 3 existing seats are carried over, leaving a remaining balance of 2\n- No extra charge yet — they\'re within the included amount\n- As they add seats beyond 5, each additional seat is billed at $10/month with [proration](/documentation/modelling-pricing/proration)\n\n## Existing usage on upgrade\n\nWhen a customer upgrades from one plan to another, Autumn **automatically carries over** their current seat usage to the new plan. This ensures there\'s no gap in tracking — existing seats don\'t disappear or go unbilled.\n\n### Prepaid\n\nThe customer\'s balance is set to their chosen quantity. Existing usage is then deducted from that balance.\n\n> **Example**: Customer has **3 seats** in use. They purchase a plan with 5 included seats, passing `quantity: 10`.\n> - Balance is set to 10 (5 included + 5 purchased)\n> - 3 existing seats are deducted → **7 remaining**\n> - Stripe charges for 10 seats (with 5 in the free tier)\n\n### Usage-based\n\nNo quantity is needed. The Stripe subscription quantity is set to the customer\'s current usage automatically.\n\n> **Example**: Customer has **3 seats** in use. They purchase a plan with 5 included seats at $10/extra seat.\n> - Balance starts at 5 (included amount)\n> - 3 existing seats are deducted → **2 remaining**\n> - Stripe subscription reflects 3 seats in use (within the free tier, so no extra charge)\n> - When they add a 6th seat, billing begins at $10/seat for the overage\n\n| Scenario | Prepaid (qty: 8) | Usage-based |\n|----------|------------------|-------------|\n| **3 in use, 5 included** | Balance: 8 → 5 remaining. Charged for 3 extra. | Balance: 5 → 2 remaining. No extra charge. |\n| **3 in use, 0 included** | Balance: 8 → 5 remaining. Charged for 8. | Balance: 0 → -3. Charged for 3 seats. |\n| **7 in use, 5 included** | Balance: 8 → 1 remaining. Charged for 3 extra. | Balance: 5 → -2. Charged for 2 extra seats. |\n\n## Checking access\n\nBefore allowing a user to add a new seat, check if they have capacity:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst { data } = await autumn.check({\n customer_id: "user_123",\n feature_id: "seats",\n});\n\nif (!data.allowed) {\n // Prompt user to purchase more seats or upgrade\n}\n```\n\n```python Python\nresponse = await autumn.check(\n customer_id="user_123",\n feature_id="seats",\n)\n\nif not response.allowed:\n # Prompt user to purchase more seats or upgrade\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "seats"\n }\'\n```\n\n</CodeGroup>\n\nFor **prepaid**, `allowed` is `true` when the customer has remaining prepaid balance (ie. unused seats).\n\nFor **usage-based**, `allowed` is `true` as long as the customer has a usage-based price configured — additional seats are simply billed at the per-unit rate, so there\'s no hard cap.\n\n## Proration on quantity changes\n\nWhen a customer increases or decreases their seat count mid-billing-cycle, you can configure how the price adjustment is handled. See [Proration](/documentation/modelling-pricing/proration) for details.',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: "references/recurring.md",
|
|
65
|
+
contents:
|
|
66
|
+
'## Recurring Plans\n\nRecurring plans let you grant customers a fixed allowance of consumable features -- like messages, credits, or API calls -- that resets each billing period. Customers pay a base price at a regular interval (monthly, quarterly, annually), and receive a fresh grant of their included features at the start of each cycle.\n\n> **Example** <br />\n> An AI writing tool offers a Pro plan at $20/month that grants 1,000 messages per month. When the billing period resets, the customer\'s message balance is reset back to 1,000.\n\n## Setting up\n\nDefine a recurring plan in your `autumn.config.ts`:\n\n```ts autumn.config.ts expandable\nimport { atmn, feature, plan } from "atmn";\n\nexport const messages = feature({\n featureId: "messages",\n name: "Messages",\n type: "metered",\n consumable: true,\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: messages.featureId,\n included: 1000,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport default atmn({ features: [messages], plans: [pro] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## Attaching a subscription\n\nUse [billing.attach](/documentation/customers/payment-flow) to attach a subscription to a customer. With `redirectMode: "always"`, a checkout URL is always returned for the customer to complete payment or confirm the plan change.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nconst response = await autumn.billing.attach({\n customerId: "user_123",\n planId: "pro",\n redirectMode: "always",\n});\n\n// Redirect customer to complete payment or confirm plan change\nredirect(response.paymentUrl);\n```\n\n```python Python\nimport asyncio\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nasync def main():\n response = await autumn.billing.attach(\n customer_id="user_123",\n plan_id="pro",\n redirect_mode="always",\n )\n\n # Redirect customer to response.payment_url\n\nasyncio.run(main())\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "redirect_mode": "always"\n }\'\n```\n\n</CodeGroup>\n\n```json\n{\n "id": "user_123",\n "name": "Jane Smith",\n "email": "jane@example.com",\n "createdAt": 1771409161016,\n "fingerprint": null,\n "stripeId": "cus_U0BKxpq1mFhuJO",\n "env": "sandbox",\n "metadata": {},\n "sendEmailReceipts": false,\n "billingControls": {\n "autoTopups": []\n },\n "subscriptions": [\n {\n "planId": "pro",\n "autoEnable": false,\n "addOn": false,\n "status": "active",\n "pastDue": false,\n "canceledAt": null,\n "expiresAt": null,\n "trialEndsAt": null,\n "startedAt": 1771431921437,\n "currentPeriodStart": 1771431921437,\n "currentPeriodEnd": 1773851121437,\n "quantity": 1\n }\n ],\n "purchases": [],\n "balances": {\n "messages": {\n "featureId": "messages",\n "granted": 1000,\n "remaining": 1000,\n "usage": 0,\n "unlimited": false,\n "overageAllowed": false,\n "maxPurchase": null,\n "nextResetAt": 1773851121437,\n "breakdown": [\n {\n "id": "cus_ent_abc123",\n "planId": "pro",\n "includedGrant": 1000,\n "prepaidGrant": 0,\n "remaining": 1000,\n "usage": 0,\n "unlimited": false,\n "reset": {\n "interval": "month",\n "resetsAt": 1773851121437\n },\n "price": null,\n "expiresAt": null\n }\n ]\n }\n }\n}\n```\n\nWhen a subscription is created, Autumn:\n\n1. Creates a Stripe subscription with the plan\'s prices\n2. Grants the customer their included [balances](/documentation/concepts/balances) for each consumable feature\n3. Starts the billing cycle -- balances reset automatically at the start of each period\n\n## Billing intervals\n\nPlans support the following billing intervals:\n\n| Interval | Description |\n|----------|-------------|\n| `week` | Billed every week |\n| `month` | Billed every month |\n| `quarter` | Billed every 3 months |\n| `semi_annual` | Billed every 6 months |\n| `year` | Billed annually |\n\nYou can create a separate plan for each interval you want to support. For example, if you want to support monthly and annual plans, you can create a `pro_monthly` plan and a `pro_annual` plan.\n\nYou can also configure a custom `interval_count` to charge at non-standard intervals (e.g., every 2 months).\n\n### Billing interval vs reset interval\n\nThe billing interval (how often the customer is charged) and the reset interval (how often their feature balance replenishes) are configured independently. They don\'t have to match.\n\n> **Example** <br />\n> A plan billed at $200/year could grant 100 messages/month. The customer pays once a year, but their message balance resets to 100 every month.\n\nThis is useful when you want to offer an annual discount while still metering usage on a shorter cycle.\n\n## Managing subscriptions\n\nOnce a customer has an active subscription, you can manage upgrades, downgrades, and cancellations. See [Managing Subscriptions](/documentation/customers/subscription-lifecycle) for details on:\n\n- **Upgrades** — prorated charges for switching to a higher-priced plan\n- **Downgrades** — scheduled at end of billing period\n- **Cancellations** — immediate or end-of-period\n\n## Subscription statuses\n\n| Status | Description |\n|--------|-------------|\n| `active` | Subscription is in good standing |\n| `trialing` | Customer is in a [free trial](/documentation/modelling-pricing/trials) period |\n| `past_due` | Payment failed, needs attention |\n| `scheduled` | Will activate at end of current billing period (e.g., downgrade) |\n| `expired` | Subscription has ended |',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
path: "references/one-off-purchases.md",
|
|
70
|
+
contents:
|
|
71
|
+
'## One-Off Purchases\n\nOne-off purchases are single-charge plans that don\'t recur. They\'re used for one-time top-ups, lifetime access plans, or any plan where the customer pays once.\n\n> **Example** <br />\n> An AI platform lets users buy 500 credits for $10 as a one-time purchase. The credits never expire and can be used at any pace.\n\n## Setting up\n\nSet `interval: "one_off"` on the plan\'s `price`, or on the item price, for a one-time charge:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const credits = feature({\n featureId: "credits",\n name: "Credits",\n type: "metered",\n consumable: true,\n});\n\nexport const creditTopUp = plan({\n planId: "credit_top_up",\n versionSlug: "v1",\n active: true,\n name: "Credit Top-Up",\n items: [\n {\n featureId: credits.featureId,\n price: {\n amount: 10,\n billingUnits: 500,\n billingMethod: "prepaid",\n interval: "one_off",\n },\n },\n ],\n});\n\nexport default atmn({ features: [credits], plans: [creditTopUp] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## How it works\n\nWhen a customer purchases a one-off plan:\n\n- Autumn creates a Stripe invoice (not a subscription) and charges it immediately\n- The feature balance is provisioned with the purchased quantity\n- The balance has a `one_off` interval — it never resets or expires\n\nOne-off purchases don\'t create Stripe subscriptions. They generate a one-time invoice instead.\n\n## Purchasing a one-off plan\n\nFor prepaid one-off plans, pass the desired `quantity` via the `options` array:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nconst { data } = await autumn.checkout({\n customer_id: "user_123",\n plan_id: "credit_top_up",\n options: [{\n feature_id: "credits",\n quantity: 1000,\n }],\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nresponse = await autumn.checkout(\n customer_id="user_123",\n plan_id="credit_top_up",\n options=[{\n "feature_id": "credits",\n "quantity": 1000,\n }],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/checkout" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "credit_top_up",\n "options": [{\n "feature_id": "credits",\n "quantity": 1000\n }]\n }\'\n```\n\n</CodeGroup>\n\n## One-off prices within a subscription\n\nA subscription plan can include both recurring and one-off prices. When it does, Autumn splits them at checkout:\n\n- **Recurring prices** bill every cycle as part of the Stripe subscription\n- **One-off prices** are charged once on the first invoice only\n\nThis is useful for setup fees, one-time credit grants, or any charge that should happen once when the customer subscribes.\n\n> **Example** <br />\n> A Pro plan charges $20/month plus a one-time $50 setup fee. The customer\'s first invoice is $70, and subsequent invoices are $20.\n\nAdd a non-consumable feature for the setup fee, then include it as a separate one-off item alongside the recurring base price:\n\n```ts autumn.config.ts expandable\nimport { atmn, feature, plan } from "atmn";\n\nexport const setupFee = feature({\n featureId: "setup_fee",\n name: "Setup Fee",\n type: "metered",\n consumable: false,\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: setupFee.featureId,\n price: {\n amount: 50,\n billingMethod: "prepaid",\n interval: "one_off",\n },\n },\n ],\n});\n\nexport default atmn({ features: [setupFee], plans: [pro] });\n```\n\nWhen you attach the plan, you can select a quantity for the setup fee. The $20/month base price recurs on every invoice. The setup fee item is charged once on the first invoice only.\n\n## Balance stacking\n\nOne-off balances stack with existing balances from subscriptions. Autumn uses [deduction order](/documentation/concepts/balances#deduction-order) to ensure shorter-interval balances (e.g., monthly) are used before one-off (lifetime) balances.\n\n## Use cases\n\n| Use case | Configuration |\n|----------|---------------|\n| Credit top-up | Prepaid price, add-on, no base price |\n| Lifetime plan | One-off base price, features with no reset |\n| One-time fee | One-off base price, no features |\n| Setup fee + subscription | Recurring base price, one-off item price on same plan |',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: "references/auto-top-ups.md",
|
|
75
|
+
contents:
|
|
76
|
+
'## Auto Top-Ups\n\nAuto top-ups automatically purchase additional balance for a customer when their usage drops below a configured threshold. This prevents service interruptions for customers who don\'t want to manually manage their balance.\n\n> **Example** <br />\n> A customer on the Standard plan gets 5,000 credits per month. When their balance drops below 500, Autumn automatically purchases 1,000 more credits at $10 using the plan\'s one-off prepaid price.\n\n## Prerequisites\n\nAuto top-ups require:\n1. A plan with a [one-off prepaid](/documentation/modelling-pricing/one-off-purchases) item for the feature you want to auto top-up\n2. The customer must have a saved payment method on file\n\n## Setting up\n\nAuto top-ups are configured per customer, not in `autumn.config.ts`. Your plan needs a one-off prepaid item for the feature you want to auto top-up:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const credits = feature({\n featureId: "credits",\n name: "Credits",\n type: "metered",\n consumable: true,\n});\n\nexport const standard = plan({\n planId: "standard",\n versionSlug: "v1",\n active: true,\n name: "Standard",\n price: { amount: 50, interval: "month" },\n items: [\n {\n featureId: credits.featureId,\n included: 5000,\n reset: { interval: "month" },\n },\n {\n featureId: credits.featureId,\n price: {\n amount: 10,\n billingUnits: 1000,\n interval: "one_off",\n billingMethod: "prepaid",\n },\n },\n ],\n});\n\nexport default atmn({ features: [credits], plans: [standard] });\n```\n\nThe one-off prepaid item (`$10 per 1,000 credits`) is what Autumn uses to replenish the balance. Configure auto top-ups per customer via the API (see below).\n\n## Configuring auto top-ups via API\n\nSet up auto top-ups for a customer by updating their billing controls:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n autoTopups: [{\n featureId: "credits",\n enabled: true,\n threshold: 500,\n quantity: 1000,\n }],\n },\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "auto_topups": [{\n "feature_id": "credits",\n "enabled": True,\n "threshold": 500,\n "quantity": 1000,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "auto_topups": [{\n "feature_id": "credits",\n "enabled": true,\n "threshold": 500,\n "quantity": 1000\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\n## Auto top-up configuration\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string | The feature to monitor |\n| `enabled` | boolean | Whether auto top-up is active |\n| `threshold` | number | Balance level that triggers a top-up |\n| `quantity` | number | How many units to purchase each time |\n| `purchase_limit` | object | Optional limit on how often top-ups can occur |\n\n### Purchase limits\n\nTo prevent runaway spending, you can set a purchase limit:\n\n```json\n{\n "purchase_limit": {\n "interval": "month",\n "interval_count": 1,\n "limit": 5\n }\n}\n```\n\nThis limits the customer to 5 auto top-ups per month. Supported intervals: `hour`, `day`, `week`, `month`.\n\n## How it works\n\n1. After every usage event (via `track`), Autumn checks the customer\'s remaining balance\n2. If the balance falls below the configured `threshold`, an auto top-up is triggered\n3. Autumn creates an invoice for the configured `quantity` using the one-off prepaid price from the customer\'s plan\n4. The invoice is charged to the customer\'s saved payment method\n5. The balance is replenished with the purchased amount\n\nAuto top-ups use burst suppression to prevent duplicate purchases when multiple track events happen in quick succession. There\'s a 30-second cooldown between top-ups for the same feature.\n\n## Notifications\n\nSubscribe to the [`billing.auto_topup_succeeded`](/api-reference/webhooks/billingAutoTopupSucceeded) webhook to be notified when a top-up grants credits. The payload includes the granted quantity, the new balance, and the underlying invoice — useful for sending receipts, updating internal ledgers, or reconciling balance after a recharge.\n\nSubscribe to [`billing.auto_topup_failed`](/api-reference/webhooks/billingAutoTopupFailed) to monitor auto top-ups that are blocked, declined, or fail before granting balance. The payload includes a machine-readable `reason` and any available provider error details.\n\nLimit-blocked failure webhooks are suppressed per blocking window to avoid duplicate notifications while the same limit remains active.',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
path: "references/rollovers.md",
|
|
80
|
+
contents:
|
|
81
|
+
'## Rollovers\n\nRollovers let unused feature balances carry forward to the next billing cycle instead of being lost at reset. This gives customers more flexibility and prevents wasted allocation.\n\n> **Example** <br />\n> A customer on a plan with 1,000 credits/month only uses 600 in January. With rollovers enabled, the remaining 400 credits carry over — giving them 1,400 credits available in February.\n\n## Setting up\n\nAdd a `rollover` config to a plan item:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const credits = feature({\n featureId: "credits",\n name: "Credits",\n type: "metered",\n consumable: true,\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: credits.featureId,\n included: 1000,\n reset: { interval: "month" },\n rollover: {\n max: 2000,\n expiryDurationType: "forever",\n expiryDurationLength: 1,\n },\n },\n ],\n});\n\nexport default atmn({ features: [credits], plans: [pro] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## Rollover configuration\n\n| Field | Description |\n|-------|-------------|\n| `max` | Maximum amount that can roll over. Set to `null` for no cap. |\n| `expiryDurationType` | `"forever"` (never expires) or `"month"` (expires after N months) |\n| `expiryDurationLength` | Number of months until rollover balances expire. Ignored if type is `"forever"`. |\n\n## How rollovers work\n\nAt the end of each billing cycle, when a feature\'s balance resets:\n\n1. Autumn checks how much unused balance remains\n2. If rollovers are configured, the unused balance is saved as a **rollover balance**\n3. The feature resets to its granted amount, and the rollover is added on top\n4. If a `max` cap is set, the oldest rollover balances are trimmed first (FIFO)\n5. Expired rollover balances are removed automatically\n\n## Viewing rollover balances\n\nRollover balances appear in the `breakdown` array when you retrieve a customer\'s balances. Each rollover entry has its own expiry date:\n\n```json\n{\n "balances": {\n "credits": {\n "included_usage": 1400,\n "balance": 1400,\n "usage": 0,\n "breakdown": [\n {\n "plan_id": "pro",\n "included_usage": 1000,\n "balance": 1000,\n "usage": 0,\n "interval": "month",\n "next_reset_at": 1745193600000\n },\n {\n "id": "roll_abc123",\n "included_usage": 400,\n "balance": 400,\n "usage": 0,\n "interval": "one_off",\n "expires_at": null\n }\n ]\n }\n }\n}\n```\n\n## Deduction order\n\nRollovers are deducted **before** a customer\'s main balances for the same feature. Within the rollover pool, balances are consumed in `expires_at` order: soonest-expiring first, with rollovers that never expire going last. Only once all rollover balances are drained does Autumn fall through to the regular [deduction order](/documentation/concepts/balances#deduction-order) over the main entitlements.\n\nThis means carried-over balance is used up before fresh monthly allocation, so rollovers you\'re about to lose to expiry get spent first.\n\n> **Example** <br />\n> A customer has a 1,000 credits/month balance that just reset, plus a 400 credits rollover from last month. They use 300 credits. <br />\n> Autumn deducts all 300 from the rollover, leaving 100 credits in rollover and the full 1,000 credits monthly untouched.\n\nRollovers are only available on `consumable` features with a reset interval. Non-consumable features (like seats) don\'t reset and therefore don\'t support rollovers.\n\n## Entity rollovers\n\nIf you\'re using [entity plans](/documentation/modelling-pricing/entity-plans), rollovers are tracked per entity. Each entity\'s unused balance rolls over independently.',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
path: "references/trials.md",
|
|
85
|
+
contents:
|
|
86
|
+
'## Trials\n\nFree trials give customers temporary access to a paid plan before they\'re charged. Autumn supports two trial modes: **card required** (collect payment info upfront, bill when trial ends) and **card not required** (no payment info needed, access expires automatically).\n\n> **Example** <br />\n> A SaaS tool offers a 14-day free trial of their Pro plan. If the customer doesn\'t cancel, billing begins on day 15.\n\n## Setting up\n\nAdd a `freeTrial` object to your plan:\n\n```ts autumn.config.ts expandable\nimport { atmn, feature, plan } from "atmn";\n\nexport const messages = feature({\n featureId: "messages",\n name: "Messages",\n type: "metered",\n consumable: true,\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n group: "main",\n price: { amount: 20, interval: "month" },\n freeTrial: {\n durationLength: 14,\n durationType: "day",\n cardRequired: true,\n },\n items: [\n {\n featureId: messages.featureId,\n included: 1000,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport default atmn({ features: [messages], plans: [pro] });\n```\n\nTrial duration types: `day`, `month`, `year`.\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## Card required trials\n\nWhen `cardRequired` is `true`, the customer must provide payment information to start the trial. Stripe creates a subscription with a trial period — no charge occurs until the trial ends.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nconst { data } = await autumn.checkout({\n customer_id: "user_123",\n plan_id: "pro",\n});\n\n// Returns Stripe Checkout URL — customer adds card and starts trial\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nresponse = await autumn.checkout(\n customer_id="user_123",\n plan_id="pro",\n)\n# Returns Stripe Checkout URL\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/checkout" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro"\n }\'\n```\n\n</CodeGroup>\n\nIf the customer doesn\'t cancel before the trial ends, their card is automatically charged.\n\n## Card not required trials\n\nWhen `cardRequired` is `false`, no checkout is needed. You can attach the plan directly:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst { data } = await autumn.attach({\n customer_id: "user_123",\n plan_id: "pro",\n});\n```\n\n```python Python\nresponse = await autumn.attach(\n customer_id="user_123",\n plan_id="pro",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro"\n }\'\n```\n\n</CodeGroup>\n\nWhen the trial expires, the customer loses access unless they add a payment method. If a [free plan](/documentation/modelling-pricing/free-plans) with `autoEnable` exists in the same group, it\'s activated as a fallback.\n\nYou can combine `autoEnable` with `cardRequired: false` to create an **auto-trial** plan. The trial starts automatically when a customer is created, and expires after the trial period — no API call needed.\n\n## Checking trial status\n\nThe customer\'s subscription includes a `trial_ends_at` timestamp when a trial is active. You can also expand `trials_used` to see which trials a customer has consumed:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst { data } = await autumn.customers.get("user_123");\n\nfor (const sub of data.subscriptions) {\n if (sub.trialEndsAt) {\n console.log(`Trialing until ${new Date(sub.trialEndsAt)}`);\n }\n}\n```\n\n```python Python\nresponse = await autumn.customers.get("user_123")\n\nfor sub in response.subscriptions:\n if sub.trial_ends_at:\n print(f"Trialing until {sub.trial_ends_at}")\n```\n\n</CodeGroup>\n\n## Trial deduplication\n\nEach customer can only use a plan\'s trial **once**. If they try to attach the same plan again, the trial is skipped and they\'re billed immediately.\n\nTo prevent trial abuse across multiple accounts, set a `fingerprint` when creating a customer (e.g., device ID, browser fingerprint). Autumn checks whether any customer with the same fingerprint has already used the trial.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.create({\n id: "user_456",\n name: "Jane Doe",\n email: "jane@example.com",\n fingerprint: "device_abc123",\n});\n```\n\n```python Python\nawait autumn.customers.create(\n id="user_456",\n name="Jane Doe",\n email="jane@example.com",\n fingerprint="device_abc123",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "id": "user_456",\n "name": "Jane Doe",\n "email": "jane@example.com",\n "fingerprint": "device_abc123"\n }\'\n```\n\n</CodeGroup>\n\nCustom trials passed via `customize.freeTrial` always **bypass** deduplication. Use this for support cases where you want to grant a second trial.\n\nYou can check which trials a customer has already used by expanding `trials_used` on the customer object:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst customer = await autumn.customers.getOrCreate({\n customerId: "user_123",\n expand: ["trials_used"],\n});\n```\n\n```python Python\ncustomer = await autumn.customers.get_or_create(\n customer_id="user_123",\n expand=["trials_used"],\n)\n\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "id": "user_123",\n "expand": ["trials_used"]\n }\'\n```\n\n</CodeGroup>\n\n## Upgrades and Downgrades\n\nWhen upgrading to a plan with a trial, the trial behavior depends on the customer\'s current state and whether the new plan has an unused trial:\n\n| Current state | Unused trial? | Result |\n|---|---|---|\n| Trialing | Yes | Current trial ends. Fresh trial starts on new plan. |\n| Trialing | No | Current trial ends. Billing starts immediately. |\n| Active (not trialing) | Yes | Trial starts. Current cycle refunded. |\n| Active (not trialing) | No | No trial. Billing starts at new price. |\n\nWhen a customer downgrades during a trial, the lower plan is scheduled to activate when the trial ends. The lower plan\'s own trial is not applied - you cannot get a new trial on a downgrade.\n\nYou can override any of these behaviors by passing `customize.freeTrial` on the attach call. See [Overriding trial behavior](#overriding-trial-behavior) below.\n\n## Overriding trial behavior\n\nYou can override the default trial behavior on any `/attach` or `/update-subscription` call by passing `customize.freeTrial`:\n\nPass a `freeTrial` object to start a trial with a custom duration. This **bypasses deduplication** — the customer always gets the trial, even if they\'ve trialed this plan before.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.attach({\n customerId: "user_123",\n planId: "pro",\n customize: {\n freeTrial: {\n durationLength: 30,\n durationType: "day",\n cardRequired: true,\n },\n },\n});\n```\n\n```python Python\nawait autumn.attach(\n customer_id="user_123",\n plan_id="pro",\n customize={\n "free_trial": {\n "duration_length": 30,\n "duration_type": "day",\n "card_required": True,\n }\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "customize": {\n "free_trial": {\n "duration_length": 30,\n "duration_type": "day",\n "card_required": true\n }\n }\n }\'\n```\n\n</CodeGroup>\n\nPass `freeTrial: null` to skip the trial entirely and begin billing immediately — even if the plan has a trial configured.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.attach({\n customerId: "user_123",\n planId: "pro",\n customize: {\n freeTrial: null,\n },\n});\n// Charged immediately, no trial\n```\n\n```python Python\nawait autumn.attach(\n customer_id="user_123",\n plan_id="pro",\n customize={"free_trial": None},\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "customize": { "free_trial": null }\n }\'\n```\n\n</CodeGroup>\n\nYou can also pass `freeTrial: null` on `/update-subscription` to end an active trial early and start billing right away.\n\nTo extend a trial, call `/update-subscription` with a new `customize.freeTrial`. The new trial duration is computed **from now** — it replaces the current trial end date rather than adding to it.\n\n<CodeGroup>\n\n```typescript TypeScript\n// Customer is 5 days into a 14-day trial.\n// This gives them a fresh 14 days from now (not 14 + 9 remaining).\nawait autumn.updateSubscription({\n customerId: "user_123",\n planId: "pro",\n customize: {\n freeTrial: {\n durationLength: 14,\n durationType: "day",\n },\n },\n});\n```\n\n```python Python\nawait autumn.update_subscription(\n customer_id="user_123",\n plan_id="pro",\n customize={\n "free_trial": {\n "duration_length": 14,\n "duration_type": "day",\n }\n },\n)\n```\n\n</CodeGroup>\n\nTrial extensions are **replacement**, not additive. If a customer is 5 days into a 14-day trial and you set a new 14-day trial, they get 14 days from today (19 days total from the original start), not 14 days added to the remaining 9.\n\n## Trials with shared subscriptions\n\nWhen using [entities](/documentation/modelling-pricing/entity-plans) or add-ons, trial state is shared across the same Stripe subscription. This is because Stripe manages trials at the subscription level.\n\nYou can pass in `newBillingSubscription: true` to create a new subscription for each plan, rather than merging into the existing subscription.\n\nHere are some principles to keep in mind when using trials with shared subscriptions:\n\n#### First entity gets the trial\n\nWhen the first entity is attached with a trial plan, the trial starts on the shared subscription. Any subsequent entities attached to the same subscription **inherit the existing trial state** — they don\'t start their own independent trial.\n\n#### Adding plans to a non-trialing subscription\n\nIf the subscription is **not** trialing, new plans are charged immediately — even if the product they\'re being attached to has a trial configured. The product\'s trial config is ignored for merges into an active subscription.\n\n#### Shared trial state affects all plans\n\nBecause entities (by default) share a subscription, trial state changes affect **all** entities:\n\n- **Entity upgrade to a plan with a trial**: a fresh trial starts, and all other entities on the subscription inherit the new trial end date.\n- **Entity upgrade to a plan without a trial**: the trial ends for **all** entities, and they\'re all billed immediately.\n- **Entity downgrade during trial**: the downgrade is scheduled for when the trial ends.\n\nPassing `customize.freeTrial` on an entity attach or upgrade affects the **shared subscription**, so all entities are affected. Similarly, passing `freeTrial: null` ends the trial for all entities on the subscription.\n\n## Resetting usage after trial \n\nThis feature is coming soon.\n\nBy default, feature usage during a trial carries over into the paid period. If you want usage to **reset when billing starts**, pass `transition_rules.reset_after_trial_end` with the feature IDs to reset:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.attach({\n customerId: "user_123",\n planId: "pro",\n transitionRules: {\n resetAfterTrialEnd: ["messages"],\n },\n});\n```\n\n```python Python\nawait autumn.attach(\n customer_id="user_123",\n plan_id="pro",\n transition_rules={\n "reset_after_trial_end": ["messages"],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "transition_rules": {\n "reset_after_trial_end": ["messages"]\n }\n }\'\n```\n\n</CodeGroup>\n\nThis sets the feature\'s reset cycle to begin when the trial ends rather than when the trial starts, so the customer gets a full fresh allowance once they start paying.',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
path: "references/entity-plans.md",
|
|
90
|
+
contents:
|
|
91
|
+
'## Entity Plans\n\nAn **entity** is a resource that lives under a parent customer — a user, a workspace, a project. Entity plans let each of those hold its own plan, with its own balances, while the parent customer pays.\n\n> **Example** <br />\n> A team plan costs $30/seat/month. Each seat gets 50 AI meeting summaries per month. If a team has 5 users, each user has their own balance of 50 summaries — they can\'t use each other\'s allocation.\n\n## Two ways to provision\n\nBoth approaches end in the same place: an entity holding a plan. They differ in **where capacity comes from**.\n\n```\n an entity holds a plan\n │\n ┌────────────────────┴────────────────────┐\n attach directly licenses\n ─────────────── ────────\n capacity = whoever you attached capacity = a pool of seats you bought\n charged when the entity is attached charged when the seats are bought\n no unassigned state seats can sit empty, be reassigned\n```\n\nPick with one question: **do you sell capacity before you know who fills it?**\n\n| | Attach directly | Licenses |\n|---|---|---|\n| **Use when** | Entities appear and you bill for them as they do | Customers commit to a seat count upfront |\n| **Buying** | `billing.attach` per entity | `licenseQuantities` on the parent plan |\n| **Provisioning** | Same `billing.attach` call | `licenses.attach` assigns from the pool |\n| **Removing** | `billing.update` with a cancel action | `licenses.release` returns the seat to the pool |\n| **Empty seats** | Not possible | Bought but unassigned seats are normal |\n\nDifferent tiers per entity work in **both** modes — attach different plans to different entities, or offer more than one license plan under the same parent.\n\nEntities are created with a `feature_id` identifying their type (e.g. a non-consumable `seats` or `workspaces` feature). If you only need to *count* seats and bill for them, with no per-seat balances or identity, you don\'t need entities at all — see [per-seat pricing](/documentation/modelling-pricing/per-unit-pricing).\n\n## Attaching plans directly\n\nCreate your plans as normal — no entity-specific configuration on the plan itself. Put plans that should replace each other on upgrade/downgrade in the same `group`.\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const requests = feature({\n featureId: "requests",\n name: "API Requests",\n type: "metered",\n consumable: true,\n});\n\nexport const workspaceFree = plan({\n planId: "workspace_free",\n versionSlug: "v1",\n active: true,\n name: "Workspace Free",\n group: "workspace",\n items: [\n {\n featureId: requests.featureId,\n included: 100,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport const workspacePro = plan({\n planId: "workspace_pro",\n versionSlug: "v1",\n active: true,\n name: "Workspace Pro",\n group: "workspace",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: requests.featureId,\n included: 10000,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport default atmn({\n features: [requests],\n plans: [workspaceFree, workspacePro],\n});\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n#### Create the entity\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nawait autumn.entities.create({\n customerId: "org_123",\n entityId: "workspace_a",\n featureId: "workspaces",\n name: "Workspace A",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nawait autumn.entities.create(\n customer_id="org_123",\n entity_id="workspace_a",\n feature_id="workspaces",\n name="Workspace A",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/entities.create" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "entity_id": "workspace_a",\n "feature_id": "workspaces",\n "name": "Workspace A"\n }\'\n```\n\n</CodeGroup>\n\n#### Attach a plan to it\n\nPass `entityId` to scope the attach to that entity:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.billing.attach({\n customerId: "org_123",\n planId: "workspace_pro",\n entityId: "workspace_a",\n});\n```\n\n```python Python\nawait autumn.billing.attach(\n customer_id="org_123",\n plan_id="workspace_pro",\n entity_id="workspace_a",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing.attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "plan_id": "workspace_pro",\n "entity_id": "workspace_a"\n }\'\n```\n\n</CodeGroup>\n\nEach entity\'s subscription is created separately in Stripe, with billing cycles synced to the parent customer.\n\nTo upgrade or downgrade, attach the new plan with the same `entityId` — the usual [upgrade/downgrade](/documentation/customers/subscription-lifecycle) logic applies.\n\n#### Cancel an entity\'s plan\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.billing.update({\n customerId: "org_123",\n planId: "workspace_pro",\n entityId: "workspace_a",\n cancelAction: "cancel_end_of_cycle",\n});\n```\n\n```python Python\nawait autumn.billing.update(\n customer_id="org_123",\n plan_id="workspace_pro",\n entity_id="workspace_a",\n cancel_action="cancel_end_of_cycle",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing.update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "plan_id": "workspace_pro",\n "entity_id": "workspace_a",\n "cancel_action": "cancel_end_of_cycle"\n }\'\n```\n\n</CodeGroup>\n\nThe same [cancel/uncancel](/documentation/customers/subscription-lifecycle#cancellations) behavior applies.\n\n## Licenses\n\nA **license plan** describes everything one entity gets. The parent plan offers a pool of them, and you assign one to an entity to hand it its own balance.\n\n```\nteam plan ──licenses: [{ seat, included: 1 }]──► pool of seats\n │\n licenses.attach │ licenses.release\n ▼\n entity "user_alice" ──► own balance: 50 summaries/mo\n```\n\nThe pool has a `granted` size (included seats plus any paid seats), a `usage` count (seats currently assigned), and a `remaining` count. Assigning consumes a seat; releasing gives it back.\n\nCreate the feature each seat consumes, then a license plan holding what one seat gets. Link it from the parent plan via `licenses`:\n\n```ts autumn.config.ts\nimport { atmn, feature, license, plan } from "atmn";\n\nexport const summaries = feature({\n featureId: "summaries",\n name: "Meeting Summaries",\n type: "metered",\n consumable: true,\n});\n\n// Everything one seat gets, priced per seat.\nexport const seat = plan({\n planId: "seat",\n versionSlug: "v1",\n active: true,\n name: "Seat",\n group: "licenses",\n price: { amount: 30, interval: "month" },\n items: [\n {\n featureId: summaries.featureId,\n included: 50,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport const team = plan({\n planId: "team",\n versionSlug: "v1",\n active: true,\n name: "Team",\n licenses: [\n license({\n licensePlanId: seat.planId,\n versionSlug: seat.versionSlug,\n included: 1,\n }),\n ],\n});\n\nexport default atmn({ features: [summaries], plans: [seat, team] });\n```\n\n`included: 1` means the Team plan comes with one free seat. Seats beyond that are paid at the license plan\'s own price.\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\nGive the license plan its own `group`. Attaching a plan replaces other plans in the same group, so a license plan sharing a group with its parent would knock the parent off.\n\n#### Buy seats\n\nSeats are bought on the parent plan. `quantity` is the **total** number of seats, including the plan\'s free `included` amount:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.billing.attach({\n customerId: "org_123",\n planId: "team",\n licenseQuantities: [{\n licensePlanId: "seat",\n quantity: 5,\n }],\n});\n```\n\n```python Python\nawait autumn.billing.attach(\n customer_id="org_123",\n plan_id="team",\n license_quantities=[{\n "license_plan_id": "seat",\n "quantity": 5,\n }],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing.attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "plan_id": "team",\n "license_quantities": [\n { "license_plan_id": "seat", "quantity": 5 }\n ]\n }\'\n```\n\n</CodeGroup>\n\nWith 1 included seat and `quantity: 5`, the customer gets 5 seats and pays for 4. Attach again with a new `quantity` to change the count later — Autumn prorates the difference.\n\nA **priced** license plan must be attached at the customer level before it can be assigned to entities. Buying seats with `licenseQuantities` does this for you.\n\n#### Assign a license\n\nAssigning is what provisions the entity\'s individual balance — creating an entity on its own does not:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.licenses.attach({\n customerId: "org_123",\n planId: "seat",\n entities: [\n { entityId: "user_alice", name: "Alice", featureId: "seats" },\n ],\n});\n```\n\n```python Python\nawait autumn.licenses.attach(\n customer_id="org_123",\n plan_id="seat",\n entities=[\n {"entity_id": "user_alice", "name": "Alice", "feature_id": "seats"},\n ],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/licenses.attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "plan_id": "seat",\n "entities": [\n { "entity_id": "user_alice", "name": "Alice", "feature_id": "seats" }\n ]\n }\'\n```\n\n</CodeGroup>\n\n`feature_id` is the entity type and is required only when the entity doesn\'t exist yet — Autumn creates it for you. You can pass several entities in one call.\n\nAssignment is idempotent. Re-assigning an entity that already holds an active license for the same plan succeeds without consuming another seat. If the pool has no seats left, the call errors — buy more seats first.\n\n#### Release a license\n\nThe entity\'s balance is removed and the seat returns to the pool, ready to reassign:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.licenses.release({\n customerId: "org_123",\n licensePlanId: "seat",\n entityIds: ["user_alice"],\n});\n```\n\n```python Python\nawait autumn.licenses.release(\n customer_id="org_123",\n license_plan_id="seat",\n entity_ids=["user_alice"],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/licenses.release" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "license_plan_id": "seat",\n "entity_ids": ["user_alice"]\n }\'\n```\n\n</CodeGroup>\n\nReleasing frees the seat but does not change what the customer pays — they keep the seats they bought. To stop paying for one, attach the parent plan again with a lower `quantity`.\n\n`license_plan_id` is optional, and only needed to disambiguate when an entity holds licenses from more than one plan.\n\n#### Inspect seats\n\n[`licenses.list`](/api-reference/licenses/listLicenses) returns each pool with its `granted`, `usage`, and `remaining` counts. [`licenses.list_assignments`](/api-reference/licenses/listLicenseAssignments) returns which entities currently hold one.\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/licenses.list" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{ "customer_id": "org_123" }\'\n```\n\n## Checking and tracking per entity\n\nRegardless of how the entity got its plan, pass `entity_id` to `check` and `track` to operate on that entity\'s balance:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst { data } = await autumn.check({\n customer_id: "org_123",\n feature_id: "summaries",\n entity_id: "user_alice",\n});\n\nconsole.log(data.allowed);\nconsole.log(data.balance);\n```\n\n```python Python\nresponse = await autumn.check(\n customer_id="org_123",\n feature_id="summaries",\n entity_id="user_alice",\n)\n\nprint(response.allowed)\nprint(response.balance)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "feature_id": "summaries",\n "entity_id": "user_alice"\n }\'\n```\n\n</CodeGroup>\n\nTrack the same way:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.track({\n customer_id: "org_123",\n feature_id: "summaries",\n entity_id: "user_alice",\n value: 1,\n});\n```\n\n```python Python\nawait autumn.track(\n customer_id="org_123",\n feature_id="summaries",\n entity_id="user_alice",\n value=1,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "feature_id": "summaries",\n "entity_id": "user_alice",\n "value": 1\n }\'\n```\n\n</CodeGroup>\n\n### Customer-level vs entity-level\n\n| Level | How to use | Behavior |\n|-------|-----------|----------|\n| **Entity-level** | Pass `entity_id` in check/track | Checks/deducts from that entity\'s individual balance |\n| **Customer-level** | Omit `entity_id` | Returns the total balance across all entities |\n\nWhen tracking at the customer level (without `entity_id`), usage is deducted from the first-assigned entity to keep entity-level totals in sync with the customer-level total.\n\n## Worked example\n\n[Entity-level balances](/examples/entity-balances) walks the licenses model end to end: an AI meeting-notes product on team pricing, from customer creation through buying seats, assigning them, and releasing them when someone leaves.',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
path: "references/credit-systems.md",
|
|
95
|
+
contents:
|
|
96
|
+
'## Credit Systems\n\nCredit systems let you track actions with different credit costs from a single balance pool.\n\nA credit system is made up of a list of [features](/documentation/concepts/features) that can draw from it, and a credit cost per unit of usage for each feature.\n\n> **Example** <br />\n> You have a Pro plan that gives users `100 basic messages` per month, and `10 premium messages` per month. These 2 balances are separate and independent of each other.\n> To give your users more flexibility, you instead decide to use a credit system, where:\n> - `basic message`: costs 1 credit per message\n> - `premium message`: costs 10 credits per message \n>\n> Instead of having 2 separate balances for each message type, your Pro plan can have `200 credits` per month. Your users can use the credits in any combination of basic and premium messages they want.\n\n## Creating a credit system\n\n Make sure you have some metered features created before creating a credit\n system.\n\nDefine metered features, then create a `credit_system` feature with a `creditSchema` that maps each feature to a credit cost:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const basicMessage = feature({\n featureId: "basic_message",\n name: "Basic Message",\n type: "metered",\n consumable: true,\n});\n\nexport const premiumMessage = feature({\n featureId: "premium_message",\n name: "Premium Message",\n type: "metered",\n consumable: true,\n});\n\nexport const credits = feature({\n featureId: "credits",\n name: "Credits",\n type: "credit_system",\n creditSchema: [\n { meteredFeatureId: basicMessage.featureId, creditCost: 1 },\n { meteredFeatureId: premiumMessage.featureId, creditCost: 10 },\n ],\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: credits.featureId,\n included: 200,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport default atmn({\n features: [basicMessage, premiumMessage, credits],\n plans: [pro],\n});\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n**Example**\n\nIf each `premium_request` is worth 3 credits, then using 6 premium requests will cost 18 credits.\n\nNow you can add this credit system to a plan, such as granting 50 credits per month or charging $1 per credit.\n\n## Tracking and limiting credit usage\n\nWhen implementing a credit system into your application, **you should interact with the underlying features -- not the credit system itself**. This means passing in the underlying `feature_id` when checking or tracking usage.\n\n#### Checking access\n\nBefore allowing a customer to use a feature, `check` if they have enough credits to do so. If each "premium request" is worth 3 credits, then this example will check if the customer has at least 18 credits remaining.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst response = await autumn.check({\n customerId: "user_123",\n featureId: "premium_request",\n requiredBalance: 6,\n});\n\nconsole.log(response.allowed);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.check(\n customer_id="user_123",\n feature_id="premium_request",\n required_balance=6,\n)\nprint(response.allowed)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "premium_request",\n "required_balance": 6\n }\'\n```\n\n</CodeGroup>\n\nThe response will contain the balance for the credit system that is being deducted from.\n\n```json\n{\n "allowed": true,\n "customerId": "user_123",\n "requiredBalance": 6,\n "balance": {\n "featureId": "credits",\n "granted": 100,\n "remaining": 100,\n "usage": 0,\n "unlimited": false,\n "overageAllowed": false,\n "nextResetAt": 1757192635393\n }\n}\n```\n\nIn this case, we have a balance of 100 credits remaining, so we\'re allowed to use our 6 "premium requests" feature.\n\n If a feature is not defined in the credit system, it will return `allowed: false`\n\n#### Tracking usage\n\nSince the customer has sufficient credits, you can let them use their 6 "premium requests". Afterwards, you can [track](/documentation/customers/tracking-usage) the usage to update their balance.\n\nThis will decrement the customer\'s balance by 18 credits (6 requests * 3 credits per request).\n\n<CodeGroup>\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.track({\n customerId: "user_123",\n featureId: "premium_request",\n value: 6,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.track(\n customer_id="user_123",\n feature_id="premium_request",\n value=6,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "premium_request",\n "value": 6\n }\'\n```\n\n</CodeGroup>\n\n```json\n{\n "customerId": "user_123",\n "value": 6,\n "balance": {\n "featureId": "credits",\n "granted": 100,\n "remaining": 82,\n "usage": 18,\n "unlimited": false,\n "overageAllowed": false,\n "nextResetAt": 1757192635393\n }\n}\n```\n\nSince the customer started with a balance of 100 credits, and used 18 credits, their remaining balance is 82 credits.\n\n## Rate cards and dimensions\n\nThe `creditSchema` is the credit system\'s **rate card**: one row per metered feature. A row\'s rate can be flat, graduated by usage, or vary by the properties you send with each event.\n\n### Billing units\n\n`billingUnits` prices a bundle of usage at once: `{ meteredFeatureId: \'tokens\', billingUnits: 1000, creditCost: 1 }` charges 1 credit per 1,000 tokens.\n\n### Graduated rates\n\nA graduated row steps the credit cost as usage in the current cycle grows. Tier boundaries are in units and the cost is per `billingUnits`, priced tier by tier: below, the first 10,000 tokens cost 1 credit per 1,000 tokens (10 credits), and everything after costs 0.5 credits per 1,000 tokens.\n\n```ts autumn.config.ts\n{\n meteredFeatureId: tokens.featureId,\n billingUnits: 1000,\n tierBehavior: \'graduated\',\n tiers: [\n { to: 10_000, creditCost: 1 },\n { to: \'inf\', creditCost: 0.5 },\n ],\n}\n```\n\nThe final tier must use `\'inf\'`, and boundaries must strictly increase.\n\n### Dimensions\n\nA **dimension** is a named alternative rate that applies when an event\'s `properties` match. Pass the properties on `track` and `check`:\n\n```ts\nawait autumn.track({\n customer_id: \'cus_123\',\n feature_id: \'actions\',\n value: 1,\n properties: { size: \'large\', region: \'eu\' },\n});\n```\n\n```ts autumn.config.ts\n{\n meteredFeatureId: actions.featureId,\n creditCost: 1,\n dimensions: {\n size_large: { match: { size: \'large\' }, creditCost: 16 },\n size_large_region_eu: {\n match: { size: \'large\', region: \'eu\' },\n creditCost: 20,\n },\n size_xl: {\n match: { size: \'xl\' },\n tierBehavior: \'graduated\',\n tiers: [\n { to: 5, creditCost: 2 },\n { to: \'inf\', creditCost: 1 },\n ],\n },\n },\n multipliers: {\n lifecycle_spot: { match: { lifecycle: \'spot\' }, factor: 0.3 },\n },\n}\n```\n\nHow a rate is chosen for an event:\n\n1. The dimension whose `match` has the **most keys** that all match the event wins. `{ size: \'large\', region: \'eu\' }` beats `{ size: \'large\' }`.\n2. Ties on key count are broken by `priority` (higher wins). Two dimensions that could both match the same event with the same key count and no priority are rejected when you save.\n3. If no dimension matches, the row\'s own rate applies.\n4. **Multipliers** then scale the chosen rate: every matching multiplier\'s `factor` is multiplied together and every `add` is summed. A multiplier set that could push a rate below zero is rejected at save time.\n\nProperty values are compared as strings, so `{ size: 1 }` and `{ size: \'1\' }` match the same dimension. Dimension names must be at most 64 characters and cannot contain `::`.\n\nUsage is attributed per dimension, so graduated dimensions progress through their own tiers, and invoice credit line items are broken down by feature and dimension.\n\n A plan item can override its credit system\'s rate card for customers on that plan via `featureOverride: { creditSchema: [...] }`. The override replaces the rate card entirely, dimensions included.\n\n## Itemized invoice credits\n\nWhen a plan bills a credit system **pay-per-use at exactly one currency unit per credit** (for example `$1` per credit, or `$100` per 100 credits), Autumn treats the balance as invoice credits: every tracked usage is attributed to the feature that spent it, and the invoice lists one line per feature ("Premium messages, 40 units … $8") plus a "Credits applied" line for the credits the plan included. Balances like this can only be moved by tracked usage and cycle resets, so the invoice always matches the ledger.\n\nAny other price shape (a fractional price per credit, prepaid packs, included-only or pooled items) bills as an ordinary overage. The decision is made per customer when the plan is attached, so changing a plan\'s price later never rewrites an existing customer\'s invoices.\n\n There is no switch to turn this on. The plan item\'s price decides, so a credit system can itemize on one plan and bill plainly on another.\n\n## Stacking with direct balances\n\nA feature can have both a direct balance **and** belong to a credit system. When this happens, the balances stack and **direct balances are always consumed before credit system balances**, regardless of interval.\n\n> **Example** <br />\n> A customer\'s plan grants `10 premium messages` per month directly, plus `200 credits` per month from a credit system (where each premium message costs 10 credits). <br /><br />\n> When the customer sends a premium message, Autumn deducts from the direct premium message balance first. Once those 10 direct messages are used up, subsequent premium messages draw from the credit pool instead.\n\n The `check` endpoint accounts for both balances. If the customer has 5 direct premium messages remaining plus 100 credits (enough for 10 more premium messages), `check` will report that the customer is allowed.\n\n## Monetary credits\n\nYou may want your credit system to represent a monetary value: eg, $10 of credits. To implement this, you can map each credit to a cent value (eg, 1 credit = 1 cent).\n\n1. When creating your credit system, define credit amounts in the per-cent cost \n\n Eg: if each `premium_request` costs 3 cents, our credit cost should be 3.\n\n2. When adding the credits to a plan, set the granted amount of credits in cents \n\n Eg, if customers get 5 USD credits for free, they should have an included usage of `500`.\n\n3. When charging for the credits, set the cost of each credit to 1 cent\n\nSee the credits pricing guide for a more detailed example of setting up a monetary credits system\n\n## AI Credit Systems\n\nFor AI applications that need to track token usage with per-model pricing, you can create an AI credit system. This lets you define markup percentages for each model and automatically calculate costs based on input/output tokens.\n\nMarkups are optional. `defaultMarkup` applies to every model unless overridden — by `providerMarkups` (keyed by the first segment of the model ID, e.g. `openrouter`), or by `modelMarkups` for a specific model, which takes highest priority. With no markups set, models are billed at their Models.dev base cost.\n\nA markup of `-100` makes the model free: usage events are still recorded, but nothing is deducted from the balance.\n\n```ts Simplest setup — one markup for everything\nexport const aiCredits = feature({\n featureId: \'ai_credits\',\n name: \'AI Credits\',\n type: \'ai_credit_system\',\n defaultMarkup: 30, // every model billed at models.dev cost + 30%\n});\n```\n\nOr mix the levels for finer control:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const aiCredits = feature({\n featureId: "ai_credits",\n name: "AI Credits",\n type: "ai_credit_system",\n // Global fallback markup\n defaultMarkup: 30,\n // Per-provider defaults\n providerMarkups: {\n openrouter: { markup: 25 },\n },\n // Per-model overrides (highest priority)\n modelMarkups: {\n "anthropic/claude-opus-4-5": { markup: 20 },\n "anthropic/claude-sonnet-4-5": { markup: 15 },\n "openai/gpt-4o-mini": { markup: -100 }, // free for customers\n // For custom/self-hosted models, specify input/output costs in $/M tokens\n "custom/my-model": { markup: 25, inputCost: 0.01, outputCost: 0.03 },\n },\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 50, interval: "month" },\n items: [\n {\n featureId: aiCredits.featureId,\n included: 10, // $10 worth of AI credits\n reset: { interval: "month" },\n },\n ],\n});\n\nexport default atmn({ features: [aiCredits], plans: [pro] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n### Model ID Format\n\nModel IDs follow the `provider/model` format:\n- Standard models: `anthropic/claude-opus-4-5`, `openai/gpt-4o`\n- OpenRouter models: `openrouter/anthropic/claude-opus-4.6`\n- Custom models: `custom/my-model-name`\n\nFor standard models, pricing is automatically fetched from models.dev, including separate rates for cache reads/writes, reasoning, and audio tokens where the model publishes them, plus large-context tier pricing (e.g. above 200k input tokens) when applicable.\n\nFor custom models, you must specify both `inputCost` and `outputCost` in dollars per million tokens — tracking fails if either is missing. Custom models bill input and output tokens only; cache, reasoning, and audio pools are ignored.\n\n### Tracking Token Usage\n\nUse the `trackTokens` endpoint to deduct credits based on token usage:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.balances.trackTokens({\n customerId: "user_123",\n modelId: "anthropic/claude-opus-4-5",\n inputTokens: 1500,\n outputTokens: 500,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.balances.track_tokens(\n customer_id="user_123",\n model_id="anthropic/claude-opus-4-5",\n input_tokens=1500,\n output_tokens=500,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/balances.track_tokens" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "model_id": "anthropic/claude-opus-4-5",\n "input_tokens": 1500,\n "output_tokens": 500\n }\'\n```\n\n</CodeGroup>\n\nThe cost is calculated automatically based on the model\'s pricing plus your configured markup percentage.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
path: "references/free-plans.md",
|
|
100
|
+
contents:
|
|
101
|
+
'## Free Plans\n\nFree plans let you give every new customer access to a limited set of features at no cost. They\'re the foundation of freemium models — customers start free and upgrade when they need more.\n\n> **Example** <br />\n> A developer tool offers a free tier with 100 API requests per month and 1 workspace. When a user exceeds the limit, they\'re prompted to upgrade.\n\n## Setting up\n\nCreate a plan with no `price` and set `autoEnable: true`:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const apiRequests = feature({\n featureId: "api_requests",\n name: "API Requests",\n type: "metered",\n consumable: true,\n});\n\nexport const workspaces = feature({\n featureId: "workspaces",\n name: "Workspaces",\n type: "metered",\n consumable: false,\n});\n\nexport const free = plan({\n planId: "free",\n versionSlug: "v1",\n active: true,\n name: "Free",\n group: "main",\n autoEnable: true,\n items: [\n {\n featureId: apiRequests.featureId,\n included: 100,\n reset: { interval: "month" },\n },\n {\n featureId: workspaces.featureId,\n included: 1,\n },\n ],\n});\n\nexport default atmn({ features: [apiRequests, workspaces], plans: [free] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## How it works\n\nWhen `autoEnable` is set, every new customer created via the API or SDK is automatically assigned this plan. This flag can only be set if there are no prices on the plan. Since there are no prices, no payment is required.\n\nIf a customer cancels their paid plan and you have an auto-enabled free plan in the same group, the free plan will be re-activated automatically.\n\n## Gating features\n\nUse the [check](/documentation/customers/check) endpoint to gate access based on the free plan\'s limits:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nconst { data } = await autumn.check({\n customer_id: "user_123",\n feature_id: "api_requests",\n});\n\nif (!data.allowed) {\n // Prompt user to upgrade\n}\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nresponse = await autumn.check(\n customer_id="user_123",\n feature_id="api_requests",\n)\n\nif not response.allowed:\n # Prompt user to upgrade\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "api_requests"\n }\'\n```\n\n</CodeGroup>\n\nWhen `allowed` is `false`, the customer has exhausted their free tier balance. This is a good moment to prompt them to upgrade.',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
path: "references/add-ons.md",
|
|
105
|
+
contents:
|
|
106
|
+
'## Add-Ons\n\nAdd-ons are plans that can be purchased alongside a customer\'s existing plan, rather than replacing it. They\'re used for top-ups, extra feature packs, or supplementary services.\n\n> **Example** <br />\n> A customer on the Pro plan can purchase a "Storage Add-On" for an extra 100GB/month, or a one-time "Credit Top-Up" of 500 credits.\n\n## Setting up\n\nSet `addOn: true` on the plan:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const storage = feature({\n featureId: "storage",\n name: "Storage (GB)",\n type: "metered",\n consumable: false,\n});\n\nexport const credits = feature({\n featureId: "credits",\n name: "Credits",\n type: "metered",\n consumable: true,\n});\n\nexport const storageAddOn = plan({\n planId: "storage_add_on",\n versionSlug: "v1",\n active: true,\n name: "Extra Storage",\n addOn: true,\n price: { amount: 5, interval: "month" },\n items: [\n {\n featureId: storage.featureId,\n included: 100,\n },\n ],\n});\n\nexport const creditTopUp = plan({\n planId: "credit_top_up",\n versionSlug: "v1",\n active: true,\n name: "Credit Top-Up",\n addOn: true,\n items: [\n {\n featureId: credits.featureId,\n price: {\n amount: 10,\n billingUnits: 500,\n billingMethod: "prepaid",\n interval: "one_off",\n },\n },\n ],\n});\n\nexport default atmn({\n features: [storage, credits],\n plans: [storageAddOn, creditTopUp],\n});\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## How add-ons work\n\nWithout the add-on flag, attaching a new plan replaces the customer\'s current plan (within the same [group](/documentation/concepts/plans#plan-properties)). With the add-on flag:\n\n- The plan is **added alongside** the customer\'s existing plans\n- Multiple add-ons can be active at the same time\n- Add-ons don\'t participate in upgrade/downgrade logic\n\n## Balance stacking\n\nWhen an add-on provides the same feature as the customer\'s main plan, the balances [stack](/documentation/concepts/balances#balance-stacking). Each source is tracked separately in the `breakdown` array.\n\n> **Example** <br />\n> A customer\'s Pro plan grants 1,000 credits/month. They purchase a one-time top-up of 500 credits. Their total balance is 1,500 credits, tracked as two separate sources.\n\nAutumn uses [deduction order](/documentation/concepts/balances#deduction-order) to consume shorter-interval balances first (monthly before lifetime).\n\n## Purchasing add-ons\n\nAdd-ons use the same checkout/attach flow as regular plans:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nconst { data } = await autumn.checkout({\n customer_id: "user_123",\n plan_id: "storage_add_on",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nresponse = await autumn.checkout(\n customer_id="user_123",\n plan_id="storage_add_on",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/checkout" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "storage_add_on"\n }\'\n```\n\n</CodeGroup>\n\nFor prepaid add-ons (like a credit top-up), pass the quantity:\n\n```typescript TypeScript\nconst { data } = await autumn.checkout({\n customer_id: "user_123",\n plan_id: "credit_top_up",\n options: [{\n feature_id: "credits",\n quantity: 1000,\n }],\n});\n```\n\n## Cancelling add-ons\n\nCancel an add-on using the same [cancel](/documentation/customers/subscription-lifecycle#cancellations) flow:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.cancel({\n customer_id: "user_123",\n plan_id: "storage_add_on",\n});\n```\n\n```python Python\nawait autumn.cancel(\n customer_id="user_123",\n plan_id="storage_add_on",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/cancel" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "storage_add_on"\n }\'\n```\n\n</CodeGroup>\n\n## Common add-on patterns\n\n| Pattern | Configuration |\n|---------|---------------|\n| Recurring add-on | `addOn: true`, recurring price (e.g., $5/month for extra storage) |\n| One-time top-up | `addOn: true`, prepaid price, no base price |\n| Feature pack | `addOn: true`, grants boolean or metered features |',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
path: "references/plan-variants.md",
|
|
110
|
+
contents:
|
|
111
|
+
'## Plan Variants\n\nPlan variants let you model multiple versions of the same offer without duplicating the full plan. The base plan holds the shared definition, and each variant stores only the differences: usually a price change, an added item, or a different usage allowance.\n\n> **Example** <br />\n> A Pro plan has the same core features for every customer, but is sold monthly, annually, and as a higher-volume package. Model these as variants of `pro` instead of three unrelated plans.\n\nVariants are most useful for:\n\n- Monthly vs annual billing intervals\n- A/B testing plan packages\n- Volume ladders that share most features but differ in included usage or overage price\n\n## Setting up\n\nEach variant is its own `variant({...})` fixture, listed in the base plan\'s `variants`:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan, variant } from "atmn";\n\nexport const emails = feature({\n featureId: "emails",\n name: "Emails",\n type: "metered",\n consumable: true,\n});\n\nexport const proAnnual = variant({\n variantPlanId: "pro_annual",\n versionSlug: "v1",\n name: "Pro Annual",\n customize: {\n price: { amount: 200, interval: "year" },\n },\n});\n\nexport const pro100k = variant({\n variantPlanId: "pro_100k",\n versionSlug: "v1",\n name: "Pro 100k",\n customize: {\n price: { amount: 35, interval: "month" },\n removeItems: [{ featureId: emails.featureId, billingMethod: "usage_based" }],\n addItems: [\n {\n featureId: emails.featureId,\n included: 100000,\n price: {\n amount: 0.9,\n billingUnits: 1000,\n billingMethod: "usage_based",\n interval: "month",\n },\n },\n ],\n },\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: emails.featureId,\n included: 10000,\n price: {\n amount: 1,\n billingUnits: 1000,\n billingMethod: "usage_based",\n interval: "month",\n },\n },\n ],\n variants: [proAnnual, pro100k],\n});\n\nexport default atmn({ features: [emails], plans: [pro] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n## How variants work\n\nEach variant is still a plan you can attach by ID, such as `pro_annual` or `pro_100k`. The difference is that Autumn keeps it connected to the base plan.\n\nUse variants when plans share most of their features. If a variant changes many unrelated parts of the plan, create a separate plan instead.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
path: "references/fork-licenses.md",
|
|
115
|
+
contents:
|
|
116
|
+
'# Seats: per-unit item or licenses?\n\nThe normal case is simple; the trap is missing the uncommon one.\n\n## Normal: seats are just a number\n\n*"$10 per seat."* Nothing granted per seat, nobody assigns seats to people.\n\n→ A per-unit priced item on the plan. No entities, no licenses. Done.\n\n## The trap: each seat grants something\n\n*"Team is $40/seat/month, every seat gets 100 summaries."*\n\nTempting (wrong): per-unit seat item + one big summaries allowance on the team plan.\n\nWhy it breaks: the allowance doesn\'t grow when they add a 6th seat, and seats have no identity — no per-seat balance, no assigning seat #3 to Alice.\n\nRight: the seat is a **license** — a small plan of its own (own group, $40 price, grants 100 summaries) that the team plan hands out per seat. The link is a `license({...})` fixture in the parent\'s `licenses`, naming the child\'s version; `included` is how many come free with the parent, and extras bill at the seat plan\'s price:\n\n```ts\nexport const seat = plan({\n planId: "seat",\n versionSlug: "v1",\n active: true,\n name: "Seat",\n group: "seat",\n price: { amount: 40, interval: "month" },\n items: [{ featureId: summaries.featureId, included: 100, reset: { interval: "month" } }],\n});\n\nexport const team = plan({\n planId: "team",\n versionSlug: "v1",\n active: true,\n name: "Team",\n price: { amount: 500, interval: "month" },\n licenses: [license({ licensePlanId: seat.planId, versionSlug: seat.versionSlug, included: 5 })],\n});\n```\n\nDivision of labor when both levels exist: **per-seat things (the seat\'s own price, its granted allowance) live on the license plan; account-wide things (base price, shared purchases like credit packs) live on the parent** — the parent attaches at the customer, so its items are already shared by every seat. Don\'t invent add-on plans for purchases the parent can carry, and don\'t put the per-seat grant on the parent (it wouldn\'t scale with seats).\n\n## When parents differ: one child, per-license diffs\n\n**Define the child plan ONCE; every parent references the same child id, carrying its own differences in `customize`.** N parents → 1 child plan definition → N license entries. The child holds the mainline take (what the primary parent gets) plus everything all units share (boolean features); a differing parent\'s license overrides only its own price/grant.\n\nThe shape, schematically:\n\n```ts\nexport const <child> = plan({\n planId: "<child>",\n group: "<child>", // own group, or attaching replaces the parent\n price: { amount: <mainline unit price>, interval: "month" },\n items: [ <mainline grant>, <booleans every unit has> ],\n});\n\nexport const <parentA> = plan({ // gets the mainline take: link only\n licenses: [license({ licensePlanId: <child>.planId, versionSlug: <child>.versionSlug, included: <n> })],\n});\n\nexport const <parentB> = plan({ // differs: diff on the license, never a second child plan\n licenses: [license({\n licensePlanId: <child>.planId,\n versionSlug: <child>.versionSlug,\n included: <m>,\n customize: {\n price: { amount: <parentB unit price>, interval: "month" },\n removeItems: [ <filter matching the mainline grant> ],\n addItems: [ <parentB\'s grant> ],\n },\n })],\n});\n```\n\nWorked example — an agency platform: Studio ($90/mo) and Agency ($450/mo) both sell client sites. A site is $8/mo on Agency with 2,000 renders; on Studio it\'s $12/mo with only 750. Every site gets SSL:\n\n```ts\nexport const site = plan({\n planId: "site",\n versionSlug: "v1",\n active: true,\n name: "Site",\n group: "site",\n price: { amount: 8, interval: "month" },\n items: [\n { featureId: renders.featureId, included: 2000, reset: { interval: "month" } },\n { featureId: ssl.featureId },\n ],\n});\n\nexport const agency = plan({\n planId: "agency",\n versionSlug: "v1",\n active: true,\n name: "Agency",\n price: { amount: 450, interval: "month" },\n licenses: [license({ licensePlanId: site.planId, versionSlug: site.versionSlug, included: 10 })],\n});\n\nexport const studio = plan({\n planId: "studio",\n versionSlug: "v1",\n active: true,\n name: "Studio",\n price: { amount: 90, interval: "month" },\n licenses: [license({\n licensePlanId: site.planId,\n versionSlug: site.versionSlug,\n included: 2,\n customize: {\n price: { amount: 12, interval: "month" },\n removeItems: [{ featureId: renders.featureId }],\n addItems: [{ featureId: renders.featureId, included: 750, reset: { interval: "month" } }],\n },\n })],\n});\n```\n\nWRONG — child duplicated per parent:\n\n```ts\nexport const studioSite = plan({ planId: "studio_site", price: { amount: 12, ... }, items: [ /* 750 renders, SSL */ ] });\nexport const agencySite = plan({ planId: "agency_site", price: { amount: 8, ... }, items: [ /* 2000 renders, SSL */ ] });\n```\n\nRIGHT — the `site` config above: one `site` plan, two license entries, studio\'s diff in `customize`. Duplicated children break sharing — an SSL change now needs two edits, and a customer moving Studio→Agency gets a brand-new site plan instead of the same one on new terms.\n\n**Self-check before finishing: count the child plan definitions for this pattern — there must be exactly one.**\n\n## Rare: seats need identity but grant nothing\n\nThey want to assign, reassign, and hold empty seats — each seat tracked on its own plan. Also licenses. Uncommon; confirm they actually need it before reaching for this.\n\n## Deciding\n\nAsk one question: **does a seat grant anything?**\n\n- No → per-unit item (normal case).\n- Yes → licenses.\n- No, but they need to track who holds each seat → licenses (rare — confirm).\n\nWhat licenses *are* (pools, assign/release, per-link customize) is defined in the `autumn-concepts` skill\'s licenses reference; attach flows live in the entity-plans docs. Read those when building — this file only owns the decision.',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
path: "references/fork-variants.md",
|
|
120
|
+
contents:
|
|
121
|
+
'# Volume buckets: variants or one prepaid item?\n\n## The trap: "tiers" in the user\'s mouth becomes `tiers` in the config\n\n*"Starter also comes in higher transcription tiers — the 60K tier at $39/month with 60,000 minutes included, the 150K tier at $69/month with 150,000. Same $1.20 per 1,000 overage on all of them."*\n\nTempting (wrong): fold the tiers into Starter as one prepaid volume-tiered item. It type-checks, it pushes. Two smells say it\'s wrong before it breaks:\n\n- Starter\'s $19 base price has nowhere to go — you end up deleting it and decomposing each tier price into fake quantity rows. **Restructuring the existing plan to make the new "tiers" fit is the wrong-fork smell.**\n- $19/20K, $39/60K, $69/150K follow no per-unit rate — these are price points on a menu, not a price rule.\n\nRight — each tier IS the subscription, so each is a variant of the base plan:\n\n```ts\nexport const starter60k = variant({\n variantPlanId: "starter_60k",\n versionSlug: "v1",\n name: "Starter 60K",\n customize: {\n price: { amount: 39, interval: "month" },\n removeItems: [{ featureId: minutes.featureId }],\n addItems: [{ featureId: minutes.featureId, included: 60_000, reset: { interval: "month" }, price: minuteOverage }],\n },\n});\n```\n\n`starter` lists it in `variants: [starter60k]`. The base plan stays untouched; customers upgrade between tiers like between plans.\n\n## When one prepaid item IS right\n\n*"Buy extra render hours any time: 200 for $30, 1,000 for $120, 5,000 for $500."*\n\nThe bucket is a quantity bought **on top of** whatever plan they\'re on — nothing about their subscription changes. One prepaid item with volume tiers, because the tier rows only map quantity to price. Where that item sits (plan vs add-on) is the add-on fork\'s question, not this one\'s.\n\n## Deciding\n\nAsk one question: **is each bucket the subscription itself, or a purchase on top of one?**\n\n- The subscription itself → one variant per bucket.\n- A purchase on top → prepaid item; volume tiers when the per-unit rate shifts with quantity.\n- Per-bucket overage rates or per-bucket features → variants regardless; one item cannot express those.\n- Unsure → variants (they can express anything a tier row can, not vice versa), or ask: "do customers subscribe to a tier, or buy an amount on top of their plan?"\n\nWhat variants can and cannot change is defined in the plan-variants docs; this file only owns the decision.',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
path: "references/fork-addon.md",
|
|
125
|
+
contents:
|
|
126
|
+
'# How does a purchase-on-top get modeled?\n\n"Customers can also buy extra credits/packs/top-ups" — two independent questions decide the structure. Answer both; neither alone does.\n\n## Q1 — Whose subscription is it part of?\n\n- Priced or bundled differently per plan ("$20/20k on Team, $60/20k on Starter") → a prepaid **item on each plan**. The price difference IS plan differentiation; a separate add-on can\'t express it.\n- Same offer regardless of plan, opt-in → **one add-on plan** (`addOn: true`, the purchase as its prepaid item). Several sizes are tiers on that one item, not a plan per size.\n\n## Q2 — What level does its balance live at?\n\nThe purchase must sit on something attached where the balance is shared.\n\n- Base plans attach at the **customer** → they already ARE customer-level; a shared purchase can be an item on them (Q1 decides which shape).\n- Base plans attach **per entity** → no plan at the shared level exists, so a shared purchase forces a **customer-level add-on** — even if Q1 alone wouldn\'t have created one. An item on the entity plan would strand the balance on one entity.\n\n## The contrast (same sentence, two structures)\n\n*"Teams can also buy shared credit packs."*\n\n```\nparent plans at the customer plans attached per entity\n(seats/units via licenses) (each workspace its own plan)\n\n Team ── prepaid pack item workspace plan ── pooled grant\n Starter ── prepaid pack item │\n (per-plan pricing, Q1) ▼\n customer add-on ── prepaid pack item\n (no customer plan existed, Q2)\n```\n\nLeft: the parent plan is already customer-level, so the pack is just an item there — and per-plan pricing demanded it anyway. Right: every plan is entity-attached, so the shared pack needs its own customer-level add-on plan.\n\nSay why in the proposal: "packs go on the plan since each plan prices them differently" or "packs are a separate purchase shared by all workspaces".',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
path: "references/fork-pooled.md",
|
|
130
|
+
contents:
|
|
131
|
+
"# Where do balances and purchases live?\n\n## The trap: putting shared purchases on the plan\n\n*\"Growth is $99/mo per project and includes 20k tokens. Teams can also buy token packs — shared across all their projects.\"*\n\nTempting (wrong): put the prepaid pack items on the growth plan. It type-checks, it pushes. It breaks the first time a team buys a pack: the tokens land on ONE project's balance instead of being usable by all of them.\n\nRight — split by who owns what:\n\n```\nproject A gets 20k + overage ┐\nproject B gets 20k + overage ├──► one shared customer balance ◄── token-pack add-on (customer level)\nproject C gets 20k + overage ┘ ▲\n └── any project's usage draws from here\n```\n\n- The plan's allowance (20k per project): pooled — each project's grant joins the shared customer balance (`pooled: true` on the item).\n- Purchases the whole team shares (packs): here a customer-level add-on plan, because every plan is entity-attached — no customer-level plan exists to carry the item. The full plan-item-vs-add-on decision is the add-on fork's.\n- Overage ($/token past the allowance): usually an item on each project's plan even when the balance is pooled, because that breaks extra usage down per entity. Two items on the plan: the pooled grant carries no price, and a separate usage-priced item (`included: 0`) carries the overage — a pooled item can't itself be usage-priced.\n\n## Deciding\n\nThe rule of thumb: **purchases and balance at the org; caps and usage tracking at the entity.**\n\n- Each entity has its own allowance and its own limit → separate balances per entity (attach the plan per entity, no pooling).\n- Grants combine and anyone can spend the total → pooled.\n- \"Shared across…\" anywhere in the pitch → strong pooled signal. Confirm, don't assume separate.\n- Want a per-entity cap on a shared balance → that's a usage limit (billing control), not a separate balance.\n\nHow pooled balances actually behave (contributions, stacking with customer purchases) is defined in the `autumn-concepts` skill — the plan-items reference for the item flag, the customer-entity reference for the runtime balance. This file only owns the decision.",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
path: "references/cases.md",
|
|
135
|
+
contents:
|
|
136
|
+
'# Worked cases\n\nFive archetypes, each chosen because it teaches one structural fork. They are shapes, not current company pricing — the numbers are illustrative.\n\n## 1. CI platform with per-project build minutes (F4: pooled)\n\nPitch: "Team is $150/mo per project and includes 8k build minutes. Orgs can buy minute packs — shared across all their projects."\n\n- Naive: prepaid pack items on the team plan. Packs land on one project\'s balance; "shared" is broken.\n- Structure: team attached per project with a `pooled` minutes item (each project\'s 8k joins one customer balance); packs on a customer-level add-on plan; overage stays an item on each project\'s plan so extra usage breaks down per entity.\n- The deciding fact: purchases are shared, allowances are per-project. Purchase and balance at the customer; grants and attribution at the entity.\n\n## 2. Team plan where seats carry credits (F3: licenses)\n\nPitch: "Team is $40/seat/month; every seat gets 100 summaries a month."\n\n- Naive: per-unit seat item + one big summaries allowance on the team plan. The allowance doesn\'t scale with seats and seats have no identity.\n- Structure: a seat license plan (own group) priced $40 granting 100 summaries; the team plan offers it via `licenses`.\n- The deciding fact: the seat *grants something*. Count-only seats would stay a per-unit item with no entities at all.\n\n## 3. Webhook delivery tier ladder (F1: plan-per-tier)\n\nPitch: "$20/mo for 50k events, $35 for 100k, $60 for 200k — overage $0.90/1k, $0.70/1k, $0.45/1k respectively."\n\n- Naive: one plan with a volume-tiered item. Collapses because each rung needs its own overage rate, and an item has one.\n- Structure: one plan (or variant) per rung; the prepaid tier is the price (no base price); each carries its own usage-priced overage item.\n- The deciding fact: something differs *in kind* per tier, not just in amount.\n\n## 4. AI app with actions and credits (F5: credit system)\n\nPitch: "Pro includes 500 credits; a chat message costs 1 credit, an image 5, a video 25."\n\n- Naive: three metered features with three allowances. Users see three balances; pricing page shows one.\n- Structure: one credit-system feature mapping the three actions at their rates; plans grant credits; app tracks the underlying actions, never the credit system directly.\n- The deciding fact: several actions draw one shared balance at different rates.\n\n## 5. Annual pricing that resets monthly (F1 detail)\n\nPitch: "Pro $20/mo or $200/yr — 1,000 messages per month either way."\n\n- Naive: annual plan granting 12,000 messages a year.\n- Structure: annual variant of pro; price interval year, reset interval month. Billing interval and reset interval are independent.\n- The deciding fact: the allowance is stated per month even when billing is annual. Ask when the pitch doesn\'t say.',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
path: "references/atmn.md",
|
|
140
|
+
contents:
|
|
141
|
+
'# atmn catalog flows\n\nContents: commands · when to use it · the config is the catalog\'s state (versions, renames, drafts) · variants and licenses · config shapes · splitting the config · update loop · pull · sandboxes and keys · what to show the user.\n\nUse `atmn` when a project has or should have an `autumn.config.ts` source of truth.\n\nCommands — these two, not `atmn preview` (that does not exist):\n\n```sh\natmn push # lints, then previews the diff; applies nothing\natmn push --yes # apply exactly what the preview showed\n```\n\n`push` without `--yes` is always a dry run, on a clean org too, and it never asks anything: `--yes` is the only gate. The commands that do ask (`init`, `login --keyless`, `sandbox use`, `skills install`) print the flag to pass instead whenever there is no TTY, which is every agent run; `--headless` forces that in a terminal.\n\n## When to use it\n\n- New project: run `atmn init`. It connects (sign in, or keyless), places the config, pulls whatever the org already holds, and installs these skills beside it.\n- Existing project: if `autumn.config.ts` exists, run `atmn pull` first so every row carries its server ids, then edit and push. Never rewrite a config that predates you from scratch.\n- Use MCP/API directly when the user wants dashboard/API-first changes or there is no local config workflow.\n\n## The config is the catalog\'s state\n\nThe document is the whole desired catalog for every collection it states. A plan or feature missing from a stated `plans` / `features` is a deletion (archived when customers depend on it). A collection left out entirely is not managed. That rule reaches down to versions: **a version row missing from `plans` is deleted too**.\n\nEvery version of a plan is a row in `plans`. Rows of one plan share a `planId`; each names its version with `versionSlug`; exactly one is `active: true` and the rest `active: false`. There is no history collection and no timeline — array order means nothing. What a version *is* (a group of customers, not a step in time) is the `autumn-concepts` skill\'s: read `references/plan.md` in the `autumn-concepts` skill. This file owns how rows express it.\n\nTwo fields make a row addressable:\n\n- `planId` + `versionSlug` — what you write. The pair names exactly one version. `versionSlug` is required on every plan row and every variant row, including a plan\'s only version; an omitted slug is a lint error, never an implicit `v1`.\n- `internalId` — the server\'s stable id. It is minted on the first `push --yes` and written back into the fixture; `pull` writes it for every row that lacks one. A fixture carrying it can be renamed (`planId`, `featureId`, `versionSlug`) and the server treats that as a rename, not a delete plus create.\n\n**This is where atmn differs from the API and the dashboard.** Those send one plan entry plus flags — `versioning`, `active`, `propagate` — as `references/catalog-update.md` describes. A config has none of those flags. The intent is read off the rows — which rows exist, which one is active, which carry an `internalId` — and the preview reports what the server derived. If the preview says something other than what you meant, change the rows and preview again; never look for a flag.\n\n| You want | Row edit | Preview shows |\n|---|---|---|\n| Change a version in place (active or history) | edit that row; keep its `versionSlug` and `internalId` | `~ pro@v1`, a migration for its customers |\n| The same change on every version | make the edit on every row of the plan | one `~` per row |\n| A new version; customers stay on the old one | add a row: same `planId`, new `versionSlug`, `active: true`, **no `internalId`**; flip the old row to `active: false` | `+ pro@v2`, and `active: true -> false` on the old row |\n| A draft nobody can buy yet | add the row with `active: false` beside the active one | `+` with no pointer move |\n| Rename the plan id | change `planId` on **every** row of the plan (all carry `internalId`) | a rename, not `-` + `+` |\n| Rename a version | change `versionSlug` on a row that carries `internalId` | `Version slug` rename |\n| Retire a version | remove its row | `-`; refused while customers are on it (it names both exits: migrate them, or archive the whole plan) |\n\nGuards the lint applies before anything is sent: a plan with no active row or two active rows (`mark the one customers can buy active: true and the rest active: false`), a missing `versionSlug`, the same `planId` + `versionSlug` twice. A plan whose only row is `active: false` is refused — a draft needs an active sibling.\n\nNumbers vs slugs: the server numbers versions in creation order and the preview labels rows with that number (`pro@v2`). The config never states numbers, only slugs. Push a `v1` row after `v2` already exists and the server numbers it higher; the slug still says `v1`. Never read a preview number back as the slug.\n\n## Variants and licenses\n\nWhat a variant\'s customize can change, when a base edit reaches a variant, and how a license link anchors to a child version are catalog-wide: `references/catalog-update.md`. What is atmn\'s:\n\n- A variant is an entry in its base row\'s `variants` array and a license link an entry in the parent row\'s `licenses` array: `variant({...})` and `license({...})` fixtures, inline or imported from their own files, edited in place there. Pull writes new ones in that form. Every entry the config lists is a declared overlay; there is no `propagate` in a config, so a base edit reaches a variant through the entry you write, not a follow flag.\n- Minting a base version means listing the variant entries again under the new base row, each with the new `versionSlug` and no `internalId`. The old entries stay under the old base row. One variant version cannot serve two base rows; the lint names both rows and says to version and relink the variant.\n- To retire a variant, set `archived: true` on its entry. A variant left out of the array is a deletion, refused while customers hold it.\n- Every `license({...})` states `versionSlug` — the lint refuses one without it — because the link is pinned to that child version and a config that names it links the same version in every environment. Pull writes it back. Minting a child version moves no parent; relinking a parent is editing that slug.\n\n## Config shapes\n\n`autumn.config.ts` uses the atmn package types, not raw API JSON. Field names are camelCase: `featureId`, `planId`, `billingMethod`, `billingUnits`, `freeTrial`, `intervalCount`, `versionSlug`. Follow the exported types from the package when editing config. Amounts are plain dollars: $20 is `20`, never `2000`.\n\nBuilders — `feature`, `plan`, `variant`, `license`. Items are plain objects on the plan; there is no `item()` builder and fixtures expose `featureId` / `planId`, never `.id`. A plan with an active `v2`, its `v1` kept for the customers still on it, each with an annual variant:\n\n```ts\nimport { atmn, feature, plan, variant } from "atmn";\n\nexport const messages = feature({\n featureId: "messages",\n name: "Messages",\n type: "metered",\n consumable: true,\n});\n\nexport const proAnnualV2 = variant({\n variantPlanId: "pro_annual",\n versionSlug: "v2",\n name: "Pro Annual",\n customize: { price: { amount: 250, interval: "year" } },\n});\n\nexport const proAnnualV1 = variant({\n variantPlanId: "pro_annual",\n versionSlug: "v1",\n name: "Pro Annual",\n customize: { price: { amount: 200, interval: "year" } },\n});\n\nexport const proV2 = plan({\n planId: "pro",\n versionSlug: "v2",\n active: true,\n name: "Pro",\n price: { amount: 25, interval: "month" },\n items: [{ featureId: messages.featureId, included: 10000, reset: { interval: "month" } }],\n variants: [proAnnualV2],\n});\n\nexport const proV1 = plan({\n internalId: "prod_…", // written back by push --yes / pull\n planId: "pro",\n versionSlug: "v1",\n active: false,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [{ featureId: messages.featureId, included: 5000, reset: { interval: "month" } }],\n variants: [proAnnualV1],\n});\n\nexport default atmn({ features: [messages], plans: [proV2, proV1] });\n```\n\nBefore `proV2` existed, `proV1` was the active row; minting `v2` was adding `proV2` without an `internalId` and flipping `proV1` to `active: false`.\n\nUsage-priced item:\n\n```ts\n{\n featureId: messages.featureId,\n included: 10000,\n reset: { interval: "month" },\n price: {\n amount: 0.9,\n billingMethod: "usage_based",\n billingUnits: 1000,\n interval: "month",\n },\n}\n```\n\n## Splitting the config\n\n`atmn init` scaffolds four files: `autumn.config.ts` (the root, `export default atmn({...})`), `features.ts`, `plans.ts`, `rewards.ts`. Every version of every plan lives in the `plans` array — active and history rows side by side. The config is ordinary TypeScript, so a row can be lifted into its own file under any export name and referenced from the array; `pull` follows imports and edits each fixture where it lives, and appends rows it has to add to the imported array. What it cannot do is edit a fixture that is not a plain literal — a spread, a helper call, a `.map()` — and it says so and writes nothing rather than guess.\n\n## Update loop\n\n1. Inspect or create `autumn.config.ts`.\n2. Edit the config to represent the desired catalog.\n3. Run `atmn push` to preview changes.\n4. Show the user the plan diffs, the customer impact, which plans mint a new version, and the draft migrations it would create. If the versioning is not what they meant, change the rows (table above) and preview again.\n5. Rerun `atmn push --yes` to apply the same preview.\n6. Report created/updated/deleted/archived features and plans, and the draft migrations the output lists. `push --yes` also writes `internalId` (and any `versionSlug` the server assigned) back into the fixtures — say so; those edits are expected.\n\nTwo notes push prints that are worth relaying: a plan removed while an id-less plan appears looks like a rename — pull first so the fixture carries its id; and a config still stating a deprecated field (`entityFeatureId`) gets a note naming the replacement.\n\n## Pull\n\n`atmn pull` writes the server\'s catalog back into the config in place: it flips `active` where the dashboard promoted a version, appends versions the config never mentioned, and backfills `internalId` and `versionSlug`. Run it after anyone touches the dashboard, and before editing a config you did not write. With no config yet, `pull` asks which folder to create it in; headless, it prints the `-c <dir>` hint and stops, so run `atmn init` or pass `-c` instead.\n\n`atmn pull --overwrite` is different: it rewrites `autumn.config.ts` and the `features.ts`, `plans.ts` and `rewards.ts` beside it from the server. It never deletes a file, and it leaves alone any file that does not import the package. It needs `--yes`, and it is the right move only when the config describes a different org than the key — the tell is `Your config no longer matches this org\'s catalog`. Anywhere else, a plain `pull` is what you want.\n\n## Sandboxes and keys\n\n- `atmn sandbox use <name>` pins a named sandbox; every command after it targets that sandbox until `atmn sandbox use --clear`.\n- `atmn reset --yes` empties the pinned sandbox; `atmn push --yes` rebuilds it from the config.\n- `atmn env --json` says which org, sandbox and key a command would hit, with `notes` on what to do when something is off.\n- A missing key fails fast and names the fix: `AUTUMN_SECRET_KEY is not set. Run atmn login, or atmn login --keyless if you don\'t have an account.` — hand that to the setup flow, don\'t ask the user to paste a key.\n\n## What to show the user\n\n- Which plans mint a new version, and the draft migrations that come with them.\n- Feature/plan/version deletions that will archive instead because dependencies or customers exist.\n- Which sandbox is pinned before applying anything.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
path: "references/catalog-update.md",
|
|
145
|
+
contents:
|
|
146
|
+
'# Catalog update flow\n\nContents: ground rules · the update model (desired state, two ways to state it, addressing a row, versioning, variants, licenses, features) · loop · reading the preview · deciding per plan · what to report.\n\nUse this when customers are on these plans and the user wants to change pricing or plans. A half-built config from a setup session is not this — keep building with the normal workflow. What a version, a draft and `active` mean is the `autumn-concepts` skill\'s: read `references/plan.md` in the `autumn-concepts` skill. How rows in `autumn.config.ts` express the same intents: `references/atmn.md`.\n\n## Ground rules\n\n- Never run the new-catalog interview against a live catalog. Read the current catalog first; it is the truth to diff against, not a draft to replace.\n- Touch only what the change names. Every other plan, item and id stays byte-identical — rewriting untouched plans is the classic update failure.\n- Match the existing catalog\'s patterns; if sibling plans model a thing one way, the change follows that way.\n- The questions are "who\'s affected", not "what do you sell": new version or edit in place? all versions? which variants and license parents follow? migrate customers or grandfather them?\n- Structural changes ("add seats", "make credits shared") re-enter the Shape forks exactly as a new catalog would.\n- Preview before every write; apply only the exact previewed params.\n\n## The update model\n\nRead with `catalogV2.get` (`include_versions: true` to see every version). Preview with `catalogV2.preview_update`, apply with `catalogV2.update` — the same params, so a preview is always exactly what apply would do. `catalogV2.diff` is a read-only delta with no write validation, for reconciling a local copy.\n\n**The payload is desired state, per collection.** `features`, `plans`, `rewards`, `referral_programs`: a collection left out is untouched. With `skip_deletions: false` a stated collection is complete and anything missing from it is removed; with `skip_version_deletions: false` the same holds for a stated plan\'s versions. Removal archives rather than deletes whenever customers hold the row. `remove_plans` (optionally pinned to one version) and `remove_features` remove by name under the default; `skip_plan_ids` shields plans from a complete payload.\n\n**Two ways to state a change.** A targeted payload states only the plan being changed and says who follows through `propagate` — that is how the dashboard and API or MCP callers work. A whole-catalog payload states every plan, every version and every variant, and flips both `skip_*_deletions` off — that is what `autumn.config.ts` sends. Both reach the same server; pick the one matching the surface you are on and don\'t mix them.\n\n**If the surface is atmn**, the same model applies with these differences, which decide whether an edit does what you meant (mechanics in `references/atmn.md`):\n\n- The config is the complete state of every collection it names. A plan, version or variant left out is a deletion, not "unchanged".\n- There are no `versioning`, `propagate` or follow flags. Intent is read off the rows: a row with a new `versionSlug` and no `internalId` mints a version, flipping `active` promotes, every variant the base lists is an explicit overlay, and a license link names its child version.\n- Rows are addressed by the stable `internalId` the CLI writes back after `push --yes` and `pull`. Renaming a plan id, feature id or version slug is safe only on a row that carries it — so run `atmn pull` before editing a config you did not write.\n- The lint runs before anything is sent, and preview then apply are `atmn push` and `atmn push --yes` with the same config, so a push previews exactly what it would apply.\n\n**Addressing a row.** `plan_id` names the plan; `version_slug` pins one version, and omitting it targets the active row. `internal_id` addresses a row by its stable id, which is what makes `plan_id` and `version_slug` renames safe; without it, `new_plan_id` renames and is blocked while customers or reward programs reference the id.\n\n**Versioning.** `versioning` is `existing` (default: edit the addressed row), `all_versions` (the same edit on every version), or `new_version` (mint the next version; customers stay where they are). `active: true` promotes the minted row; omit it to mint a draft. `new_version_slug` names the minted row. `migration: { draft: true }` creates a migration draft for customers on an in-place or all-versions edit; it is rejected with `new_version`, because minting is the choice to leave customers alone.\n\n**Variants.** They live under the base entry\'s `variants[]`, never as top-level plans. Each entry is a declared overlay: `customize.items` replaces the item list, `add_items` / `remove_items` patch it, `price` and `free_trial` override. Declaring an entry is not the same as following: a base edit reaches a variant only when `propagate.variants` names it (pinned by `version_slug`), and relatives not named are frozen. When the base mints a new version, a following or overlaid variant with customers mints its next version too. To retire a variant, set `archived: true` on its entry. Nesting an entry under a base links it there; `base_variant_id: null` detaches it.\n\n**Licenses.** A parent\'s `licenses[]` entry names `license_plan_id`, `included`, `prepaid_only`, an optional `version_slug` and a `customize` (price, add or remove items). A link is pinned to one child version, named by `version_slug`. Versioning the child moves no parent: moving a parent onto the new child version is an explicit change to its link. `propagate.license_parents` lets named parents follow a child edit; anything not named stays pinned. The preview\'s `license_parents` lists the parents pointing at a child row being changed.\n\n**Features.** `new_feature_id` renames; `archived` archives or restores. A plan may reference a feature stated in the same payload.\n\n## Loop\n\n1. Read the current catalog and the proposed change.\n2. Build params for only what changes.\n3. Preview. Never skip this before a write.\n4. Summarize what the preview reports (below) and settle the decisions per plan.\n5. If anything changed, revise and preview again.\n6. Apply with the exact previewed params, under the global write-approval rules.\n7. Report, including any migration draft — it moves nobody until it is reviewed and run.\n\n## Reading the preview\n\nPer plan entry:\n\n- `action` is per plan id: `create` means no live version existed, `update` covers edits and minting alike; `will_archive` says a removal archives instead. `state.usage` carries capped customer counts; `state.reasons` are ready-made lines for why something archives or is blocked.\n- `versioning` says what actually happens (`new_version` is null when an existing row is edited) and `options` lists the strategies available for this plan today — offer only those.\n- `sibling_versions`: the other versions that could receive the same edit, each with the slots it had diverged on that the edit would overwrite.\n- `variants[]`: each resolved as `unchanged` (frozen), `propagated` (followed) or `explicit` (the payload declared it), with conflicts — slots the variant already overrides. Conflicts inform the decision; they never block.\n- `license_parents[]`: the same three states for parents whose link points at this row.\n- Feature entries carry blockers that would reject the update; check them before applying.\n- Top level: the migration drafts the update would create.\n\n## Deciding, per plan\n\n1. **Versioning.** If the change touches no base price and no priced item, edit in place (`existing`, or `all_versions` when every customer group should get it) and say so — no question needed. If it changes a base price or a priced item on a plan with customers, ask: new version (they keep their terms) or in place plus a migration draft (they move to the new terms)? Offer only what `options` lists.\n2. **Relatives.** Default to following conflict-free variants and license parents; ask before following into a conflict, showing what would be overwritten.\n3. **Migration.** For an in-place edit on a plan with customers, offer the draft. Never create one alongside `new_version`.\n\n## What to report\n\nWhich plans mint a new version and which are edited in place; which variants and parents follow; deletions that archive because customers or dependencies exist; and the migration drafts created, with the reminder that they still have to be run.',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
path: "references/rewards.md",
|
|
150
|
+
contents:
|
|
151
|
+
"# Rewards\n\nRewards are coupons or feature grants; referral programs hand them out. In a catalog update they are stated collections like plans: `rewards` and `referralPrograms` in `autumn.config.ts`, `rewards` and `referral_programs` in the API payload. Omit a collection to leave it untouched; an empty one under a complete payload deletes every reward. List existing rewards first and confirm plan and feature ids before creating one.\n\nRules:\n\n- Each reward is exactly one of `coupon` or `feature_grant`.\n- Coupons are `percentage_discount` (at most 100) or `fixed_discount` (major currency units). `plan_ids: null` means every plan; otherwise name current plan ids.\n- Duration: `months` needs a positive `length`; `one_off` and `forever` need `length: null`.\n- A feature grant needs at least one grant and one promo code. A boolean feature is granted with `included: null`; metered and credit features need a positive amount.\n- Reward ids, promo codes, plan ids and feature ids within one reward are unique.",
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "autumn-integrate",
|
|
157
|
+
description:
|
|
158
|
+
"Integrating Autumn into an app's backend — creating customers, billing flows (attach, checkout, upgrade, cancel), gating features with check, recording usage with track, and exposing billing data to the frontend. Use when the user asks to integrate Autumn, add billing/payments to their app, gate features, meter usage, or build checkout/upgrade flows. Requires a pushed catalog; use autumn-catalog first if plans don't exist yet.",
|
|
159
|
+
version: "2.0.0",
|
|
160
|
+
markdown:
|
|
161
|
+
"---\nname: autumn-integrate\ndescription: Integrating Autumn into an app's backend — creating customers, billing flows (attach, checkout, upgrade, cancel), gating features with check, recording usage with track, and exposing billing data to the frontend. Use when the user asks to integrate Autumn, add billing/payments to their app, gate features, meter usage, or build checkout/upgrade flows. Requires a pushed catalog; use autumn-catalog first if plans don't exist yet.\nversion: 2.0.0\n---\n\n# Integrate\n\nBefore using this skill, first load the `autumn-concepts` skill — it defines the objects these calls operate on — customers, plans, balances, entities.\n\n## STRICT RULES\n\n1. **Everything is backend.** All Autumn calls run in the app's server code with `AUTUMN_SECRET_KEY` — never in the browser. The frontend gets billing data only through the app's own endpoints.\n2. **The old frontend layer is deprecated — never use or suggest it**: `autumnHandler` mounts, React hooks (`useCustomer`, `useEntity`, `AutumnProvider`), shadcn components. If an app already uses them, don't rip them out unasked — but build new work backend-only.\n3. **Resolve the customer before anything else.** Every handler that checks, tracks, or attaches must serve a brand-new user: `customers.getOrCreate` with the app's own stable id (user or org id from auth — whoever pays — never an email). Never assume the customer exists.\n4. **An error response is a failure.** A 4xx/5xx from any Autumn call is never \"working\" — read the error, fix the call.\n\n## Order of operations\n\nWork these in order; each step names what to settle and where the details live. Read the referenced doc before implementing the step — the snippets and parameters are there, not here.\n\n**1 — Discover the app.** Framework, server entry points, where auth lives, which existing routes handle the actions being billed. Settle the customer id: the app's stable user or org id — whoever pays. If the product bills per workspace/project/seat, those are entities under one customer, not separate customers.\n\n**2 — Settle the level.** Before writing any flow, ask the user how their plans and features actually work: what does a plan get bought for — the whole account, or each workspace/deployment/seat under it? And where does each feature's balance live — shared across the account, or per unit? The catalog hints (entity-scoped items, license plans) but doesn't decide — a plan can attach per deployment while credit packs stay account-wide. The answers flow through everything: `billing.attach` takes `entityId` for entity-level plans, `check`/`track` take `entityId` for entity-scoped balances, billing controls set at the matching level. Mixing levels silently misbills — an account-wide check against a per-seat balance always passes. For entities, entity-scoped balances, when to use which level, read `references/feature-entities.md`. For plans that attach per entity, read `references/entity-plans.md`.\n\n**3 — Install and connect.** `autumn-js` (or the platform's SDK) + `AUTUMN_SECRET_KEY` in server env. One shared client in server code. For install, client setup, the end-to-end happy path, read `references/setup.md`.\n\n**4 — Customer creation.** `customers.getOrCreate` where the app knows who the user is — signup/login hook, or lazily in billing handlers. Pass name/email when available. A default (auto-enable) plan attaches on creation — no attach call for the free tier. If step 2 settled anything at entity level, mirror this for entities: when the app creates its unit (a workspace, a deployment, a seat), create the entity then — usually in the same handler, right before the entity-level attach or check that needs it. For getOrCreate parameters, Stripe linking, pre-creating, read `references/creating-customers.md`. For creating entities, entity parameters, read `references/feature-entities.md`.\n\n**5 — Purchase and plan changes.** All through two calls, split by intent (pass `entityId` when the plan attaches at entity level — step 2):\n - `billing.attach` — getting on a plan, AND upgrades/downgrades within a plan group: attach the new plan; a higher (monthly-normalized) price applies immediately with proration, a lower one schedules for period end. `planSchedule` overrides. Default attach charges a saved card in place and returns `checkout_url` only when payment is actually needed — handle both outcomes. Force `redirectMode: \"always\"` only when the product wants hosted checkout unconditionally; custom flows preview first. For hosted vs custom checkout, redirectMode, previewAttach, read `references/payment-flow.md`. For upgrade/downgrade behavior, scheduled downgrades, carry-over on upgrade, read `references/subscription-lifecycle.md`.\n - `billing.update` — changing the subscription you're on: prepaid quantities (seats), `cancelAction` (`cancel_end_of_cycle` / `cancel_immediately` / `uncancel`), customization. It never switches plans — that's attach's job. Preview twins exist for both calls — use them wherever the app shows a confirmation screen. For quantities, cancelAction, previewUpdate, prorationBehavior, read `references/updating-subscriptions.md`.\n - When several plans move together — a plan plus its add-on in one purchase, or tied cancellation — use the multi twins: `billing.multiAttach` (one checkout / one payment covering every plan) and `billing.multiUpdate` (one atomic call applying a `cancelAction` per plan). Two sequential attach/update calls mean two checkouts or a half-canceled customer, and concurrent ones trip the per-customer lock.\n - Payment snags are response **state, not exceptions**: `required_action` (3DS, payment method, failure) plus `payment_url` — handle the states, surface the URL. For 3DS, payment failures, required_action codes, read `references/edge-cases.md`.\n - For deep parameter detail on attach, update, and the other billing actions (createSchedule, discounts, custom terms), the `autumn-billing` skill is the reference — it's written for operating billing directly, but its parameter and edge-case knowledge applies when implementing these calls in code.\n\n**6 — Check and track.** In the billed action's own handler: `check` before the work, `track` after it succeeds. Gate on `allowed` — don't re-derive access from balances. Pass `requiredBalance` when an action costs more than 1, and `entityId` when the balance lives on an entity (step 2) — check and track must agree on the level. For the check → act → track pattern, read `references/gating.md`. For check parameters, allowed semantics, credit systems, entity checks, read `references/check.md`. For track and setUsage, read `references/tracking-usage.md`.\n\n**7 — Billing controls.** Per-customer policy on top of the plan: set via `customers.update` with `billing_controls` (`spend_limits`, `usage_limits`, `usage_alerts`, `auto_topups`, `overage_allowed`), or at purchase time by passing `billing_controls` on `billing.attach`. The flagship shape is a user-facing \"overage billing on/off\" toggle: off maps to a `spend_limits` entry with `skip_overage_billing: true` — overage still accrues but is never invoiced — plus an `overage_limit` (use `limit_type: \"usage_percentage\"`, e.g. `20` = overage up to 20% of the plan allowance) so the user is blocked at the cap instead of running up unbilled usage forever; on means clearing or replacing the entry (`skip_overage_billing: false`). Whether overage needs enabling at all depends on the plan: items with a usage price already allow it (controls only modulate the billing); pure-grant items hard-stop at zero and need `overage_allowed` per customer — which permits the overage but doesn't bill it. Alerts (\"email at 80%\") are `usage_alerts` with `threshold_type: \"usage_percentage\"`; prepaid balances self-replenish via `auto_topups`. For all five controls, field shapes, customer vs entity level, plan defaults, read `references/billing-controls.md`. For spend limit semantics, skip_overage_billing, usage alerts, read `references/spend-limits.md`. For auto top-up setup and requirements, read `references/auto-top-ups.md`.\n\n**8 — Billing data for the frontend.** The app exposes its own endpoints calling `customers.get` / `plans.list` server-side; the UI reads those. For customer payload, plans list with eligibility, portal, read `references/display-billing.md`.\n\n**9 — Verify.** Call the app's routes as a fresh user; confirm in sandbox that the customer exists, checks gated, usage recorded, purchase reached checkout. `customers.get` is the oracle. **Done means verified behavior — compiling is not done.** Then undo what verifying created — release the license assignments, delete the entities, and reverse the usage your test recorded (a negative `track` or `setUsage` back), and nothing more. Never delete the customer or any state that existed before you started; pre-existing subscriptions and assignments are real. Verification also re-runs flows, so billing endpoints must tolerate repeats: joining a plan you're already on returns success, not a crash.\n\n## Gotchas the docs don't cover\n\n- **Concurrent billing mutations on one customer return 429** — attach/update share a per-customer lock; serialize instead of retrying blindly.\n- **A denied check should say what ran out** — include the feature and its balance state in the response, not a bare unexplained 403. What the caller does with it is the app's call: an API returns a clear error code, a dashboard might prompt an upgrade.\n\n## Conduct\n\n- Follow the app's existing patterns — router style, error handling, response shapes. Autumn code should look like the app wrote it.\n- Use catalog ids exactly; never invent feature or plan ids. Missing from the catalog → say so; that's an `autumn-catalog` change, not a workaround.\n- Smallest working integration first: one gated action end to end, verified, before spreading wider.\n\nGoing to production: For live keys, production checklist, read `references/deploy.md`. For fail-open behavior during Autumn outages, read `references/fail-open.md`.",
|
|
162
|
+
references: [
|
|
163
|
+
{
|
|
164
|
+
path: "references/feature-entities.md",
|
|
165
|
+
contents:
|
|
166
|
+
'## Entities\n\nEntities are sub accounts of a customer. For example, you may have a product that allows 500 credits **per user** per month.\n\n## Product setup\n\nAn entity can hold its own plan, with its own balances, while the parent customer pays. There are two ways to provision that, differing in where capacity comes from:\n\n1. **Attach directly**: pass the [entity ID](/api-reference/billing/attach#body-entity-id) into an attach call. The entity gets its own subscription in Stripe, with billing cycles synced to the parent. Use this when entities appear and you bill for them as they do.\n2. **Licenses**: the parent plan offers a pool of seats that the customer buys upfront, and you assign one to an entity to give it its plan. Use this when customers commit to a seat count before you know who fills it.\n\nBoth support different tiers per entity. See [entity plans](/documentation/modelling-pricing/entity-plans) for the full setup of either.\n\n**Example**\n\nMax has a team product where each seat gets 30 meeting note summaries per month, and teams buy their seat count upfront. He creates a Seat license plan holding the 30 summaries, offers it under his Team plan, and assigns a license each time someone joins.\n\nJamie also has a team product, but workspaces are created ad hoc and each can be on a Free or Pro tier. She creates the two tiers as normal and attaches them at the entity level as workspaces appear.\n\n## Creating entities\n\nYou can manage feature entities via the `entities` route. This can be used to create, update and delete entities, such as when a seat or workspace is added or removed.\n\nWhen you create an entity, a **usage event** will automatically be sent that increments the count of the number of entities (eg seats) being used.\n\nThis means if you create 2 entities for "seats", your usage of "seats" will be 2, allowing Autumn to bill accordingly if there\'s a price set.\n\nThis is how seats are counted when you attach plans directly. With [licenses](/documentation/modelling-pricing/entity-plans#licenses), seats are counted by the license pool instead, and the entity\'s feature only identifies its type.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.entities.create({\n customerId: "org_123",\n entityId: "user_abc",\n featureId: "seats",\n name: "John Doe",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.entities.create(\n customer_id="org_123",\n entity_id="user_abc",\n feature_id="seats",\n name="John Doe",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/entities" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "entity_id": "user_abc",\n "feature_id": "seats",\n "name": "John Doe"\n }\'\n```\n\n</CodeGroup>\n\n## Managing entity balances\n\nJust like with normal features, you can check feature access and track usage events for each entity.\n\n#### Check feature access\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst response = await autumn.check({\n customerId: "org_123",\n featureId: "ai-messages",\n entityId: "user_abc",\n});\n\nconsole.log(response.allowed);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.check(\n customer_id="org_123",\n feature_id="ai-messages",\n entity_id="user_abc",\n)\nprint(response.allowed)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "feature_id": "ai-messages",\n "entity_id": "user_abc"\n }\'\n```\n\n</CodeGroup>\n\n#### Send usage event\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.track({\n customerId: "org_123",\n featureId: "ai-messages",\n entityId: "user_abc",\n value: 10,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.track(\n customer_id="org_123",\n feature_id="ai-messages",\n entity_id="user_abc",\n value=10,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "feature_id": "ai-messages",\n "entity_id": "user_abc",\n "value": 10\n }\'\n```\n\n</CodeGroup>\n\n#### Customer-level vs entity-level balances\n\nA customer-level balance is the total balance for the feature across all entities. Entity-level balances are the balance for a specific entity. You can check access and track usage events either at the customer-level or entity-level.\n\n**Example**\n\nYou have a product that allows 500 credits per user per month. However, the credits are shared across all users in the account.\n\nYou can create a feature entity for "seats" and then check access and send usage events at the top-level.\n\nTo use top-level balances, just omit the `entityId` from your check request.\n\n This will increment the usage counter for the top-level balance, and also\n deduct from the first-created entity so that the sum of the the entity\n balances is always the same as the top-level balance.\n\n## Deleting Entities\n\n Just as creating an entity sent a usage event for the associated feature,\n deleting an entity will decrease the usage.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.entities.delete({\n customerId: "org_123",\n entityId: "user_abc",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.entities.delete(\n customer_id="org_123",\n entity_id="user_abc",\n)\n```\n\n```bash cURL\ncurl -X DELETE "https://api.useautumn.com/v1/entities/user_abc" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{ "customer_id": "org_123" }\'\n```\n\n</CodeGroup>',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
path: "references/entity-plans.md",
|
|
170
|
+
contents:
|
|
171
|
+
'## Entity Plans\n\nAn **entity** is a resource that lives under a parent customer — a user, a workspace, a project. Entity plans let each of those hold its own plan, with its own balances, while the parent customer pays.\n\n> **Example** <br />\n> A team plan costs $30/seat/month. Each seat gets 50 AI meeting summaries per month. If a team has 5 users, each user has their own balance of 50 summaries — they can\'t use each other\'s allocation.\n\n## Two ways to provision\n\nBoth approaches end in the same place: an entity holding a plan. They differ in **where capacity comes from**.\n\n```\n an entity holds a plan\n │\n ┌────────────────────┴────────────────────┐\n attach directly licenses\n ─────────────── ────────\n capacity = whoever you attached capacity = a pool of seats you bought\n charged when the entity is attached charged when the seats are bought\n no unassigned state seats can sit empty, be reassigned\n```\n\nPick with one question: **do you sell capacity before you know who fills it?**\n\n| | Attach directly | Licenses |\n|---|---|---|\n| **Use when** | Entities appear and you bill for them as they do | Customers commit to a seat count upfront |\n| **Buying** | `billing.attach` per entity | `licenseQuantities` on the parent plan |\n| **Provisioning** | Same `billing.attach` call | `licenses.attach` assigns from the pool |\n| **Removing** | `billing.update` with a cancel action | `licenses.release` returns the seat to the pool |\n| **Empty seats** | Not possible | Bought but unassigned seats are normal |\n\nDifferent tiers per entity work in **both** modes — attach different plans to different entities, or offer more than one license plan under the same parent.\n\nEntities are created with a `feature_id` identifying their type (e.g. a non-consumable `seats` or `workspaces` feature). If you only need to *count* seats and bill for them, with no per-seat balances or identity, you don\'t need entities at all — see [per-seat pricing](/documentation/modelling-pricing/per-unit-pricing).\n\n## Attaching plans directly\n\nCreate your plans as normal — no entity-specific configuration on the plan itself. Put plans that should replace each other on upgrade/downgrade in the same `group`.\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const requests = feature({\n featureId: "requests",\n name: "API Requests",\n type: "metered",\n consumable: true,\n});\n\nexport const workspaceFree = plan({\n planId: "workspace_free",\n versionSlug: "v1",\n active: true,\n name: "Workspace Free",\n group: "workspace",\n items: [\n {\n featureId: requests.featureId,\n included: 100,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport const workspacePro = plan({\n planId: "workspace_pro",\n versionSlug: "v1",\n active: true,\n name: "Workspace Pro",\n group: "workspace",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: requests.featureId,\n included: 10000,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport default atmn({\n features: [requests],\n plans: [workspaceFree, workspacePro],\n});\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\n#### Create the entity\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nawait autumn.entities.create({\n customerId: "org_123",\n entityId: "workspace_a",\n featureId: "workspaces",\n name: "Workspace A",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nawait autumn.entities.create(\n customer_id="org_123",\n entity_id="workspace_a",\n feature_id="workspaces",\n name="Workspace A",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/entities.create" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "entity_id": "workspace_a",\n "feature_id": "workspaces",\n "name": "Workspace A"\n }\'\n```\n\n</CodeGroup>\n\n#### Attach a plan to it\n\nPass `entityId` to scope the attach to that entity:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.billing.attach({\n customerId: "org_123",\n planId: "workspace_pro",\n entityId: "workspace_a",\n});\n```\n\n```python Python\nawait autumn.billing.attach(\n customer_id="org_123",\n plan_id="workspace_pro",\n entity_id="workspace_a",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing.attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "plan_id": "workspace_pro",\n "entity_id": "workspace_a"\n }\'\n```\n\n</CodeGroup>\n\nEach entity\'s subscription is created separately in Stripe, with billing cycles synced to the parent customer.\n\nTo upgrade or downgrade, attach the new plan with the same `entityId` — the usual [upgrade/downgrade](/documentation/customers/subscription-lifecycle) logic applies.\n\n#### Cancel an entity\'s plan\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.billing.update({\n customerId: "org_123",\n planId: "workspace_pro",\n entityId: "workspace_a",\n cancelAction: "cancel_end_of_cycle",\n});\n```\n\n```python Python\nawait autumn.billing.update(\n customer_id="org_123",\n plan_id="workspace_pro",\n entity_id="workspace_a",\n cancel_action="cancel_end_of_cycle",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing.update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "plan_id": "workspace_pro",\n "entity_id": "workspace_a",\n "cancel_action": "cancel_end_of_cycle"\n }\'\n```\n\n</CodeGroup>\n\nThe same [cancel/uncancel](/documentation/customers/subscription-lifecycle#cancellations) behavior applies.\n\n## Licenses\n\nA **license plan** describes everything one entity gets. The parent plan offers a pool of them, and you assign one to an entity to hand it its own balance.\n\n```\nteam plan ──licenses: [{ seat, included: 1 }]──► pool of seats\n │\n licenses.attach │ licenses.release\n ▼\n entity "user_alice" ──► own balance: 50 summaries/mo\n```\n\nThe pool has a `granted` size (included seats plus any paid seats), a `usage` count (seats currently assigned), and a `remaining` count. Assigning consumes a seat; releasing gives it back.\n\nCreate the feature each seat consumes, then a license plan holding what one seat gets. Link it from the parent plan via `licenses`:\n\n```ts autumn.config.ts\nimport { atmn, feature, license, plan } from "atmn";\n\nexport const summaries = feature({\n featureId: "summaries",\n name: "Meeting Summaries",\n type: "metered",\n consumable: true,\n});\n\n// Everything one seat gets, priced per seat.\nexport const seat = plan({\n planId: "seat",\n versionSlug: "v1",\n active: true,\n name: "Seat",\n group: "licenses",\n price: { amount: 30, interval: "month" },\n items: [\n {\n featureId: summaries.featureId,\n included: 50,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport const team = plan({\n planId: "team",\n versionSlug: "v1",\n active: true,\n name: "Team",\n licenses: [\n license({\n licensePlanId: seat.planId,\n versionSlug: seat.versionSlug,\n included: 1,\n }),\n ],\n});\n\nexport default atmn({ features: [summaries], plans: [seat, team] });\n```\n\n`included: 1` means the Team plan comes with one free seat. Seats beyond that are paid at the license plan\'s own price.\n\nPreview with `atmn push`, then apply with `atmn push --yes`.\n\nGive the license plan its own `group`. Attaching a plan replaces other plans in the same group, so a license plan sharing a group with its parent would knock the parent off.\n\n#### Buy seats\n\nSeats are bought on the parent plan. `quantity` is the **total** number of seats, including the plan\'s free `included` amount:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.billing.attach({\n customerId: "org_123",\n planId: "team",\n licenseQuantities: [{\n licensePlanId: "seat",\n quantity: 5,\n }],\n});\n```\n\n```python Python\nawait autumn.billing.attach(\n customer_id="org_123",\n plan_id="team",\n license_quantities=[{\n "license_plan_id": "seat",\n "quantity": 5,\n }],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/billing.attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "plan_id": "team",\n "license_quantities": [\n { "license_plan_id": "seat", "quantity": 5 }\n ]\n }\'\n```\n\n</CodeGroup>\n\nWith 1 included seat and `quantity: 5`, the customer gets 5 seats and pays for 4. Attach again with a new `quantity` to change the count later — Autumn prorates the difference.\n\nA **priced** license plan must be attached at the customer level before it can be assigned to entities. Buying seats with `licenseQuantities` does this for you.\n\n#### Assign a license\n\nAssigning is what provisions the entity\'s individual balance — creating an entity on its own does not:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.licenses.attach({\n customerId: "org_123",\n planId: "seat",\n entities: [\n { entityId: "user_alice", name: "Alice", featureId: "seats" },\n ],\n});\n```\n\n```python Python\nawait autumn.licenses.attach(\n customer_id="org_123",\n plan_id="seat",\n entities=[\n {"entity_id": "user_alice", "name": "Alice", "feature_id": "seats"},\n ],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/licenses.attach" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "plan_id": "seat",\n "entities": [\n { "entity_id": "user_alice", "name": "Alice", "feature_id": "seats" }\n ]\n }\'\n```\n\n</CodeGroup>\n\n`feature_id` is the entity type and is required only when the entity doesn\'t exist yet — Autumn creates it for you. You can pass several entities in one call.\n\nAssignment is idempotent. Re-assigning an entity that already holds an active license for the same plan succeeds without consuming another seat. If the pool has no seats left, the call errors — buy more seats first.\n\n#### Release a license\n\nThe entity\'s balance is removed and the seat returns to the pool, ready to reassign:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.licenses.release({\n customerId: "org_123",\n licensePlanId: "seat",\n entityIds: ["user_alice"],\n});\n```\n\n```python Python\nawait autumn.licenses.release(\n customer_id="org_123",\n license_plan_id="seat",\n entity_ids=["user_alice"],\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/licenses.release" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "license_plan_id": "seat",\n "entity_ids": ["user_alice"]\n }\'\n```\n\n</CodeGroup>\n\nReleasing frees the seat but does not change what the customer pays — they keep the seats they bought. To stop paying for one, attach the parent plan again with a lower `quantity`.\n\n`license_plan_id` is optional, and only needed to disambiguate when an entity holds licenses from more than one plan.\n\n#### Inspect seats\n\n[`licenses.list`](/api-reference/licenses/listLicenses) returns each pool with its `granted`, `usage`, and `remaining` counts. [`licenses.list_assignments`](/api-reference/licenses/listLicenseAssignments) returns which entities currently hold one.\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/licenses.list" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{ "customer_id": "org_123" }\'\n```\n\n## Checking and tracking per entity\n\nRegardless of how the entity got its plan, pass `entity_id` to `check` and `track` to operate on that entity\'s balance:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst { data } = await autumn.check({\n customer_id: "org_123",\n feature_id: "summaries",\n entity_id: "user_alice",\n});\n\nconsole.log(data.allowed);\nconsole.log(data.balance);\n```\n\n```python Python\nresponse = await autumn.check(\n customer_id="org_123",\n feature_id="summaries",\n entity_id="user_alice",\n)\n\nprint(response.allowed)\nprint(response.balance)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "feature_id": "summaries",\n "entity_id": "user_alice"\n }\'\n```\n\n</CodeGroup>\n\nTrack the same way:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.track({\n customer_id: "org_123",\n feature_id: "summaries",\n entity_id: "user_alice",\n value: 1,\n});\n```\n\n```python Python\nawait autumn.track(\n customer_id="org_123",\n feature_id="summaries",\n entity_id="user_alice",\n value=1,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "feature_id": "summaries",\n "entity_id": "user_alice",\n "value": 1\n }\'\n```\n\n</CodeGroup>\n\n### Customer-level vs entity-level\n\n| Level | How to use | Behavior |\n|-------|-----------|----------|\n| **Entity-level** | Pass `entity_id` in check/track | Checks/deducts from that entity\'s individual balance |\n| **Customer-level** | Omit `entity_id` | Returns the total balance across all entities |\n\nWhen tracking at the customer level (without `entity_id`), usage is deducted from the first-assigned entity to keep entity-level totals in sync with the customer-level total.\n\n## Worked example\n\n[Entity-level balances](/examples/entity-balances) walks the licenses model end to end: an AI meeting-notes product on team pricing, from customer creation through buying seats, assigning them, and releasing them when someone leaves.',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
path: "references/setup.md",
|
|
175
|
+
contents:
|
|
176
|
+
'## Setup and payments\n\nIn this example we\'ll create the pricing for a premium AI chatbot. We\'re going to have:\n\n- A <Badge color="green">Free</Badge> plan that gives users 5 chat messages per month for free\n- A <Badge color="blue">Pro</Badge> plan that gives users 100 chat messages per month for $20 per month.\n\n<Steps>\n<Step title="Create your pricing plans">\nCreate a plan for each pricing tier that your app offers. In our example we\'ll create a "Free" and "Pro" plan, and assign them features.\n\nBrowse our [Examples](/examples) for guides on setting up credit systems, top ups and other common pricing models.\n\nRun the following command in your root directory:\n\n<CodeGroup>\n```bash bun\nbunx atmn init\n```\n```bash npm\nnpx atmn init\n```\n```bash pnpm\npnpm dlx atmn init\n```\n</CodeGroup>\n\nThis will prompt you to login or create an account, and create an `autumn.config.ts` file. Paste in the code below, or view our [config schema](/cli/config) to build your own.\n\n```typescript autumn.config.ts [expandable]\nimport { atmn, feature, plan } from "atmn";\n\n// Features\nexport const messages = feature({\n featureId: "messages",\n name: "Messages",\n type: "metered",\n consumable: true,\n});\n\n// Plans\nexport const free = plan({\n planId: "free",\n versionSlug: "v1",\n active: true,\n name: "Free",\n autoEnable: true,\n items: [\n // 5 messages per month\n {\n featureId: messages.featureId,\n included: 5,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: {\n amount: 20,\n interval: "month",\n },\n items: [\n // 100 messages per month\n {\n featureId: messages.featureId,\n included: 100,\n reset: { interval: "month" },\n },\n ],\n});\n\nexport default atmn({ features: [messages], plans: [free, pro] });\n```\n\nThen, preview your changes against Autumn\'s sandbox environment.\n\n<CodeGroup>\n```bash bun\nbunx atmn push\n```\n```bash npm\nnpx atmn push\n```\n```bash pnpm\npnpm dlx atmn push\n```\n</CodeGroup>\n\nOnce the preview looks right, apply it:\n\n<CodeGroup>\n```bash bun\nbunx atmn push --yes\n```\n```bash npm\nnpx atmn push --yes\n```\n```bash pnpm\npnpm dlx atmn push --yes\n```\n</CodeGroup>\n\n If you already have products created in the dashboard, run `atmn pull` to\n pull them into your local config.\n\n</Step>\n\n<Step title="Installation">\n[Create an Autumn Secret key](https://app.useautumn.com/sandbox/dev?tab=api_keys), and paste it in your `.env` variables. Then, install the Autumn SDK. If you\'re using the CLI, this will be done for you.\n\n```bash .env\nAUTUMN_SECRET_KEY=am_sk_test_42424242...\n```\n\n<CodeGroup>\n\n```bash bun\nbun add autumn-js\n```\n\n```bash npm\nnpm install autumn-js\n```\n\n```bash pnpm\npnpm add autumn-js\n```\n\n```bash yarn\nyarn add autumn-js\n```\n\n```bash pip\npip install autumn-sdk\n```\n\n</CodeGroup>\n\n</Step>\n\n<Step title="Create an Autumn customer">\nWhen the customer signs up, create an Autumn customer for them. Autumn will automatically enable the <Badge color="green">Free</Badge> plan, since you marked it with the `auto-enable` flag.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({\n secretKey: "am_sk_42424242",\n});\n\nconst customer = await autumn.customers.getOrCreate({\n customerId: "user_or_org_id_from_auth",\n name: "John Doe",\n email: "john@example.com",\n});\n```\n\n```python Python\nimport asyncio\nfrom autumn_sdk import Autumn\n\nautumn = Autumn(\'am_sk_42424242\')\n\nasync def main():\n customer = await autumn.customers.get_or_create(\n customer_id="user_or_org_id_from_auth",\n name="John Doe",\n email="john@example.com",\n )\n\nasyncio.run(main())\n```\n\n```bash cURL\ncurl --request POST \\\n --url https://api.useautumn.com/v1/customers \\\n --header \'Authorization: Bearer am_sk_42424242\' \\\n --header \'Content-Type: application/json\' \\\n --data \'{\n "customer_id": "user_or_org_id_from_auth",\n "name": "John Doe",\n "email": "john@example.com"\n}\'\n```\n\n</CodeGroup>\n\n Autumn\'s customer ID is the same as your internal user or org ID generated\n from your auth provider. No need to store any extra IDs.\n\nIn the Autumn dashboard, you will see your user under the [customers](https://app.useautumn.com/customers) page.\n\n</Step>\n\n<Step title="Stripe Payment Flow">\nCall `attach` when the customer wants to purchase the <Badge color="blue">Pro</Badge> plan. This will return a Stripe payment URL. Once they\'ve paid, Autumn will grant access to "100 messages per month" defined in Step 1.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({\n secretKey: "am_sk_42424242",\n});\n\nconst response = await autumn.billing.attach({\n customerId: "user_or_org_id_from_auth",\n planId: "pro",\n redirectMode: "always",\n});\n\n// Redirect customer to complete payment or confirm plan change\nredirect(response.paymentUrl);\n```\n\n```python Python\nimport asyncio\nfrom autumn_sdk import Autumn\n\nautumn = Autumn(\'am_sk_42424242\')\n\nasync def main():\n response = await autumn.billing.attach(\n customer_id=\'user_or_org_id_from_auth\',\n plan_id=\'pro\',\n redirect_mode=\'always\',\n )\n\nasyncio.run(main())\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/attach\' \\\n-H \'Authorization: Bearer am_sk_42424242\' \\\n-H \'Content-Type: application/json\' \\\n-d \'{\n "customer_id": "user_or_org_id_from_auth",\n "plan_id": "pro",\n "redirect_mode": "always"\n}\'\n```\n\n</CodeGroup>\n\n Use Stripe\'s test card `4242 4242 4242 4242` to make a purchase in sandbox.\n You can enter any Expiry and CVV.\n\nThis can be used for any plan changes scenario (upgrades, downgrades, one-time topups, renewals, etc).\n\nUpgrades will happen immediately, and downgrades will be scheduled for the next billing cycle.\n\nThe **`redirectMode: "always"`** flag will always return a payment URL.\n\nNew purchases redirect to Stripe Checkout to enter payment details, and subsequent charges redirect to an Autumn hosted, one-click confirmation page.\n\nYou can build your own billing confirmation flows by using the [previewAttach](/api-reference/billing/previewAttach) function.\n\n</Step>\n</Steps>\n\n**Next: Track and limit usage**\n\nNow that the plan is enabled and you\'ve handled payments, you can now make sure that customers have the access to the right features and limits based on their plan.\n\n Enforce usage limits and feature permissions using Autumn\'s `check` and\n `track` functions',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
path: "references/creating-customers.md",
|
|
180
|
+
contents:
|
|
181
|
+
'## Creating Customers\n\nCustomers represent the entities — usually users or organizations — of your application that can use and pay for your products.\n\nFor each customer, Autumn will:\n\n- Keep record of the products they\'ve purchased\n- Track the features they\'ve used and have access to\n- Bill them through Stripe for the prices you\'ve set\n\n## Creating a customer via API\n\nUse the `customers.getOrCreate` method to create a customer. This is idempotent — it creates the customer if they don\'t exist, or returns the existing one if they do.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst customer = await autumn.customers.getOrCreate({\n customerId: "user_123",\n name: "John Doe",\n email: "john@example.com",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\ncustomer = await autumn.customers.get_or_create(\n customer_id="user_123",\n name="John Doe",\n email="john@example.com",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "name": "John Doe",\n "email": "john@example.com"\n }\'\n```\n\n</CodeGroup>\n\nOnly the `customerId` field is required — this should be your unique identifier for the customer that you\'ll use in all future API calls.\n\nA common pattern is to call `customers.getOrCreate` on every login or signup in your application, so Autumn always has the latest customer information.\n\nCustomers must be created before calling the [`check`](/documentation/customers/check) or [`track`](/documentation/customers/tracking-usage) endpoints. If you call these endpoints with a `customer_id` that doesn\'t exist, the API will return a `customer_not_found` error. Make sure to call `customers.getOrCreate` during signup or login before checking access or tracking usage.\n\n## Pre-creating customers via the dashboard\n\nYou can create a customer in the Autumn dashboard before they\'ve ever interacted with your application. This is useful for enterprise or sales-led deals where you want to provision access before the customer signs up.\n\n1. Navigate to the [Customers page](https://app.useautumn.com/customers)\n2. Click "Create Customer"\n3. Fill in the customer\'s details (name, email). **Leave the `id` field blank** — it will be assigned when the customer first logs in.\n4. Click "Create Customer"\n\nOnce the customer is created, you can enable products and configure their features from the customer details page. When the customer eventually signs up in your application, Autumn will match them by email and link the pre-created customer record.\n\nThe email you provide must match the email the customer will use to sign up or log in. You can update the email from the customer details page if needed.\n\n**Example: Enterprise onboarding**\n\nYou\'ve closed an enterprise deal with Acme Corp. Before their team starts using your product:\n\n1. Create a customer in the dashboard with the billing contact\'s email\n2. Enable a custom Enterprise plan with negotiated pricing\n3. When the Acme team signs up, Autumn matches the email and they immediately have their plan active — no checkout needed\n\n## Customer properties\n\n#### Customer ID\n\nYour unique identifier for the customer. This is the only required field. It could be:\n\n- Your database ID for the user\n- Their email address\n- Any other unique identifier in your system\n\n#### Name and Email\n\nOptional fields that help identify the customer in the Autumn dashboard and on Stripe invoices.\n\n## Stripe integration\n\nBy default, Autumn does **not** create a Stripe customer when you create an Autumn customer. A Stripe customer is created lazily — only when the first billing operation needs one (like `billing.attach`, `billing.openCustomerPortal`, or `billing.setupPayment`).\n\n```mermaid\nflowchart LR\n A["Your app<br/><code>user_123</code>"] -->|customers.getOrCreate| B["Autumn Customer<br/><code>user_123</code>"]\n B -->|on first billing call| C["Stripe Customer<br/><code>cus_abc123</code>"]\n```\n\nYou can change this behavior:\n\n#### Create in Stripe immediately\n\nPass `createInStripe: true` to create the Stripe customer at the same time as the Autumn customer. This is useful if you need the Stripe customer ID upfront (e.g., for your own Stripe integration).\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.getOrCreate({\n customerId: "user_123",\n name: "John Doe",\n email: "john@example.com",\n createInStripe: true,\n});\n```\n\n```python Python\nawait autumn.customers.get_or_create(\n customer_id="user_123",\n name="John Doe",\n email="john@example.com",\n create_in_stripe=True,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "name": "John Doe",\n "email": "john@example.com",\n "create_in_stripe": true\n }\'\n```\n\n</CodeGroup>\n\n#### Link to an existing Stripe customer\n\nIf you already have a Stripe customer (e.g., you\'re migrating to Autumn), pass `stripeId` to link it instead of creating a new one:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.getOrCreate({\n customerId: "user_123",\n stripeId: "cus_abc123",\n});\n```\n\n```python Python\nawait autumn.customers.get_or_create(\n customer_id="user_123",\n stripe_id="cus_abc123",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "stripe_id": "cus_abc123"\n }\'\n```\n\n</CodeGroup>\n\nOnce linked, the mapping is bidirectional — the Stripe customer ID is stored on the Autumn customer, and the Autumn customer ID is stored in the Stripe customer\'s metadata.\n\nFor more details on how Autumn and Stripe work together, see [Stripe Sync](/documentation/concepts/stripe).',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
path: "references/feature-entities.md",
|
|
185
|
+
contents:
|
|
186
|
+
'## Entities\n\nEntities are sub accounts of a customer. For example, you may have a product that allows 500 credits **per user** per month.\n\n## Product setup\n\nAn entity can hold its own plan, with its own balances, while the parent customer pays. There are two ways to provision that, differing in where capacity comes from:\n\n1. **Attach directly**: pass the [entity ID](/api-reference/billing/attach#body-entity-id) into an attach call. The entity gets its own subscription in Stripe, with billing cycles synced to the parent. Use this when entities appear and you bill for them as they do.\n2. **Licenses**: the parent plan offers a pool of seats that the customer buys upfront, and you assign one to an entity to give it its plan. Use this when customers commit to a seat count before you know who fills it.\n\nBoth support different tiers per entity. See [entity plans](/documentation/modelling-pricing/entity-plans) for the full setup of either.\n\n**Example**\n\nMax has a team product where each seat gets 30 meeting note summaries per month, and teams buy their seat count upfront. He creates a Seat license plan holding the 30 summaries, offers it under his Team plan, and assigns a license each time someone joins.\n\nJamie also has a team product, but workspaces are created ad hoc and each can be on a Free or Pro tier. She creates the two tiers as normal and attaches them at the entity level as workspaces appear.\n\n## Creating entities\n\nYou can manage feature entities via the `entities` route. This can be used to create, update and delete entities, such as when a seat or workspace is added or removed.\n\nWhen you create an entity, a **usage event** will automatically be sent that increments the count of the number of entities (eg seats) being used.\n\nThis means if you create 2 entities for "seats", your usage of "seats" will be 2, allowing Autumn to bill accordingly if there\'s a price set.\n\nThis is how seats are counted when you attach plans directly. With [licenses](/documentation/modelling-pricing/entity-plans#licenses), seats are counted by the license pool instead, and the entity\'s feature only identifies its type.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.entities.create({\n customerId: "org_123",\n entityId: "user_abc",\n featureId: "seats",\n name: "John Doe",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.entities.create(\n customer_id="org_123",\n entity_id="user_abc",\n feature_id="seats",\n name="John Doe",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/entities" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "entity_id": "user_abc",\n "feature_id": "seats",\n "name": "John Doe"\n }\'\n```\n\n</CodeGroup>\n\n## Managing entity balances\n\nJust like with normal features, you can check feature access and track usage events for each entity.\n\n#### Check feature access\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst response = await autumn.check({\n customerId: "org_123",\n featureId: "ai-messages",\n entityId: "user_abc",\n});\n\nconsole.log(response.allowed);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.check(\n customer_id="org_123",\n feature_id="ai-messages",\n entity_id="user_abc",\n)\nprint(response.allowed)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "feature_id": "ai-messages",\n "entity_id": "user_abc"\n }\'\n```\n\n</CodeGroup>\n\n#### Send usage event\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.track({\n customerId: "org_123",\n featureId: "ai-messages",\n entityId: "user_abc",\n value: 10,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.track(\n customer_id="org_123",\n feature_id="ai-messages",\n entity_id="user_abc",\n value=10,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "feature_id": "ai-messages",\n "entity_id": "user_abc",\n "value": 10\n }\'\n```\n\n</CodeGroup>\n\n#### Customer-level vs entity-level balances\n\nA customer-level balance is the total balance for the feature across all entities. Entity-level balances are the balance for a specific entity. You can check access and track usage events either at the customer-level or entity-level.\n\n**Example**\n\nYou have a product that allows 500 credits per user per month. However, the credits are shared across all users in the account.\n\nYou can create a feature entity for "seats" and then check access and send usage events at the top-level.\n\nTo use top-level balances, just omit the `entityId` from your check request.\n\n This will increment the usage counter for the top-level balance, and also\n deduct from the first-created entity so that the sum of the the entity\n balances is always the same as the top-level balance.\n\n## Deleting Entities\n\n Just as creating an entity sent a usage event for the associated feature,\n deleting an entity will decrease the usage.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.entities.delete({\n customerId: "org_123",\n entityId: "user_abc",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.entities.delete(\n customer_id="org_123",\n entity_id="user_abc",\n)\n```\n\n```bash cURL\ncurl -X DELETE "https://api.useautumn.com/v1/entities/user_abc" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{ "customer_id": "org_123" }\'\n```\n\n</CodeGroup>',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
path: "references/payment-flow.md",
|
|
190
|
+
contents:
|
|
191
|
+
'## Payment Flow\n\n## Using hosted pages\n\nPass `redirectMode: "always"` and `billing.attach` will always return a `paymentUrl` — just redirect the customer and Autumn handles payment collection, confirmation, and activation.\n\n<CodeGroup>\n\n```typescript TypeScript\nconst response = await autumn.billing.attach({\n customerId: "user_123",\n planId: "pro",\n redirectMode: "always",\n});\n\nredirect(response.paymentUrl);\n```\n\n```python Python\nresponse = await autumn.billing.attach(\n customer_id="user_123",\n plan_id="pro",\n redirect_mode="always",\n)\n# Redirect to response.payment_url\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.attach\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "redirect_mode": "always"\n }\'\n```\n\n</CodeGroup>\n\nNew customers without a payment method are sent to **Stripe Checkout**. Existing customers are sent to **Autumn Checkout** to review and confirm. After checkout, the customer is redirected to your `successUrl` (or the default URL in your Autumn dashboard).\n\n## Building your own checkout\n\nFor full control over the checkout experience, use `redirectMode: "if_required"`. This charges the saved payment method directly instead of redirecting — the customer is only sent to Stripe Checkout if they don\'t have a payment method yet.\n\n```mermaid actions={false}\ngraph LR\n A["previewAttach"] --> B["user confirms"] --> C["attach"] --> D{"Payment method?"}\n D -->|Yes| E["Charged"]\n D -->|No| F["Stripe Checkout"]\n E --> G["Plan enabled"]\n F --> G\n style A fill:#f472b622,stroke:#f472b6\n style C fill:#f472b622,stroke:#f472b6\n```\n\n### Step 1: Preview the charge\n\nCall `billing.previewAttach` to get line items, totals, and proration details to display in your UI.\n\n<CodeGroup>\n\n```typescript TypeScript\nconst preview = await autumn.billing.previewAttach({\n customerId: "user_123",\n planId: "pro",\n});\n\n// preview.lineItems — array of charges and credits\n// preview.total — net amount\n// preview.currency — e.g. "usd"\n```\n\n```python Python\npreview = await autumn.billing.preview_attach(\n customer_id="user_123",\n plan_id="pro",\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.preview_attach\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro"\n }\'\n```\n\n</CodeGroup>\n\n```json\n{\n "customerId": "user_123",\n "lineItems": [\n {\n "title": "Pro Plan",\n "description": "Monthly subscription",\n "amount": 20\n },\n {\n "title": "Credit for Free Plan",\n "description": "Unused time on current plan",\n "amount": -5\n }\n ],\n "total": 15,\n "currency": "usd",\n "nextCycle": {\n "startsAt": 1735689600000,\n "total": 20\n }\n}\n```\n\n### Step 2: Confirm and charge\n\nOnce the customer confirms, call `billing.attach` with `redirectMode: "if_required"`.\n\n<CodeGroup>\n\n```typescript TypeScript\nconst response = await autumn.billing.attach({\n customerId: "user_123",\n planId: "pro",\n redirectMode: "if_required",\n});\n\nif (response.paymentUrl) {\n redirect(response.paymentUrl);\n} else {\n showSuccess();\n}\n```\n\n```python Python\nresponse = await autumn.billing.attach(\n customer_id="user_123",\n plan_id="pro",\n redirect_mode="if_required",\n)\n\nif response.payment_url:\n redirect(response.payment_url)\nelse:\n show_success()\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.attach\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "redirect_mode": "if_required"\n }\'\n```\n\n</CodeGroup>\n\n## Handling the response\n\nThe `billing.attach` response has two key fields:\n\n**`payment_url`** — a URL the customer should be redirected to, or `null` if no redirect is needed.\n\n**`required_action`** — present when payment couldn\'t be processed automatically. See [Edge Cases](/documentation/customers/edge-cases) for details on handling 3DS, payment failures, and retries.',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
path: "references/subscription-lifecycle.md",
|
|
195
|
+
contents:
|
|
196
|
+
'## Subscription Lifecycle\n\n## Upgrades\n\nUpgrades happen when you attach a plan with a higher price than the customer\'s current plan. Use `billing.attach` — Autumn handles the rest.\n\nIf a payment method exists, attaching the plan will immediately charge the customer. If upgrading from a free to a paid plan, a checkout URL is generated instead.\n\n**Pricing behavior:**\n\n- **Fixed prices** are prorated based on time remaining in the billing period\n- **Usage-based prices** bill outstanding usage at the old rate immediately, then apply the new rate going forward\n\n## Downgrades\n\nDowngrades happen when you attach a plan with a lower price. Unlike upgrades, downgrades are **scheduled** to take effect at the end of the current billing period.\n\nThe new plan will have status `scheduled` until it activates. Customers can cancel a scheduled downgrade by re-attaching their current plan.\n\nIf you\'ve set a [`group`](/documentation/concepts/plans#plan-properties) when creating plans, upgrades and downgrades only apply between plans in the same group. Attaching a plan from a different group adds it alongside the existing plan.\n\n## Cancellations\n\nCancel a subscription using `billing.update` with the `cancelAction` parameter. By default, cancellations take effect at the end of the billing period.\n\n### Cancel at end of billing period\n\n<CodeGroup>\n\n```typescript TypeScript\nconst response = await autumn.billing.update({\n customerId: "user_123",\n planId: "pro",\n cancelAction: "cancel_end_of_cycle",\n});\n```\n\n```python Python\nresponse = await autumn.billing.update(\n customer_id="user_123",\n plan_id="pro",\n cancel_action="cancel_end_of_cycle",\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.update\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "cancel_action": "cancel_end_of_cycle"\n }\'\n```\n\n</CodeGroup>\n\nThe subscription remains active until the end of the current billing period. If you have a default plan (with `is_default` set), it will be activated after the cancellation takes effect.\n\n### Cancel immediately\n\nTo cancel a subscription right away with a prorated refund:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst response = await autumn.billing.update({\n customerId: "user_123",\n planId: "pro",\n cancelAction: "cancel_immediately",\n});\n```\n\n```python Python\nresponse = await autumn.billing.update(\n customer_id="user_123",\n plan_id="pro",\n cancel_action="cancel_immediately",\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.update\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "cancel_action": "cancel_immediately"\n }\'\n```\n\n</CodeGroup>\n\nThis ends the subscription immediately and issues a prorated refund for the remaining time in the billing period.\n\n## Uncanceling\n\nIf a subscription was scheduled for cancellation (via `cancel_end_of_cycle`), you can reverse it before the period ends using `uncancel`:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst response = await autumn.billing.update({\n customerId: "user_123",\n planId: "pro",\n cancelAction: "uncancel",\n});\n```\n\n```python Python\nresponse = await autumn.billing.update(\n customer_id="user_123",\n plan_id="pro",\n cancel_action="uncancel",\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.update\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "cancel_action": "uncancel"\n }\'\n```\n\n</CodeGroup>\n\nThis clears the pending cancellation and the subscription continues as normal. Any default plan that was scheduled to activate after cancellation is also removed.\n\nYou cannot uncancel a subscription that was already canceled immediately — only pending cancellations (scheduled for end of cycle) can be reversed.\n\n## Canceling a scheduled plan change\n\nWhen a downgrade or other plan change is **scheduled** for the end of the billing period, canceling it works the same way as uncanceling. Call `billing.update` with `cancelAction: "uncancel"` on the customer\'s **active** plan:\n\n<CodeGroup>\n\n```typescript TypeScript\n// Customer is on Pro with a scheduled downgrade to Basic.\n// Cancel the scheduled change and keep Pro.\nconst response = await autumn.billing.update({\n customerId: "user_123",\n planId: "pro",\n cancelAction: "uncancel",\n});\n```\n\n```python Python\n# Customer is on Pro with a scheduled downgrade to Basic.\n# Cancel the scheduled change and keep Pro.\nresponse = await autumn.billing.update(\n customer_id="user_123",\n plan_id="pro",\n cancel_action="uncancel",\n)\n```\n\n```bash cURL\n# Customer is on Pro with a scheduled downgrade to Basic.\n# Cancel the scheduled change and keep Pro.\ncurl -X POST \'https://api.useautumn.com/v1/billing.update\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "cancel_action": "uncancel"\n }\'\n```\n\n</CodeGroup>\n\nThis removes the scheduled replacement plan and keeps the customer on their current plan.\n\n## `cancel_action` reference\n\n| Value | Behavior |\n|---|---|\n| `cancel_end_of_cycle` | Schedules cancellation at the end of the current billing period. Subscription stays active until then. |\n| `cancel_immediately` | Cancels immediately with a prorated refund for remaining time. |\n| `uncancel` | Reverses a pending cancellation or removes a scheduled plan change. |\n\n## Usage reset behavior\n\nWhen a new plan is enabled, you can control what happens to existing feature usage with the `reset_usage_when_enabled` property on the plan item:\n\n- `true`: Usage resets to 0 (typical for consumable features like credits)\n- `false`: Usage carries over to the new plan (typical for continuous features like seats)\n\n**Example:** A customer on Free has used 20 of their 100 credits. They upgrade to Pro which includes 500 credits.\n\n- If `reset_usage_when_enabled = true`: They get 500 credits\n- If `reset_usage_when_enabled = false`: They get 480 credits (500 - 20 used)\n\n## Carry-over on plan upgrades\n\nWhen a customer upgrades plans, you can preserve their unused balances or account for their existing usage. Two parameters on [`billing.attach`](/api-reference/billing/attach) give you fine-grained control over what happens to consumable features during an immediate upgrade.\n\nCarry-over only works with **immediate** upgrades (`planSchedule: "immediate"` or default upgrade behavior). Scheduled plan changes and downgrades do not support carry-over.\n\n### Carrying over unused balances\n\nUse `carryOverBalances` to preserve a customer\'s remaining balance from the old plan. The unused credits are added as a one-off balance on the new plan, so the customer doesn\'t lose what they\'ve already paid for.\n\n<CodeGroup>\n\n```typescript TypeScript\nconst response = await autumn.billing.attach({\n customerId: "user_123",\n planId: "enterprise",\n carryOverBalances: {\n enabled: true,\n },\n});\n```\n\n```python Python\nresponse = await autumn.billing.attach(\n customer_id="user_123",\n plan_id="enterprise",\n carry_over_balances={\n "enabled": True,\n },\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.attach\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "enterprise",\n "carry_over_balances": {\n "enabled": true\n }\n }\'\n```\n\n</CodeGroup>\n\n**Example:** A customer on Pro has 300 of 1,000 credits remaining. They upgrade to Enterprise (2,000 credits/month). With `carryOverBalances` enabled, the 300 unused credits carry forward — giving them 2,300 credits on the new plan. The carried-over balance expires at the next reset or end of cycle.\n\n### Carrying over prior usage\n\nUse `carryOverUsages` to deduct prior usage from the new plan\'s allowance, preventing customers from getting a free reset on upgrade.\n\n<CodeGroup>\n\n```typescript TypeScript\nconst response = await autumn.billing.attach({\n customerId: "user_123",\n planId: "enterprise",\n carryOverUsages: {\n enabled: true,\n },\n});\n```\n\n```python Python\nresponse = await autumn.billing.attach(\n customer_id="user_123",\n plan_id="enterprise",\n carry_over_usages={\n "enabled": True,\n },\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.attach\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "enterprise",\n "carry_over_usages": {\n "enabled": true\n }\n }\'\n```\n\n</CodeGroup>\n\n**Example:** A customer on Pro has used 700 of 1,000 credits this month. They upgrade to Enterprise (2,000 credits/month). With `carryOverUsages` enabled, 700 usage is deducted from the new allowance — giving them 1,300 remaining instead of a full 2,000.\n\n### Scoping to specific features\n\nBoth parameters accept an optional `featureIds` array to limit carry-over to specific features:\n\n```typescript TypeScript\nconst response = await autumn.billing.attach({\n customerId: "user_123",\n planId: "enterprise",\n carryOverBalances: {\n enabled: true,\n featureIds: ["credits", "api_calls"],\n },\n carryOverUsages: {\n enabled: true,\n featureIds: ["credits"],\n },\n});\n```\n\nOnly consumable metered features support carry-over. Boolean features, unlimited features, and allocated (non-consumable) features are not eligible.',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
path: "references/updating-subscriptions.md",
|
|
200
|
+
contents:
|
|
201
|
+
'## Updating Subscriptions\n\nUse `billing.update` to modify an existing subscription. This is different from `billing.attach` which is for attaching new plans or changing between plans.\n\n**When to use `billing.update`:**\n\n- **Update feature quantities** — if your plan contains prepaid features (like seats)\n- **Cancel or uncancel** — cancel a subscription immediately or at end of cycle\n- **Customize the plan** — modify pricing or feature configuration (advanced)\n\n## Updating prepaid feature quantities\n\nPrepaid features are features where customers pay upfront for a quantity (e.g., seats, team members). Here\'s an example plan with a prepaid `seats` feature:\n\n```typescript autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const seats = feature({\n featureId: "seats",\n name: "Seats",\n type: "metered",\n consumable: false, // Non-consumable = doesn\'t reset\n});\n\nexport const team = plan({\n planId: "team",\n versionSlug: "v1",\n active: true,\n name: "Team Plan",\n price: {\n amount: 49,\n interval: "month",\n },\n items: [\n {\n featureId: seats.featureId,\n included: 5, // 5 seats included\n price: {\n amount: 10, // $10 per additional seat\n interval: "month",\n billingMethod: "prepaid",\n },\n },\n ],\n});\n\nexport default atmn({ features: [seats], plans: [team] });\n```\n\nTo update the quantity of seats for a customer:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst response = await autumn.billing.update({\n customerId: "user_123",\n planId: "team",\n featureQuantities: [\n { featureId: "seats", quantity: 10 }\n ],\n});\n```\n\n```python Python\nresponse = await autumn.billing.update(\n customer_id="user_123",\n plan_id="team",\n feature_quantities=[\n { "feature_id": "seats", "quantity": 10 }\n ],\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing/update\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "team",\n "feature_quantities": [\n { "feature_id": "seats", "quantity": 10 }\n ]\n }\'\n```\n\n</CodeGroup>\n\n## Canceling a subscription\n\nUse `cancelAction` to cancel or uncancel a subscription:\n\n| Action | Description |\n|--------|-------------|\n| `cancel_end_of_cycle` | Cancel at the end of the current billing period. Customer retains access until then. |\n| `cancel_immediately` | Cancel immediately with a prorated refund. |\n| `uncancel` | Reverse a pending cancellation (only works if not yet expired). |\n\n<CodeGroup>\n\n```typescript TypeScript\n// Cancel at end of billing cycle\nawait autumn.billing.update({\n customerId: "user_123",\n planId: "pro",\n cancelAction: "cancel_end_of_cycle",\n});\n\n// Uncancel a pending cancellation\nawait autumn.billing.update({\n customerId: "user_123",\n planId: "pro",\n cancelAction: "uncancel",\n});\n```\n\n```python Python\n# Cancel at end of billing cycle\nawait autumn.billing.update(\n customer_id="user_123",\n plan_id="pro",\n cancel_action="cancel_end_of_cycle",\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing/update\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "cancel_action": "cancel_end_of_cycle"\n }\'\n```\n\n</CodeGroup>\n\n## How billing works\n\nBy default, updating a subscription with billing changes will generate an invoice:\n\n- **Increasing quantity** (e.g., adding seats) charges a prorated amount for the remainder of the billing cycle\n- **Decreasing quantity** (e.g., removing seats) generates a credit applied to the next invoice\n- **Canceling immediately** generates a negative invoice granting credits to the customer\n\n### Previewing changes before executing\n\nSimilar to [`billing.previewAttach`](/documentation/customers/payment-flow#step-1-preview-the-charge), you can use `billing.previewUpdate` to see exactly what will be charged before making changes. This returns line items and totals that you can display in a confirmation UI.\n\n<CodeGroup>\n\n```typescript TypeScript\nconst preview = await autumn.billing.previewUpdate({\n customerId: "user_123",\n planId: "team",\n featureQuantities: [\n { featureId: "seats", quantity: 10 }\n ],\n});\n\n// Display preview.lineItems, preview.total, preview.currency\n// Then call billing.update to execute\n```\n\n```python Python\npreview = await autumn.billing.preview_update(\n customer_id="user_123",\n plan_id="team",\n feature_quantities=[\n { "feature_id": "seats", "quantity": 10 }\n ],\n)\n\n# Display preview.line_items, preview.total\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing/preview-update\' \\\n -H \'Authorization: Bearer am_sk_...\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "team",\n "feature_quantities": [\n { "feature_id": "seats", "quantity": 10 }\n ]\n }\'\n```\n\n</CodeGroup>\n\n### Skipping charges\n\nIf you want to update a subscription without creating any charges or credits, pass `prorationBehavior: "none"`:\n\n```typescript\nawait autumn.billing.update({\n customerId: "user_123",\n planId: "team",\n featureQuantities: [\n { featureId: "seats", quantity: 10 }\n ],\n prorationBehavior: "none", // No charges or credits created\n});\n```\n\nThis is useful for administrative adjustments or when you want to handle billing separately.',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
path: "references/edge-cases.md",
|
|
205
|
+
contents:
|
|
206
|
+
'## Billing Reliability\n\nIn addition to the subscription lifecycle, Autumn automatically handles a number of edge cases to make sure your customers are always billed correctly.\n\n## 3D Secure (3DS)\n\nWhen a payment requires 3D Secure authentication, the `attach` response returns:\n\n```json\n{\n "required_action": {\n "code": "3ds_required",\n "reason": "Payment requires 3D Secure authentication"\n },\n "payment_url": "https://invoice.stripe.com/..."\n}\n```\n\nAs normal, redirect the customer to the `payment_url` to complete authentication. Once they authenticate, Autumn processes the payment and activates the plan automatically via the `invoice.paid` webhook.\n\nThe invoice URL expires after **10 minutes**. If the customer doesn\'t complete authentication in time, the invoice is automatically voided and the attach must be retried.\n\n## Payment Failures\n\nIf the customer\'s payment method is declined during `attach`, the response returns:\n\n```json\n{\n "required_action": {\n "code": "payment_failed",\n "reason": "Card was declined"\n },\n "payment_url": "https://invoice.stripe.com/..."\n}\n```\n\nThe `payment_url` links to Stripe\'s hosted invoice page where the customer can update their payment method and retry. As with 3DS, the invoice is auto-voided after 10 minutes if left unresolved.\n\nA third code, `payment_method_required`, is returned when no payment method is on file at all. In this case, redirect the customer to the `payment_url` which points to a Stripe Checkout session.\n\n## Past Due Subscriptions\n\nIf a recurring payment fails (e.g. card expired between billing cycles), the subscription status becomes `past_due`. To resolve this:\n\n1. Direct the customer to the [billing portal](/api-reference/billing/openCustomerPortal) to update their payment method\n2. Once updated, Stripe automatically retries the failed invoice\n\n<CodeGroup>\n\n```typescript Node.js\nconst { data } = await autumn.billing.openCustomerPortal("user_123", {\n returnUrl: "https://your-app.com/billing",\n});\n// Redirect to data.url\n```\n\n```python Python\nresponse = await autumn.billing.open_customer_portal(\n "user_123",\n return_url="https://your-app.com/billing",\n)\n# Redirect to response.url\n```\n\n</CodeGroup>\n\nIf you\'d like to block feature access when a subscription is `past_due`, please contact us. We can enable a configuration flag to do this for you.\n\n## Subscription Expiry\n\nWhen a subscription is deleted or expires, any open invoices associated with it are automatically voided. This prevents a scenario where a customer could pay a stale invoice for a subscription that no longer exists — the payment would go through but have no effect.\n\nThis also applies when a subscription transitions to `past_due` and is automatically canceled (if that behavior is enabled for your organization).\n\n## Proration Failures\n\nWhen an upgrade generates a proration invoice that fails to pay, Autumn automatically **rolls back the subscription update** to the customer\'s previous plan. The open invoice is then handled through the same 3DS / payment failure flow described above — the customer receives an invoice URL to resolve the payment.\n\n## Concurrent Requests\n\nAutumn uses distributed locking to prevent race conditions across billing operations. All mutating billing endpoints — `attach`, `multi_attach`, and `update_subscription` — share a per-customer lock. If two requests arrive simultaneously for the same customer, the second request receives a `429` response. This prevents duplicate subscriptions, double charges, or conflicting subscription updates.\n\nThe same lock is shared with auto top-ups, so a top-up triggered by usage can\'t race against a manual attach for the same customer.\n\n## Duplicate Webhook Delivery\n\nStripe may deliver the same webhook event multiple times. Autumn deduplicates webhooks using a per-event idempotency key — if the same Stripe event ID is received more than once within a 5-minute window, the duplicate is acknowledged without reprocessing.\n\nAdditionally, when Autumn initiates a subscription change (e.g. a cancellation or upgrade), it sets a short-lived lock on the subscription. This prevents the resulting Stripe webhook from re-processing the change that Autumn already applied, avoiding double-counting or conflicting state updates.\n\n## API Idempotency\n\nAll API requests support an `Idempotency-Key` header. If the same key is sent within 24 hours, the duplicate request is rejected with a `409` response. This is useful when retrying requests after network failures — you won\'t accidentally attach the same plan twice.\n\nEvent tracking (`track`) uses the same header. Pass it as a request option rather than a body field:\n\n```typescript\nawait autumn.track(\n { customerId: "cus_123", featureId: "api_calls", value: 1 },\n { headers: { "Idempotency-Key": "request_abc123" } }\n);\n```',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
path: "references/gating.md",
|
|
210
|
+
contents:
|
|
211
|
+
'## Checking and tracking\n\nTypically, your users should get access to different features and usage limits, depending on their plan.\n\nAutumn handles your customer\'s payments and grants them the features defined in your plan configuration. There are 2 functions you need to enforce limits and gating:\n\n- `check` for feature access, before allowing a user to do something\n- `track` the usage in Autumn afterwards (if needed)\n\nThis example will continue from before: a 2-tier pricing model for a chatbot.\n\nThis guide shows an asynchronous approach to checking and tracking. You can also [check and reserve](/documentation/customers/check#checking-and-reserving-usage) balance in a single, atomic API call for concurrent events.\n\n### Checking feature access\n\nCheck if a user has enough remaining balance of messages, before executing the action. The `feature_id` used here is defined by you when you create the feature in Autumn.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({\n secretKey: \'am_sk_42424242\',\n});\n\n// Check if user can send 1 message\nconst { allowed } = await autumn.check({\n customerId: "user_or_org_id_from_auth",\n featureId: "messages",\n requiredBalance: 1,\n});\n\nif (!allowed) {\n console.log("User has run out of messages");\n return;\n}\n```\n\n```python Python\nimport asyncio\nfrom autumn_sdk import Autumn\n\nautumn = Autumn(\'am_sk_42424242\')\n\nasync def main():\n # Check feature access\n response = await autumn.check(\n customer_id=\'user_or_org_id_from_auth\',\n feature_id=\'messages\',\n required_balance=1,\n )\n\nasyncio.run(main())\n```\n\n```bash cURL\n# Check feature access\ncurl -X POST \'https://api.useautumn.com/v1/check\' \\\n -H \'Authorization: Bearer am_sk_42424242\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "messages",\n "required_balance": 1\n }\'\n```\n\n</CodeGroup>\n\nYou can also use `check` to [gate boolean features](/documentation/customers/check#checking-boolean-features) (non-metered features), such as access to "premium AI models".\n\n### Tracking usage\n\nAfter the user has successfully used a chatbot message, you can record the usage in Autumn. This will decrement the user\'s message balance.\n\n<CodeGroup>\n\n```typescript TypeScript\n// Your own function to send the chat message\n\n// Then record 1 message used\nawait autumn.track({\n customerId: "user_or_org_id_from_auth",\n featureId: "messages",\n value: 1,\n});\n```\n\n```python Python\nimport asyncio\nfrom autumn_sdk import Autumn\n\nautumn = Autumn(\'am_sk_42424242\')\n\n# Your own function to send the chat message\n\n# Then record 1 message used\nasync def main():\n await autumn.track(\n customer_id=\'user_or_org_id_from_auth\',\n feature_id=\'messages\',\n value=1,\n )\n\nasyncio.run(main())\n```\n\n```bash cURL\n# Your own function to send the chat message\n\n# Then record 1 message used\ncurl -X POST \'https://api.useautumn.com/v1/track\' \\\n -H \'Authorization: Bearer am_sk_42424242\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_or_org_id_from_auth",\n "feature_id": "messages",\n "value": 1\n }\'\n```\n\n</CodeGroup>\n\nOnce you send usage events, you can verify their receipt in the Autumn dashboard, on the [customer](https://app.useautumn.com/customers) detail page.\n\nYou should always handle access checks and usage tracking server-side for security. Users can manipulate client-side code using devtools.\n\n--- \n\n**Next: Build your billing page**\n\nNow, whenever your customers change their plan, they will automatically have the correct access and limits. Next, build a billing page for your customers.\n\nDisplay plan, balance and usage information to your customers using Autumn\'s `customer` state',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
path: "references/check.md",
|
|
215
|
+
contents:
|
|
216
|
+
'## Checking access\n\nWhen you create a plan in Autumn, you define what features your customers on that plan get access to.\n\nThe `check` method returns the `allowed` field in real-time to check if a customer should have access to a feature. You can use this to block access and prompt an upsell.\n\n## The `allowed` field\n\nThe `allowed` field will return `true` for a given feature if:\n- The customer has an active plan with this feature\n- The customer has an active plan with a `credit_system` that grants this feature\n- The plan feature is <Badge>included</Badge> or <Badge>prepaid</Badge>, and the current balance is greater than the `required_balance` parameter\n- The plan feature is <Badge>usage-based</Badge>, and the user has not exceeded their max spend limit\n- The plan feature is <Badge>unlimited</Badge> or a <Badge>boolean</Badge> feature\n\nUnder these conditions, you should allow your customer to use the feature. You can then [record the usage event](/documentation/customers/tracking-usage/) so Autumn can update the `allowed` field as necessary.\n\nThe customer must already exist before calling `check`. If the `customer_id` doesn\'t match an existing customer, the API returns a `customer_not_found` error. Create customers using [`customers.getOrCreate`](/documentation/customers/creating-customers) during signup or login.\n\n## Checking metered features\n\nBefore your customer uses a feature, you can check if the customer is allowed to use it and their current usage.\n\n**Example**\n\nLet\'s imagine you have a free plan for a chatbot that allows 5 messages per month. Before your customer sends an AI message, you can check if they have any left.\n\nIf they still have messages remaining, they\'ll be allowed to send an AI message.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst response = await autumn.check({\n customerId: "user_123",\n featureId: "messages",\n});\n\nconsole.log(response.allowed);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.check(\n customer_id="user_123",\n feature_id="messages",\n)\nprint(response.allowed)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "messages"\n }\'\n```\n\n</CodeGroup>\n\n```json\n{\n "allowed": true,\n "customerId": "user_123",\n "requiredBalance": 1,\n "balance": {\n "featureId": "messages",\n "granted": 5,\n "remaining": 5,\n "usage": 0,\n "unlimited": false,\n "overageAllowed": false,\n "nextResetAt": 1767610960519\n }\n}\n```\n\nEven if your product doesn\'t have usage limits (ie your feature is purely usage-based), you can still use the above method to prevent usage if a customer\'s payment fails.\n\n## Checking for a required balance\n\nIf you know the balance a user will consume in advance, you can specify it with the `requiredBalance` parameter. This means you can prevent a user from starting a process that would consume more than their current balance.\n\nBy default, `requiredBalance` is 1, so not passing this parameter will return `allowed: true` as long as the customer has a feature balance of 1 or more.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst response = await autumn.check({\n customerId: "user_123",\n featureId: "messages",\n requiredBalance: 3,\n});\n\nconsole.log(response.allowed);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.check(\n customer_id="user_123",\n feature_id="messages",\n required_balance=3,\n)\nprint(response.allowed)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "messages",\n "required_balance": 3\n }\'\n```\n\n</CodeGroup>\n\n## Checking and reserving usage\n\nWhen you don\'t know the final cost upfront — like AI completions or long-running jobs — reserve balance with a `lock` on the check call, then finalize it once the operation completes.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst response = await autumn.check({\n customerId: "user_123",\n featureId: "api_calls",\n requiredBalance: 3,\n sendEvent: true,\n lock: {\n enabled: true,\n lockId: "request_abc123",\n expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes\n },\n});\n\nconsole.log(response.allowed);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.check(\n customer_id="user_123",\n feature_id="api_calls",\n required_balance=3,\n send_event=True,\n lock={\n "enabled": True,\n "lock_id": "request_abc123",\n "expires_at": int(time.time() * 1000) + 5 * 60 * 1000,\n },\n)\nprint(response.allowed)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "api_calls",\n "required_balance": 3,\n "send_event": true,\n "lock": {\n "enabled": true,\n "lock_id": "request_abc123",\n "expires_at": 1735689600000\n }\n }\'\n```\n\n</CodeGroup>\n\nOnce the operation completes, finalize the lock — `confirm` it to keep the deduction, or `release` it if the operation failed.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.balances.finalize({\n lockId: "request_abc123",\n action: "confirm",\n});\n```\n\n```python Python\nawait autumn.balances.finalize(\n lock_id="request_abc123",\n action="confirm",\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/balances.finalize" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "lock_id": "request_abc123",\n "action": "confirm"\n }\'\n```\n\n</CodeGroup>\n\nSee [Balance Locking](/documentation/customers/balance-locking) for the full guide, including releasing a lock and adjusting the final amount when actual usage differs from what you reserved.\n\n## Checking boolean features\n\nFor simple on/off features, use the check method to determine if a customer has access:\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst response = await autumn.check({\n customerId: "user_123",\n featureId: "premium-dashboard",\n});\n\nif (response.allowed) {\n // Show premium dashboard\n}\n```\n\n### Feature flags in customer responses\n\nBoolean features are also returned as a `flags` object on customer and entity API responses. This lets you check on/off feature access directly from the customer object without calling the `check` endpoint separately.\n\n```json\n{\n "balances": {\n "credits": { "featureId": "credits", "granted": 1000, "remaining": 800, "usage": 200 }\n },\n "flags": {\n "premiumDashboard": {\n "id": "cus_ent_abc123",\n "planId": "pro_plan",\n "expiresAt": null,\n "featureId": "premiumDashboard"\n }\n }\n}\n```\n\n- **Flags** are separate from **balances** — boolean features appear under `flags`, while metered and credit system features remain under `balances`\n- Each flag shows which `planId` it originates from and when it expires\n- Use `expand: ["flags.feature"]` to include the full feature object on each flag\n\n<CodeGroup>\n\n```typescript TypeScript\nconst customer = await autumn.customers.getOrCreate({\n customerId: "user_123",\n expand: ["flags.feature"],\n});\n\nif (customer.flags?.["premiumDashboard"]) {\n // Customer has the premium dashboard feature\n}\n```\n\n```python Python\ncustomer = await autumn.customers.get_or_create(\n customer_id="user_123",\n expand=["flags.feature"],\n)\n\nif customer.flags and "premium_dashboard" in customer.flags:\n # Customer has the premium dashboard feature\n pass\n```\n\n</CodeGroup>',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
path: "references/tracking-usage.md",
|
|
220
|
+
contents:
|
|
221
|
+
'## Tracking usage\n\nWhen customers use features in your product, you\'ll need to record their usage so Autumn can track it against their limits or bill them for usage.\n\nThere are two ways to record usage: sending events or setting usage directly.\n\nThe customer must already exist before calling `track`. If the `customer_id` doesn\'t match an existing customer, the API returns a `customer_not_found` error. Create customers using [`customers.getOrCreate`](/documentation/customers/creating-customers) during signup or login.\n\n## Sending Events\n\nThe track route is recommended for tracking consumable features, like AI messages, credits or API calls. Each time a customer uses a feature, send an event to count their usage.\n\n Before recording usage, you may want to check if the customer is [allowed to\n use the feature](/documentation/customers/check). This prevents them from exceeding usage\n limits defined in the product.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.track({\n customerId: "user_123",\n featureId: "ai-messages",\n value: 1,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.track(\n customer_id="user_123",\n feature_id="ai-messages",\n value=1,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "ai-messages",\n "value": 1\n }\'\n```\n\n</CodeGroup>\n\n```json\n{\n "customerId": "user_123",\n "value": 1,\n "balance": {\n "featureId": "ai-messages",\n "granted": 100,\n "remaining": 99,\n "usage": 1,\n "unlimited": false,\n "overageAllowed": false,\n "nextResetAt": 1767610960519\n }\n}\n```\n\n You can also send a negative `value` to increase the balance counter, which is\n useful for increasing a feature limit (eg, if a customer removes a seat).\n\n## Setting Usage Directly\n\nFor non-consumable features (such as seats or workspaces), you may prefer to set usage directly, rather than incrementing Autumn\'s feature balance. This enables you to sync a source of truth up on your side with Autumn, preventing any discrepancies.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.balances.update({\n customerId: "user_123",\n featureId: "seats",\n usage: 3,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.balances.update(\n customer_id="user_123",\n feature_id="seats",\n usage=3,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/balances/update" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "seats",\n "usage": 3\n }\'\n```\n\n</CodeGroup>\n\n The usage route overwrites the current usage value. Use this carefully, as it\n can reset or override incremental usage recorded through events.\n\n## Tracking AI Token Usage\n\nIf you\'re using an [AI credit system](/examples/monetary-credits), you can track token usage directly with `trackTokens`. This automatically converts token counts to a dollar cost using [Models.dev](https://models.dev) pricing and your configured markup, then deducts from the customer\'s credit balance.\n\nThe `modelId` must be in `provider/model` format, matching the provider and model keys from [Models.dev](https://models.dev). For example:\n- `anthropic/claude-sonnet-4-5-20250514`\n- `openai/gpt-4o`\n- `google/gemini-2.5-pro`\n\nFor providers with nested model paths (like OpenRouter), include the full path after the provider: `openrouter/anthropic/claude-opus-4.6`.\n\nToken counts are **exclusive pools**: `inputTokens` should exclude cached tokens (pass those as `cacheReadTokens` / `cacheWriteTokens`) and `outputTokens` should exclude reasoning tokens (pass those as `reasoningTokens`). Audio tokens go in `audioInputTokens` / `audioOutputTokens`. See the [API reference](/api-reference/balances/trackTokens) for the full parameter list.\n\n `autumn.balances.trackTokens` requires an autumn-js release that includes\n the method. On older versions, call the REST endpoint directly — see the\n cURL tab below.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.balances.trackTokens({\n customerId: "user_123",\n modelId: "anthropic/claude-opus-4-6",\n inputTokens: 1000,\n outputTokens: 500,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.balances.track_tokens(\n customer_id="user_123",\n model_id="anthropic/claude-opus-4-6",\n input_tokens=1000,\n output_tokens=500,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/balances.track_tokens" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "model_id": "anthropic/claude-opus-4-6",\n "input_tokens": 1000,\n "output_tokens": 500\n }\'\n```\n\n</CodeGroup>\n\n If the customer has exactly one AI credit system feature, you can omit the\n `featureId` parameter — it will be auto-detected. The request fails with an\n error if the customer has no AI credit system, or has more than one and no\n `featureId` is provided.\n\n### Vercel AI SDK integration\n\nIf you\'re using the [Vercel AI SDK](https://sdk.vercel.ai), the `@useautumn/gateway` package can automatically track token usage for every `generateText` or `streamText` call — no manual `trackTokens` calls needed.\n\n## Using Event Names\n\nIn the above examples, we used the `featureId` to identify the feature. You can instead use the `eventName` parameter to link balances to different events in your application. This can be useful when:\n\n- Multiple balances are affected by the same user action\n- Different user actions should be tracked against the same balance\n\n**Example Use Case:**\n\nYour AI chatbot has a daily limit of 10 messages, with a maximum monthly limit of 100 messages. Recording a message in Autumn should decrease both balances simultaneously.\n\nIn Autumn, you can create two features: `daily-messages` and `monthly-messages`. You can add the same event name to both features: `message-sent`. Then, assign both features to a plan with the correct included amounts.\n\nEvery time that event name is recorded, both the daily-messages and monthly-messages balances will be decremented.\n\nIf you were to use the same feature for both in this case, the daily and monthly balances would sum, allowing the user to send 10 messages a day, with an "overage" balance of 100 messages per month if they go over the daily limit.\n\nYou can define event names in the Autumn dashboard:\n\n1. Go to the [Features Page](https://app.useautumn.com/sandbox/products?tab=features).\n2. Click on the feature you want to add an event to.\n3. In the sheet, under the "advanced" section, add your event names.\n4. Save the feature.\n\nYou can then record usage for these events from your application:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nawait autumn.track({\n customerId: "user_123",\n eventName: "blog_post_generation",\n value: 1,\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nawait autumn.track(\n customer_id="user_123",\n event_name="blog_post_generation",\n value=1,\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/track" \\\n -H "Authorization: Bearer am_sk_test_1234" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "event_name": "blog_post_generation",\n "value": 1\n }\'\n```\n\n</CodeGroup>\n\n You can only use one of `eventName` or `featureId` when recording usage.\n Check that you\'re sending the correct one in the request, especially if you\'re\n using a mix of `snake_case` and `kebab-case`.',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
path: "references/billing-controls.md",
|
|
225
|
+
contents:
|
|
226
|
+
'## Billing Controls\n\nBilling controls let you manage how individual customers (or [entities](/documentation/customers/feature-entities)) consume usage-based features. You can toggle whether overage is allowed, cap how much overage accumulates, hard-cap how much a feature can be used per time window, get notified when usage crosses a threshold, and automatically replenish prepaid balances — all configured per-customer via the API or viewed in the dashboard.\n\nAll billing controls are set through the `billingControls` field when [updating a customer](/api-reference/customers/updateCustomer) or [updating an entity](/api-reference/entities/updateEntity).\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n overageAllowed: [{ featureId: "api_calls", enabled: true }],\n spendLimits: [{ featureId: "api_calls", enabled: true, overageLimit: 5000 }],\n usageLimits: [{ featureId: "api_calls", limit: 50, interval: "day" }],\n usageAlerts: [{ featureId: "api_calls", threshold: 80, thresholdType: "usage_percentage", enabled: true }],\n autoTopups: [{ featureId: "credits", enabled: true, threshold: 500, quantity: 1000 }],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "overage_allowed": [{"feature_id": "api_calls", "enabled": True}],\n "spend_limits": [{"feature_id": "api_calls", "enabled": True, "overage_limit": 5000}],\n "usage_limits": [{"feature_id": "api_calls", "limit": 50, "interval": "day"}],\n "usage_alerts": [{"feature_id": "api_calls", "threshold": 80, "threshold_type": "usage_percentage", "enabled": True}],\n "auto_topups": [{"feature_id": "credits", "enabled": True, "threshold": 500, "quantity": 1000}],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "overage_allowed": [{"feature_id": "api_calls", "enabled": true}],\n "spend_limits": [{"feature_id": "api_calls", "enabled": true, "overage_limit": 5000}],\n "usage_limits": [{"feature_id": "api_calls", "limit": 50, "interval": "day"}],\n "usage_alerts": [{"feature_id": "api_calls", "threshold": 80, "threshold_type": "usage_percentage", "enabled": true}],\n "auto_topups": [{"feature_id": "credits", "enabled": true, "threshold": 500, "quantity": 1000}]\n }\n }\'\n```\n\n</CodeGroup>\n\n## Overage Allowed\n\nBy default, whether a customer can use a feature beyond their included balance depends on the plan\'s pricing model. Features with [usage-based pricing](/documentation/modelling-pricing/usage-based-pricing) (pay-per-use) automatically allow overage — the customer keeps using and gets billed for the extra. Features without usage-based pricing (like a flat included allowance) block usage once the balance hits zero.\n\nThe `overageAllowed` control lets you override this default per customer or entity.\n\n#### Default behavior (no override)\n\n| Plan item pricing | Overage? |\n|-------------------|----------|\n| Usage-based (pay-per-use) | Allowed — customer is billed for overage |\n| Included / prepaid (no overage price) | Blocked — `check` returns `allowed: false` at zero balance |\n\n#### With `overageAllowed` enabled\n\nSetting `overageAllowed` to `true` on a feature lets a customer consume beyond their included balance **even when the plan doesn\'t have usage-based pricing** for that feature. The balance goes negative, meaning you can track how much overage occurred, though no automatic overage charge is created.\n\n> **Example** <br />\n> A customer is on a free plan with 100 API calls included (no overage pricing). Normally they\'d be blocked at 0 remaining. You set `overageAllowed: true` for `api_calls`. Now they can keep using beyond 100, and you can decide how to handle the overage in your application (prompt an upgrade, bill manually, etc.).\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n overageAllowed: [{\n featureId: "api_calls",\n enabled: true,\n }],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "overage_allowed": [{\n "feature_id": "api_calls",\n "enabled": True,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "overage_allowed": [{\n "feature_id": "api_calls",\n "enabled": true\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string | The feature to override overage behavior for |\n| `enabled` | boolean | `true` to allow overage, `false` to block it |\n\n#### Disabling overage on a pay-per-use feature\n\nYou can also use `overageAllowed` to **block** overage on a feature that would normally allow it. Setting `enabled: false` forces a hard cap at the included balance — even if the plan has usage-based pricing for that feature.\n\n> **Example** <br />\n> A customer\'s plan includes 1,000 API calls with pay-per-use overage at $1/1,000 calls. You set `overageAllowed: false` for `api_calls`. The customer is now blocked at 1,000 total calls — no overage charges will occur.\n\nSetting `overageAllowed: false` is a hard override. It takes precedence over usage-based pricing on the plan. The customer will be blocked at zero remaining balance regardless of whether overage pricing exists.\n\n#### How it interacts with spend limits\n\n`overageAllowed` and [spend limits](#spend-limits) are complementary:\n\n- **`overageAllowed`** answers: *can* usage go beyond the included balance?\n- **Spend limits** answer: *how far* can overage go?\n\nIf both are set, `overageAllowed` is checked first. If overage is blocked (`enabled: false`), the spend limit is irrelevant. If overage is allowed, the spend limit caps how much overage can accumulate.\n\n## Spend Limits\n\nSpend limits cap how much overage a customer can accumulate on a usage-based feature. Once the cap is reached, `check` returns `allowed: false` and `track` stops deducting.\n\n> **Example** <br />\n> A customer\'s plan includes 1,000 API calls with $1 per 1,000 overage calls. You set a spend limit of 5,000 on `api_calls`. The customer can use up to 6,000 total calls (1,000 included + 5,000 overage), then they\'re blocked.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n spendLimits: [{\n featureId: "api_calls",\n enabled: true,\n overageLimit: 5000,\n }],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": True,\n "overage_limit": 5000,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": true,\n "overage_limit": 5000\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string | The feature to cap |\n| `enabled` | boolean | Whether the spend limit is active |\n| `overage_limit` | number | Maximum overage units beyond the included amount |\n\nThe `overage_limit` is measured in feature units, not dollars. An `overage_limit` of 5,000 on "API calls" means 5,000 additional API calls beyond the included allowance.\n\nWhen both a spend limit and a plan-level max purchase exist for the same feature, the **spend limit takes precedence**. This lets you use max purchase as a default for all customers, then selectively raise or lower the cap per-customer.\n\nFor a deeper dive, see [Spend Limits & Usage Alerts](/documentation/modelling-pricing/spend-limits).\n\n## Usage Limits\n\nA usage limit is a **windowed hard cap**: at most `limit` units of a feature per `interval` window (day, week, month, or year), regardless of how much balance the customer has left or how the plan is priced. It\'s a throttle, that you or your customer may set.\n\nOnce usage reaches the cap inside the active window, `check` returns `allowed: false` and `track` stops deducting.\n\nUsage limits cap *total usage* of a feature within a period: a sub-limit within an existing balance. They sit on top of the plan\'s allowance and apply even when there\'s balance remaining and even when the feature has no overage pricing.\n\n`set_usage` sets a feature\'s balance but never touches usage-window counters. A capped feature stays capped: after `set_usage`, a customer whose window is exhausted still gets `allowed: false` until the window resets. To count usage against a window (including limits with conditions, which match on event properties), record usage normally with `track`.\n\nThe consumed counter belongs to the window, not to the limit\'s configuration — editing the limit never resets what\'s already been counted. To unblock a capped customer mid-window: **disable** the limit (`enabled: false`) to stop it gating while keeping its configuration, **raise** the limit (headroom becomes the new limit minus usage already counted), or **delete** it.\n\n> **Example** <br />\n> A customer\'s plan includes **300 credits per month**, but you want to stop any single day from burning through them. Set a usage limit of **50 on `credits` with a `day` interval**. The customer still gets their 300 monthly credits, but can never spend more than 50 in a day.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n usageLimits: [{\n featureId: "credits",\n limit: 50,\n interval: "day",\n }],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "usage_limits": [{\n "feature_id": "credits",\n "limit": 50,\n "interval": "day",\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "usage_limits": [{\n "feature_id": "credits",\n "limit": 50,\n "interval": "day"\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string | The feature to cap |\n| `limit` | number | Maximum units allowed per window |\n| `interval` | string | Window length: `"day"`, `"week"`, `"month"`, or `"year"`. Cannot be `one_off`. |\n\nEach customer/entity feature in a `get` response carries a `usage_limits` array where every entry also reports the `usage` consumed in the current window, so you can show "12 / 50 used today" without a separate call.\n\n### Window reset and plan changes\n\nEach window aligns to the **customer\'s billing cycle**, not the UTC calendar. A `day` cap rolls at the customer\'s billing time-of-day; a `month` cap rolls on their billing-cycle anchor. When there\'s no billing cycle to anchor to (e.g. a feature with no backing plan), the window falls back to UTC calendar alignment — daily windows roll at UTC midnight, monthly on the 1st.\n\nBecause the window is tied to the feature\'s reset cycle, **a plan change that restarts that cycle also restarts the window.** If a customer upgrades mid-month and their billing anchor moves, the usage limit\'s window re-anchors to the new cycle and the consumed counter starts fresh.\n\n### Caps on credit systems\n\nWhen a feature is part of a [credit system](/documentation/modelling-pricing/credit-systems), you can cap usage at either level:\n\n- **Cap the credit balance** — e.g. limit total `credits` spend per window across every feature that draws from it.\n- **Cap an individual feature** — e.g. give a credit system shared by features A, B, and C, but limit how many units of B specifically can be used per window. The per-feature cap is converted into credits using B\'s credit cost, so both caps are enforced together.\n\n> **Example — per-feature cap inside a credit system** <br />\n> Your `credits` system is spent by `images`, `transcriptions`, and `exports`. Customers can spend credits freely across all three, but you cap `exports` at **10 per day** so one feature can\'t drain the whole balance. A check or track on `exports` is blocked at 10/day even if plenty of credits remain.\n\nWhen more than one cap applies to a check (the cap on the evaluated feature and a cap on its parent credit system), Autumn enforces the **tightest** one — the remaining headroom is the minimum across all armed caps.\n\n## Usage Alerts\n\nUsage alerts fire a webhook when a customer\'s usage crosses a threshold. They don\'t block usage — they notify, so you can take action like sending a warning email or prompting an upgrade.\n\nThere are two threshold types:\n\n- **`usage`** — fires when absolute usage reaches a specific count\n- **`usage_percentage`** — fires when usage reaches a percentage of the included allowance\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n usageAlerts: [\n {\n featureId: "api_calls",\n threshold: 80,\n thresholdType: "usage_percentage",\n enabled: true,\n name: "80% usage warning",\n },\n {\n featureId: "api_calls",\n threshold: 900,\n thresholdType: "usage",\n enabled: true,\n name: "Approaching limit",\n },\n ],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "usage_alerts": [\n {\n "feature_id": "api_calls",\n "threshold": 80,\n "threshold_type": "usage_percentage",\n "enabled": True,\n "name": "80% usage warning",\n },\n {\n "feature_id": "api_calls",\n "threshold": 900,\n "threshold_type": "usage",\n "enabled": True,\n "name": "Approaching limit",\n },\n ],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "usage_alerts": [\n {\n "feature_id": "api_calls",\n "threshold": 80,\n "threshold_type": "usage_percentage",\n "enabled": true,\n "name": "80% usage warning"\n },\n {\n "feature_id": "api_calls",\n "threshold": 900,\n "threshold_type": "usage",\n "enabled": true,\n "name": "Approaching limit"\n }\n ]\n }\n }\'\n```\n\n</CodeGroup>\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string | The feature to monitor |\n| `threshold` | number | Trigger value — absolute count or percentage (0–100) |\n| `threshold_type` | string | `"usage"` for absolute count, `"usage_percentage"` for percentage of included allowance |\n| `enabled` | boolean | Whether the alert is active (defaults to `true`) |\n| `name` | string (optional) | A label to distinguish multiple alerts |\n\nEach alert fires **once** per threshold crossing. It won\'t re-fire unless usage drops below the threshold and crosses it again.\n\nWhen triggered, Autumn sends a `balances.usage_alert_triggered` [webhook](/documentation/webhooks). See the [webhook schema](/api-reference/webhooks/balancesUsageAlertTriggered) for the full payload.\n\nFor more details and examples, see [Spend Limits & Usage Alerts](/documentation/modelling-pricing/spend-limits#usage-alerts).\n\n## Auto Top-Ups\n\nAuto top-ups automatically replenish a customer\'s prepaid balance when it drops below a configured threshold. This prevents service interruptions for customers who don\'t want to manually manage their credits.\n\n> **Example** <br />\n> A customer gets 5,000 credits per month. When their balance drops below 500, Autumn automatically purchases 1,000 more credits using the plan\'s one-off prepaid price.\n\nAuto top-ups require a plan with a [one-off prepaid](/documentation/modelling-pricing/one-off-purchases) item for the feature, and the customer must have a payment method on file.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n autoTopups: [{\n featureId: "credits",\n enabled: true,\n threshold: 500,\n quantity: 1000,\n }],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "auto_topups": [{\n "feature_id": "credits",\n "enabled": True,\n "threshold": 500,\n "quantity": 1000,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "auto_topups": [{\n "feature_id": "credits",\n "enabled": true,\n "threshold": 500,\n "quantity": 1000\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string | The feature (credit balance) to monitor |\n| `enabled` | boolean | Whether auto top-up is active |\n| `threshold` | number | Balance level that triggers a top-up |\n| `quantity` | number | Units to purchase each time |\n| `purchase_limit` | object (optional) | Rate limit on how often top-ups can occur |\n\nTo prevent runaway spending, you can set a purchase limit:\n\n```json\n{\n "purchase_limit": {\n "interval": "month",\n "interval_count": 1,\n "limit": 5\n }\n}\n```\n\nThis limits the customer to 5 auto top-ups per month. Supported intervals: `hour`, `day`, `week`, `month`.\n\nFor setup instructions and how it works end-to-end, see [Auto Top-Ups](/documentation/modelling-pricing/auto-top-ups).\n\n## Customer vs Entity Controls\n\nBilling controls can be set at two levels:\n\n| Control | Customer-level | Entity-level |\n|---------|---------------|--------------|\n| **Overage allowed** | Yes | Yes |\n| **Spend limits** | Yes | Yes |\n| **Usage limits** | Yes | Yes |\n| **Usage alerts** | Yes | Yes |\n| **Auto top-ups** | Yes | No |\n\n**Entity-level** controls are configured by updating the entity instead of the customer. Entity-level controls override customer-level controls for that entity — for example, an entity overage override takes precedence over the customer-level setting, and entity spend limits override the customer-level limit.\n\nOverrides are resolved **per feature**: an entity\'s own entry for a feature wins, and the customer\'s entries fill in any features the entity doesn\'t set. For usage limits specifically, an inherited (customer-level) cap counts usage against the **shared customer window** — it\'s the same aggregate cap, not a separate per-entity copy. An entity-level usage limit, by contrast, gets its own per-entity window and counter.\n\n> **Example — per-entity cap** <br />\n> An org (the customer) has 1,000 monthly API calls shared across its workspaces (entities). You set a customer-level usage limit of 1,000/month so the org can\'t exceed its plan, and an entity-level limit of 200/day on a noisy workspace so it can\'t starve the others. The workspace is blocked at 200/day; the org is blocked at 1,000/month.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.entities.update({\n customerId: "org_123",\n entityId: "workspace_a",\n billingControls: {\n overageAllowed: [{\n featureId: "api_calls",\n enabled: true,\n }],\n spendLimits: [{\n featureId: "api_calls",\n enabled: true,\n overageLimit: 2000,\n }],\n usageLimits: [{\n featureId: "api_calls",\n limit: 200,\n interval: "day",\n }],\n usageAlerts: [{\n featureId: "api_calls",\n threshold: 90,\n thresholdType: "usage_percentage",\n enabled: true,\n }],\n },\n});\n```\n\n```python Python\nawait autumn.entities.update(\n customer_id="org_123",\n entity_id="workspace_a",\n billing_controls={\n "overage_allowed": [{\n "feature_id": "api_calls",\n "enabled": True,\n }],\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": True,\n "overage_limit": 2000,\n }],\n "usage_limits": [{\n "feature_id": "api_calls",\n "limit": 200,\n "interval": "day",\n }],\n "usage_alerts": [{\n "feature_id": "api_calls",\n "threshold": 90,\n "threshold_type": "usage_percentage",\n "enabled": True,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/entities/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "org_123",\n "entity_id": "workspace_a",\n "billing_controls": {\n "overage_allowed": [{\n "feature_id": "api_calls",\n "enabled": true\n }],\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": true,\n "overage_limit": 2000\n }],\n "usage_limits": [{\n "feature_id": "api_calls",\n "limit": 200,\n "interval": "day"\n }],\n "usage_alerts": [{\n "feature_id": "api_calls",\n "threshold": 90,\n "threshold_type": "usage_percentage",\n "enabled": true\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\nAuto top-ups are customer-level only because they create invoices and charge a payment method, which is tied to the customer account — not individual entities.\n\n## Plan-Level Defaults\n\nEvery billing control can also be defined on a **plan** (in the dashboard under plan settings → billing controls, or via the `billingControls` field when creating or updating a plan). Plan-level controls act as defaults for every customer on that plan — set a daily usage limit once on your free tier instead of on each customer.\n\nResolution when a customer is on one or more plans:\n\n- A **customer-level entry shadows the plan\'s entry** for the same feature (for usage limits, the same feature *and* conditions). Setting a control on the customer is how you override the plan default — and a disabled customer entry still shadows the plan\'s, it doesn\'t resurface it.\n- With **multiple attached plans** defining the same control, the most restrictive wins (auto top-ups: the most recently attached plan\'s config).\n\n### Reading effective controls\n\nFrom API version `2.3.0`, [fetching a customer](/api-reference/customers/getCustomer) returns the **effective** controls: plan defaults are merged into `billing_controls`, and every entry carries a `source` field so you can tell overrides from inherited defaults. Inherited usage limits include the live window `usage`, so you can render "X of 200 used today" without extra calls.\n\n```json\n"billing_controls": {\n "usage_limits": [\n {\n "feature_id": "emails",\n "enabled": true,\n "limit": 200,\n "interval": "day",\n "usage": 37,\n "source": "plan"\n }\n ]\n}\n```\n\nOn earlier API versions, `billing_controls` contains only customer-level entries. Entity responses always show the entity\'s own controls.\n\n## Related Webhooks\n\nBilling controls tie into three webhook events that fire automatically based on usage:\n\n| Event | When it fires |\n|-------|---------------|\n| [`balances.limit_reached`](/api-reference/webhooks/balancesLimitReached) | Customer transitions from allowed to not allowed on a feature (included allowance exhausted, max purchase hit, spend limit reached, or usage limit hit). The payload\'s `limit_type` distinguishes `included`, `max_purchase`, `spend_limit`, and `usage_limit`. |\n| [`balances.usage_alert_triggered`](/api-reference/webhooks/balancesUsageAlertTriggered) | Customer\'s usage crosses a configured alert threshold |\n| [`customer.products.updated`](/documentation/webhooks#customerproductsupdated) | Customer\'s subscription changes (new, upgrade, downgrade, cancel, etc.) |\n\nFor webhook setup and security details, see [Webhooks](/documentation/webhooks).',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
path: "references/spend-limits.md",
|
|
230
|
+
contents:
|
|
231
|
+
'## Spend Limits & Usage Alerts\n\nSpend limits let you cap how much overage a customer (or entity) can accumulate on a usage-based feature. Without a spend limit, usage-based features allow unlimited overage — the customer is billed for whatever they use. With a spend limit, Autumn blocks usage once the overage reaches the configured cap.\n\n> **Example** <br />\n> A customer is on a plan with 1,000 API calls included per month and $1 per 1,000 additional calls. You set a spend limit of 5,000 on the `api_calls` feature. The customer can use up to 6,000 total API calls (1,000 included + 5,000 overage), and is blocked after that.\n\n## Prerequisites\n\nSpend limits apply to **usage-based** features — features with overage pricing that allow usage beyond the included amount. If a feature doesn\'t allow overage, spend limits have no effect.\n\nYou\'ll need a plan with a usage-based price on the feature you want to cap:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const apiCalls = feature({\n featureId: "api_calls",\n name: "API Calls",\n type: "metered",\n consumable: true,\n});\n\nexport const pro = plan({\n planId: "pro",\n versionSlug: "v1",\n active: true,\n name: "Pro",\n price: { amount: 20, interval: "month" },\n items: [\n {\n featureId: apiCalls.featureId,\n included: 1000,\n price: {\n amount: 1,\n interval: "month",\n billingUnits: 1000,\n billingMethod: "usage_based",\n },\n },\n ],\n});\n\nexport default atmn({ features: [apiCalls], plans: [pro] });\n```\n\nPreview with `atmn push`, then apply with `atmn push --yes`. Then configure spend limits per customer via the API (see below).\n\n## Configuring spend limits\n\nSpend limits are set per-customer (or per-entity) via the API, not at the plan level. Update a customer\'s `billingControls` to add spend limits:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n spendLimits: [{\n featureId: "api_calls",\n enabled: true,\n overageLimit: 5000,\n }],\n },\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": True,\n "overage_limit": 5000,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": true,\n "overage_limit": 5000\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\n## Spend limit fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string | The feature to apply the spend limit to |\n| `enabled` | boolean | Whether the spend limit is active |\n| `overage_limit` | number (optional) | Maximum overage units allowed beyond the included amount |\n| `skip_overage_billing` | boolean (optional) | When `true`, overage on this feature is never billed — usage beyond the included amount is not added to the customer\'s invoice. Usage tracking and balance resets are unaffected. |\n\nThe `overage_limit` is measured in the same units as the feature\'s balance — not in dollars. For example, if your feature is "API calls", an `overage_limit` of 5,000 means 5,000 additional API calls beyond the included amount.\n\n## Skipping overage billing\n\nSet `skip_overage_billing` to `true` on a spend limit to let a customer use overage without being charged for it. Usage tracking, `check` responses, and end-of-cycle balance resets all behave as normal — the overage line items are simply never added to the customer\'s invoice.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n spendLimits: [{\n featureId: "api_calls",\n enabled: true,\n skipOverageBilling: true,\n }],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": True,\n "skip_overage_billing": True,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": true,\n "skip_overage_billing": true\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\nThe spend limit must be `enabled` and have a `feature_id`. It can also be set on an [entity](/documentation/customers/feature-entities) — resolution is per-feature, and the nearest configuration wins: entity-level spend limit, then customer-level, then plan-level billing controls.\n\nCombine `skip_overage_billing` with an `overage_limit` to allow a bounded amount of free overage: the customer is blocked once they hit the cap, and the overage they did use is never billed.\n\n## How it works\n\n1. The customer uses a usage-based feature and begins accumulating overage beyond their included amount\n2. On each `check` or `track` call, Autumn computes total overage across all of the customer\'s usage-based entitlements for that feature\n3. If the total overage would exceed the `overage_limit`, Autumn blocks the usage — `check` returns `allowed: false`, and `track` will not deduct beyond the limit\n\nSpend limits aggregate overage across **all** of the customer\'s entitlements for a given feature. If a customer has the same feature on multiple plans (e.g., a base plan and an add-on), the total overage across both is compared against the spend limit.\n\n## Checking access with spend limits\n\nWhen a spend limit is configured, the `check` endpoint accounts for it in the `allowed` response:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst { data } = await autumn.check({\n customerId: "user_123",\n featureId: "api_calls",\n});\n\nif (!data.allowed) {\n // Customer has hit their spend limit\n}\n```\n\n```python Python\nresponse = await autumn.check(\n customer_id="user_123",\n feature_id="api_calls",\n)\n\nif not response.allowed:\n # Customer has hit their spend limit\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/check" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "api_calls"\n }\'\n```\n\n</CodeGroup>\n\n```json\n{\n "allowed": true,\n "customerId": "user_123",\n "requiredBalance": 1,\n "balance": {\n "featureId": "api_calls",\n "granted": 1000,\n "remaining": -3000,\n "usage": 4000,\n "unlimited": false,\n "overageAllowed": true,\n "nextResetAt": 1757192635393\n }\n}\n```\nThe customer has used 4,000 API calls (3,000 overage) against a spend limit of 5,000. They still have 2,000 overage units remaining, so `allowed` is `true`.\n\n```json\n{\n "allowed": false,\n "customerId": "user_123",\n "requiredBalance": 1,\n "balance": {\n "featureId": "api_calls",\n "granted": 1000,\n "remaining": -5000,\n "usage": 6000,\n "unlimited": false,\n "overageAllowed": true,\n "nextResetAt": 1757192635393\n }\n}\n```\nThe customer has reached their 5,000 overage limit (6,000 total usage). `allowed` is `false` even though the feature allows overage.\n\n## Entity-level spend limits\n\nYou can also set spend limits on individual [entities](/documentation/customers/feature-entities) (users, workspaces, etc.) under a customer. Entity-level spend limits override customer-level limits for that entity.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.entities.update({\n customerId: "user_123",\n entityId: "workspace_a",\n billingControls: {\n spendLimits: [{\n featureId: "api_calls",\n enabled: true,\n overageLimit: 2000,\n }],\n },\n});\n```\n\n```python Python\nawait autumn.entities.update(\n customer_id="user_123",\n entity_id="workspace_a",\n billing_controls={\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": True,\n "overage_limit": 2000,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/entities/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "entity_id": "workspace_a",\n "billing_controls": {\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": true,\n "overage_limit": 2000\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\nThis limits `workspace_a` to 2,000 overage API calls, regardless of the customer-level spend limit.\n\n## Disabling a spend limit\n\nTo remove a spend limit, set `enabled` to `false` or omit the `overageLimit`:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n spendLimits: [{\n featureId: "api_calls",\n enabled: false,\n }],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": False,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "spend_limits": [{\n "feature_id": "api_calls",\n "enabled": false\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\n## Interaction with max purchase (usage limits)\n\nPlans can also have a **max purchase** limit (also called "usage limit") set on a plan item. This is a per-entitlement cap configured in the plan editor or CLI, and applies globally to all customers on that plan.\n\nWhen **both** a spend limit and a max purchase are configured for the same feature, the **spend limit takes precedence**. The per-entitlement max purchase is not enforced while a spend limit is active.\n\n> **Example** <br />\n> A plan item has a max purchase of 1,000 (so customers can use up to 1,000 overage units). But you set a customer-level spend limit of 5,000 on that feature. The customer can use up to 5,000 overage units — the spend limit overrides the plan-level max purchase for that customer.\n\nThis lets you use max purchase as a sensible default for all customers, then selectively raise (or lower) the cap for specific customers using spend limits.\n\nIf you set a spend limit **higher** than the plan\'s max purchase, the customer will be able to exceed the plan-level limit. If you set it **lower**, the customer will be capped before hitting the plan-level limit. In either case, the spend limit is the one that\'s enforced.\n\n## Spend limits vs max purchase\n\n| | Spend Limits | Max Purchase |\n|--|-------------|--------------|\n| **Configured on** | Customer or entity | Plan item (in the plan editor) |\n| **Scope** | Aggregated across all entitlements for a feature | Per-entitlement |\n| **Set via** | [Update Customer](/api-reference/customers/updateCustomer) / [Update Entity](/api-reference/entities/updateEntity) API | Dashboard or CLI when creating a plan |\n| **Dynamic** | Yes — can be changed at any time per-customer | No — applies to all customers on the plan |\n| **Precedence** | Overrides max purchase when set | Used as default when no spend limit is set |\n| **Use case** | Per-customer overage caps (e.g., enterprise spending controls) | Global safety limits for a plan tier |\n\n## Usage Alerts\n\nUsage alerts send a webhook when a customer\'s usage crosses a threshold you define. You can use this to take an action like sending a warning email, prompting an upgrade, or flagging the account internally.\n\n## Configuring usage alerts\n\nUsage alerts are set per-customer (or per-entity) via the API, using the same `billingControls` field as spend limits. There are two threshold types:\n\n- **`usage`** — fires when absolute usage reaches a specific count\n- **`usage_percentage`** — fires when usage reaches a percentage of the included allowance\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n usageAlerts: [{\n featureId: "api_calls",\n threshold: 800,\n thresholdType: "usage",\n enabled: true,\n name: "Approaching limit",\n }],\n },\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "usage_alerts": [{\n "feature_id": "api_calls",\n "threshold": 800,\n "threshold_type": "usage",\n "enabled": True,\n "name": "Approaching limit",\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "usage_alerts": [{\n "feature_id": "api_calls",\n "threshold": 800,\n "threshold_type": "usage",\n "enabled": true,\n "name": "Approaching limit"\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\nFor a percentage-based alert, use `threshold_type: "usage_percentage"` with a value between 0 and 100:\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n usageAlerts: [{\n featureId: "api_calls",\n threshold: 80,\n thresholdType: "usage_percentage",\n enabled: true,\n name: "80% usage warning",\n }],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "usage_alerts": [{\n "feature_id": "api_calls",\n "threshold": 80,\n "threshold_type": "usage_percentage",\n "enabled": True,\n "name": "80% usage warning",\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "usage_alerts": [{\n "feature_id": "api_calls",\n "threshold": 80,\n "threshold_type": "usage_percentage",\n "enabled": true,\n "name": "80% usage warning"\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\nThe `usage_percentage` threshold is calculated against the **included** allowance only. If the customer has overage enabled with a spend limit or max purchase, the percentage still refers to the included balance — not the total available usage.\n\n### Usage alert fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string (optional) | The feature to monitor. If omitted, applies to all features. |\n| `threshold` | number | The value that triggers the alert. Absolute count for `usage`, percentage (0-100) for `usage_percentage`. |\n| `threshold_type` | string | `"usage"` for an absolute count, `"usage_percentage"` for a percentage of the included allowance. |\n| `enabled` | boolean | Whether the alert is active. Defaults to `true`. |\n| `name` | string (optional) | A label to distinguish multiple alerts on the same feature. |\n\n## How usage alerts work\n\n1. On each `track` call, Autumn compares the customer\'s old and new usage against each enabled alert\n2. If the usage crosses the threshold (old usage was below, new usage is at or above), Autumn fires a `balances.usage_alert_triggered` webhook\n3. The alert fires **once** per threshold crossing — it won\'t re-fire on subsequent track calls unless usage drops below the threshold and crosses it again\n\nAlerts also work at the entity level. If you configure alerts on an [entity](/documentation/customers/feature-entities), they fire based on that entity\'s usage independently.\n\nSee the [balances.usage_alert_triggered webhook schema](/api-reference/webhooks/balancesUsageAlertTriggered) for the full payload reference.\n\n## Multiple usage alerts\n\nYou can configure multiple alerts on the same feature or across different features. Each alert fires independently when its threshold is crossed.\n\n<CodeGroup>\n\n```typescript TypeScript\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n usageAlerts: [\n {\n featureId: "api_calls",\n threshold: 500,\n thresholdType: "usage",\n enabled: true,\n name: "500 calls used",\n },\n {\n featureId: "api_calls",\n threshold: 90,\n thresholdType: "usage_percentage",\n enabled: true,\n name: "90% allowance used",\n },\n ],\n },\n});\n```\n\n```python Python\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "usage_alerts": [\n {\n "feature_id": "api_calls",\n "threshold": 500,\n "threshold_type": "usage",\n "enabled": True,\n "name": "500 calls used",\n },\n {\n "feature_id": "api_calls",\n "threshold": 90,\n "threshold_type": "usage_percentage",\n "enabled": True,\n "name": "90% allowance used",\n },\n ],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "usage_alerts": [\n {\n "feature_id": "api_calls",\n "threshold": 500,\n "threshold_type": "usage",\n "enabled": true,\n "name": "500 calls used"\n },\n {\n "feature_id": "api_calls",\n "threshold": 90,\n "threshold_type": "usage_percentage",\n "enabled": true,\n "name": "90% allowance used"\n }\n ]\n }\n }\'\n```\n\n</CodeGroup>\n\nIn this example, the customer will receive two separate webhook events as they use their API calls: one when they hit 500 absolute calls, and another when they reach 90% of their included allowance.',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
path: "references/auto-top-ups.md",
|
|
235
|
+
contents:
|
|
236
|
+
'## Auto Top-Ups\n\nAuto top-ups automatically purchase additional balance for a customer when their usage drops below a configured threshold. This prevents service interruptions for customers who don\'t want to manually manage their balance.\n\n> **Example** <br />\n> A customer on the Standard plan gets 5,000 credits per month. When their balance drops below 500, Autumn automatically purchases 1,000 more credits at $10 using the plan\'s one-off prepaid price.\n\n## Prerequisites\n\nAuto top-ups require:\n1. A plan with a [one-off prepaid](/documentation/modelling-pricing/one-off-purchases) item for the feature you want to auto top-up\n2. The customer must have a saved payment method on file\n\n## Setting up\n\nAuto top-ups are configured per customer, not in `autumn.config.ts`. Your plan needs a one-off prepaid item for the feature you want to auto top-up:\n\n```ts autumn.config.ts\nimport { atmn, feature, plan } from "atmn";\n\nexport const credits = feature({\n featureId: "credits",\n name: "Credits",\n type: "metered",\n consumable: true,\n});\n\nexport const standard = plan({\n planId: "standard",\n versionSlug: "v1",\n active: true,\n name: "Standard",\n price: { amount: 50, interval: "month" },\n items: [\n {\n featureId: credits.featureId,\n included: 5000,\n reset: { interval: "month" },\n },\n {\n featureId: credits.featureId,\n price: {\n amount: 10,\n billingUnits: 1000,\n interval: "one_off",\n billingMethod: "prepaid",\n },\n },\n ],\n});\n\nexport default atmn({ features: [credits], plans: [standard] });\n```\n\nThe one-off prepaid item (`$10 per 1,000 credits`) is what Autumn uses to replenish the balance. Configure auto top-ups per customer via the API (see below).\n\n## Configuring auto top-ups via API\n\nSet up auto top-ups for a customer by updating their billing controls:\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_..." });\n\nawait autumn.customers.update({\n customerId: "user_123",\n billingControls: {\n autoTopups: [{\n featureId: "credits",\n enabled: true,\n threshold: 500,\n quantity: 1000,\n }],\n },\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_...")\n\nawait autumn.customers.update(\n customer_id="user_123",\n billing_controls={\n "auto_topups": [{\n "feature_id": "credits",\n "enabled": True,\n "threshold": 500,\n "quantity": 1000,\n }],\n },\n)\n```\n\n```bash cURL\ncurl -X POST "https://api.useautumn.com/v1/customers/update" \\\n -H "Authorization: Bearer am_sk_..." \\\n -H "Content-Type: application/json" \\\n -d \'{\n "customer_id": "user_123",\n "billing_controls": {\n "auto_topups": [{\n "feature_id": "credits",\n "enabled": true,\n "threshold": 500,\n "quantity": 1000\n }]\n }\n }\'\n```\n\n</CodeGroup>\n\n## Auto top-up configuration\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `feature_id` | string | The feature to monitor |\n| `enabled` | boolean | Whether auto top-up is active |\n| `threshold` | number | Balance level that triggers a top-up |\n| `quantity` | number | How many units to purchase each time |\n| `purchase_limit` | object | Optional limit on how often top-ups can occur |\n\n### Purchase limits\n\nTo prevent runaway spending, you can set a purchase limit:\n\n```json\n{\n "purchase_limit": {\n "interval": "month",\n "interval_count": 1,\n "limit": 5\n }\n}\n```\n\nThis limits the customer to 5 auto top-ups per month. Supported intervals: `hour`, `day`, `week`, `month`.\n\n## How it works\n\n1. After every usage event (via `track`), Autumn checks the customer\'s remaining balance\n2. If the balance falls below the configured `threshold`, an auto top-up is triggered\n3. Autumn creates an invoice for the configured `quantity` using the one-off prepaid price from the customer\'s plan\n4. The invoice is charged to the customer\'s saved payment method\n5. The balance is replenished with the purchased amount\n\nAuto top-ups use burst suppression to prevent duplicate purchases when multiple track events happen in quick succession. There\'s a 30-second cooldown between top-ups for the same feature.\n\n## Notifications\n\nSubscribe to the [`billing.auto_topup_succeeded`](/api-reference/webhooks/billingAutoTopupSucceeded) webhook to be notified when a top-up grants credits. The payload includes the granted quantity, the new balance, and the underlying invoice — useful for sending receipts, updating internal ledgers, or reconciling balance after a recharge.\n\nSubscribe to [`billing.auto_topup_failed`](/api-reference/webhooks/billingAutoTopupFailed) to monitor auto top-ups that are blocked, declined, or fail before granting balance. The payload includes a machine-readable `reason` and any available provider error details.\n\nLimit-blocked failure webhooks are suppressed per blocking window to avoid duplicate notifications while the same limit remains active.',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
path: "references/display-billing.md",
|
|
240
|
+
contents:
|
|
241
|
+
'## Build your billing page\n\nSoftware applications typically ship with a billing page. This allows customers to change plan, cancel subscription and view their usage.\n\nThe customer endpoint returns the current state of the customer, including their active subscriptions, one-time purchases, and feature balances.\n\n## Pricing table\n\nWhen building a pricing table, you need to know what each plan means for the current customer — is it an upgrade, a downgrade, or their current plan? Is a free trial available?\n\nPass a `customerId` when listing plans and each plan will include a `customerEligibility` object:\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `attachAction` | `"activate"` \\| `"upgrade"` \\| `"downgrade"` \\| `"purchase"` \\| `"none"` | What happens when this plan is attached |\n| `status` | `"active"` \\| `"scheduled"` \\| undefined | The customer\'s current relationship to this plan |\n| `trialAvailable` | boolean | Whether the customer is eligible for the plan\'s free trial |\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst { list: plans } = await autumn.plans.list({\n customerId: "user_123",\n});\n\nfor (const plan of plans) {\n console.log(plan.name, plan.customerEligibility?.attachAction);\n // e.g. "Free" "downgrade", "Pro" "none", "Enterprise" "upgrade"\n}\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nplans = await autumn.plans.list(customer_id="user_123")\n\nfor plan in plans.list:\n print(plan.name, plan.customer_eligibility.attach_action)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/plans.list\' \\\n -H \'Authorization: Bearer am_sk_test_1234\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{ "customer_id": "user_123" }\'\n\n# Each plan in the response includes customer_eligibility\n```\n\n</CodeGroup>\n\n## Switching plans\n\nSwitching plans uses `billing.attach`. See [Attaching Plans](/documentation/customers/payment-flow) for the full guide.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst response = await autumn.billing.attach({\n customerId: "user_123",\n planId: "pro",\n});\n\n// Redirect to complete payment or confirm plan change\nredirect(response.paymentUrl);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.billing.attach(\n customer_id="user_123",\n plan_id="pro",\n)\n# Redirect to response.payment_url\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/attach\' \\\n -H \'Authorization: Bearer am_sk_test_1234\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro"\n }\'\n```\n\n</CodeGroup>\n\n## Cancelling a plan\n\nCancel a subscription using `billing.update` with a `cancelAction`. See [Subscription Lifecycle](/documentation/customers/subscription-lifecycle#cancellations) for the full guide on immediate vs end-of-cycle cancellations.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\n// Cancel at end of billing cycle\nawait autumn.billing.update({\n customerId: "user_123",\n planId: "pro",\n cancelAction: "cancel_end_of_cycle",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\n# Cancel at end of billing cycle\nawait autumn.billing.update(\n customer_id="user_123",\n plan_id="pro",\n cancel_action="cancel_end_of_cycle",\n)\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing/update\' \\\n -H \'Authorization: Bearer am_sk_test_1234\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "cancel_action": "cancel_end_of_cycle"\n }\'\n```\n\n</CodeGroup>\n\n## Uncancelling a plan\n\nIf a subscription has a pending cancellation, a scheduled downgrade, or a scheduled plan switch, you can reverse it with `cancelAction: "uncancel"`.\n\nA subscription is pending cancellation when `canceledAt` is not null while the subscription is still `active`.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst customer = await autumn.customers.getOrCreate({\n customerId: "user_123",\n});\n\nconst cancellingSub = customer.subscriptions?.find(\n (sub) => sub.status === "active" && sub.canceledAt !== null\n);\n\nif (cancellingSub) {\n await autumn.billing.update({\n customerId: "user_123",\n planId: cancellingSub.planId,\n cancelAction: "uncancel",\n });\n}\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\ncustomer = await autumn.customers.get_or_create(\n customer_id="user_123"\n)\n\ncancelling_sub = next(\n (s for s in customer.subscriptions\n if s.status == "active" and s.canceled_at is not None),\n None,\n)\n\nif cancelling_sub:\n await autumn.billing.update(\n customer_id="user_123",\n plan_id=cancelling_sub.plan_id,\n cancel_action="uncancel",\n )\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing/update\' \\\n -H \'Authorization: Bearer am_sk_test_1234\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "plan_id": "pro",\n "cancel_action": "uncancel"\n }\'\n```\n\n</CodeGroup>\n\n## Active plans\n\nDisplay the plan the user is currently on. Users can have multiple active subscriptions and purchases (e.g., main plan and add-ons).\n\n- **`subscriptions`** - Free and paid recurring plans\n- **`purchases`** - One-off plans (e.g., credit top-ups)\n\n<CodeGroup>\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst customer = await autumn.customers.getOrCreate({\n customerId: "user_123",\n});\n\nconst active = customer.subscriptions?.filter(\n (sub) => sub.status === "active"\n);\n\nconsole.log(active?.map((sub) => sub.planId).join(", "));\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\ncustomer = await autumn.customers.get_or_create(\n customer_id="user_123"\n)\n\nactive = [s for s in customer.subscriptions if s.status == "active"]\nprint([s.plan_id for s in active])\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/customers\' \\\n -H \'Authorization: Bearer am_sk_test_1234\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{ "customer_id": "user_123" }\'\n\n# Response includes subscriptions array\n```\n</CodeGroup>\n\n## Usage balances\n\nMetered features have `granted`, `usage`, and `remaining` fields. Use these to display current usage and remaining balance.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst customer = await autumn.customers.getOrCreate({\n customerId: "user_123",\n});\n\nconst messages = customer.balances?.messages;\nconsole.log(`${messages?.remaining} / ${messages?.granted}`);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\ncustomer = await autumn.customers.get_or_create(\n customer_id="user_123"\n)\n\nmessages = customer.balances.get("messages")\nprint(f"{messages.remaining} / {messages.granted}")\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/customers\' \\\n -H \'Authorization: Bearer am_sk_test_1234\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{ "customer_id": "user_123" }\'\n\n# Response includes balances.[feature_id]\n```\n</CodeGroup>\n\n## Stripe billing portal\n\nThe Stripe billing portal lets users manage their payment method, view past invoices, and cancel their plan.\n\nEnable the billing portal in your [Stripe settings](https://dashboard.stripe.com/settings/billing/portal).\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst { url } = await autumn.billing.openCustomerPortal({\n customerId: "user_123",\n returnUrl: "https://your-app.com/billing",\n});\n\nredirect(url);\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.billing.open_customer_portal(\n customer_id="user_123",\n return_url="https://your-app.com/billing",\n)\n# Redirect to response.url\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/billing.open_customer_portal\' \\\n -H \'Authorization: Bearer am_sk_test_1234\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "return_url": "https://your-app.com/billing"\n }\'\n```\n\n</CodeGroup>\n\n## Usage timeseries chart\n\nAutumn provides aggregate time series queries for usage data. Pass the response to a charting library like Recharts.\n\n<CodeGroup>\n\n```typescript TypeScript\nimport { Autumn } from "autumn-js";\n\nconst autumn = new Autumn({ secretKey: "am_sk_test_1234" });\n\nconst { list, total } = await autumn.events.aggregate({\n customerId: "user_123",\n featureId: "messages",\n range: "30d",\n});\n```\n\n```python Python\nfrom autumn_sdk import Autumn\n\nautumn = Autumn("am_sk_test_1234")\n\nresponse = await autumn.events.aggregate(\n customer_id="user_123",\n feature_id="messages",\n range="30d",\n)\n# response.list, response.total\n```\n\n```bash cURL\ncurl -X POST \'https://api.useautumn.com/v1/events.aggregate\' \\\n -H \'Authorization: Bearer am_sk_test_1234\' \\\n -H \'Content-Type: application/json\' \\\n -d \'{\n "customer_id": "user_123",\n "feature_id": "messages",\n "range": "30d"\n }\'\n```\n</CodeGroup>\n\nYou can also use the [`events.list`](/api-reference/events/listEvents) method to get the raw event data and display it in a table.\n\n---\n\n**Next: Deploy to production**\n\nOnce your billing page is in place, go through the production checklist to launch with real payments.\n\nA checklist to go live with confidence',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
path: "references/deploy.md",
|
|
245
|
+
contents:
|
|
246
|
+
"## Deploy to production\n\nOnce you've tested your integration in sandbox, follow this checklist to go live with real payments.\n\n<Steps>\n<Step>\n### Connect your live Stripe account\n\nIn the Autumn dashboard, open the **Deploy to Production** dialog from the sidebar.\nConnect your live Stripe account via OAuth — this links Autumn to your real Stripe environment.\n\nYour sandbox uses a shared Stripe test account by default. Production requires your own Stripe account.\n\n</Step>\n\n<Step>\n### Push your plans to production\n\nIf you're using the [CLI](/cli/getting-started), add `-p` to target production. It uses `AUTUMN_PROD_SECRET_KEY`. `push -p` only previews the change. Add `--yes` to apply it:\n\n```bash\nbunx atmn push -p\nbunx atmn push -p --yes\n```\n\n`atmn login` writes `AUTUMN_PROD_SECRET_KEY` next to your sandbox key, so you can use both.\n\nAlternatively, the Deploy dialog in the dashboard can copy your sandbox plans to production for you.\n\n</Step>\n\n<Step>\n### Swap your API key\n\nPoint your server-side code at a live secret key. Create a production key from [Developer Settings](https://app.useautumn.com/production/dev?tab=api_keys), and set it in your production environment:\n\n```bash .env\nAUTUMN_SECRET_KEY=am_sk_live_...\n```\n\nOnly set the live key in your production environment. Keep your local `AUTUMN_SECRET_KEY` as the sandbox key. CLI commands with `-p` read `AUTUMN_PROD_SECRET_KEY`, so you never need to overwrite it.\n\nDouble check that:\n- Your **server-side** code uses the live secret key (`am_sk_live_*`)\n- If you're using a publishable key client-side, it's the live one (`am_pk_live_*`)\n\nThe key prefix determines the environment automatically — `_test_` routes to sandbox, `_live_` routes to production. There's no separate \"environment\" config to flip.\n\n</Step>\n\n<Step>\n### Verify fail-open behavior\n\nAutumn's SDK is **fail-open by default** — if Autumn is unreachable, `check`, `track`, and customer fetches return safe dummy responses instead of throwing errors. This means Autumn can never take your app down.\n\nYou should verify this before going live. The easiest way is to point the SDK at a non-existent URL and exercise your app's core flows:\n\n<CodeGroup>\n\n```typescript TypeScript\nconst autumn = new Autumn({\n secretKey: process.env.AUTUMN_SECRET_KEY,\n serverURL: \"https://localhost:9999\", // simulate outage\n});\n```\n\n```python Python\nautumn = Autumn(\n secret_key=os.environ[\"AUTUMN_SECRET_KEY\"],\n server_url=\"https://localhost:9999\", # simulate outage\n)\n```\n\n</CodeGroup>\n\nWith this in place:\n\n1. Trigger actions that call `check` — they should return `allowed: true`\n2. Trigger actions that call `track` — they should not crash\n3. Confirm your core user flows work as normal\n4. Remove the `serverURL` override when done\n\nWhile the SDK gracefully handles outages for read-path calls, write operations like `attach` (which initiate checkout or subscription changes) will still fail when Autumn is unreachable.\n\n</Step>\n\n<Step>\n### Set up webhooks (if applicable)\n\nIf you're listening for Autumn [webhook events](/documentation/webhooks) (e.g. `customer.products.updated`), make sure your production webhook endpoint is configured in the dashboard. Verify you can receive a test event.\n\n</Step>\n\n<Step>\n### Monitor your first users\n\nAfter deploying, keep an eye on the [Customers](https://app.useautumn.com/production/customers) page in the Autumn dashboard. Verify that:\n\n- New customers are created correctly\n- Subscriptions are attached as expected\n- Usage is being tracked\n- Invoices are generated in Stripe\n\n</Step>\n\n</Steps>\n\n---\n\nOnce you've completed this checklist, you're live. Your sandbox environment remains available for testing new plans and pricing changes before pushing them to production.",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
path: "references/fail-open.md",
|
|
250
|
+
contents:
|
|
251
|
+
'## Fail-Open Defaults\n\nAutumn\'s SDKs include a **fail-open** mechanism that prevents your application from going down if Autumn is temporarily unreachable. When enabled, critical SDK methods return safe default responses instead of throwing errors.\n\n## Why fail-open?\n\nAs a billing engine, Autumn sits in the critical path of your application. If your app calls `check()` to gate access to a feature, and Autumn is unreachable, that call would throw an error -- effectively blocking your users from accessing your product.\n\nFail-open ensures your users are never blocked by an Autumn outage. Usage events may be lost during the outage, but your customers stay unaffected.\n\n## Behavior\n\nFail-open is **enabled by default**. When Autumn is unreachable (network errors, timeouts, or server errors returning 5XX status codes), the SDK returns safe defaults:\n\n| Method | Default response | Effect |\n|--------|-----------------|--------|\n| `check()` | `{ allowed: true }` | Users retain access to features |\n| `track()` | `{ value: 0, balance: null }` | Usage event is silently dropped |\n| `customers.getOrCreate()` | Sentinel customer with `id: null` | Returns a valid but empty customer object |\n\nWhen fail-open triggers, the SDK logs a prominent error to your console so you\'re immediately aware of the issue.\n\n## When fail-open triggers\n\nFail-open activates when the SDK encounters:\n\n- **Server errors** -- Autumn returns a 5XX status code (500, 502, 503, etc.)\n- **Network failures** -- DNS resolution failure, connection refused, connection reset\n- **Timeouts** -- The request to Autumn times out\n\nFail-open does **not** activate for client errors (4XX). If you receive a 400 (bad request), 401 (unauthorized), or 404 (not found), those errors propagate normally since they indicate a problem with your integration, not an Autumn outage.\n\n## Configuration\n\n### Disabling fail-open\n\nIf you prefer strict error handling and want all Autumn errors to propagate:\n\n<CodeGroup>\n```typescript TypeScript\nimport { Autumn } from "@useautumn/sdk";\n\nconst autumn = new Autumn({\n secretKey: "sk_...",\n failOpen: false,\n});\n```\n\n```python Python\n# Coming soon\n```\n</CodeGroup>\n\n### Detecting fail-open responses\n\nWhen `check()` fails open, the response will have `allowed: true` with empty values:\n\n```typescript\nconst result = await autumn.check({\n customerId: "cus_123",\n featureId: "messages",\n});\n\n// Normal response: allowed is based on actual balance\n// Fail-open response: allowed is always true, customerId is ""\n```\n\nWhen `customers.getOrCreate()` fails open, the returned customer will have `id: null`:\n\n```typescript\nconst customer = await autumn.customers.getOrCreate({\n customerId: "cus_123",\n});\n\nif (customer.id === null) {\n // Autumn was unreachable, handle gracefully\n}\n```\n\n## Console output\n\nWhen fail-open triggers, you\'ll see this in your server logs:\n\n```\nFATAL AUTUMN ERROR DETECTED; FAILING OPEN; LEARN MORE: https://docs.useautumn.com/docs/fail-open; STATUS PAGE: status.useautumn.com\n Operation: check | Status: 503 | Error: Server error\n```\n\nMonitor your logs for this message. If you see it, check [status.useautumn.com](https://status.useautumn.com) for ongoing incidents.\n\n## SDK support\n\n| SDK | Fail-open support |\n|-----|------------------|\n| `@useautumn/sdk` (TypeScript) | Supported |\n| `autumn-python` (Python) | Coming soon |\n| `autumn-js` (framework SDK) | Coming soon |',
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "autumn-concepts",
|
|
257
|
+
description:
|
|
258
|
+
"Understanding Autumn's billing objects before acting on them — what a plan version is and what active means, when a trial starts or ends billing, how entity- and seat-scoped subscriptions differ from the customer's own, what a customize diff actually changes, and how caps, overage, and top-ups behave. Load before versioning, editing, or drafting a plan, or when a customer's state does not match the plain reading of their plan.",
|
|
259
|
+
version: "2.0.0",
|
|
260
|
+
markdown:
|
|
261
|
+
"---\nname: autumn-concepts\ndescription: Understanding Autumn's billing objects before acting on them — what a plan version is and what active means, when a trial starts or ends billing, how entity- and seat-scoped subscriptions differ from the customer's own, what a customize diff actually changes, and how caps, overage, and top-ups behave. Load before versioning, editing, or drafting a plan, or when a customer's state does not match the plain reading of their plan.\nversion: 2.0.0\n---\n\n# Concepts\n\nAutumn is a database for your application billing state: features, plans, customers, subscriptions, purchases, balances, flags, and billing controls. It helps you iterate on pricing, manage credit access and usage, control overage, and keep billing behavior connected to product access.\n\nAutumn is a layer above Stripe; Stripe still handles subscription management, invoicing, and payment processing. Autumn provisions and updates Stripe subscriptions, schedules, invoices, and related billing objects for you.\n\n## Object graph\n\n```txt\nOrganization\n- features[] -> Feature\n- plans[] -> Plan\n- customers[] -> Customer\n\nConfiguration model:\nFeature\n- referenced by -> Plan Item\n- credit_system type: maps actions to credit costs (flat or tiered)\n\nPlan\n- items[] -> Plan Item\n - feature_id -> Feature\n - optional price -> usage_based or prepaid feature price\n- price -> base recurring or one-off price\n- versions[] -> parallel definitions of this plan; ONE is active\n- variants[] -> Plan (a derived plan storing only its differences)\n- licenses[] -> Plan (a seat plan this plan hands out per seat)\n- aliases -> old plan ids that still resolve after a rename\n\nPlan Item\n- feature_id -> Feature\n- optional price -> usage_based or prepaid feature price\n- pooled? -> entity grants combine into one shared customer balance\n\nRuntime model:\nCustomer\n- subscriptions[] -> Subscription -> Plan (a specific version of it)\n- purchases[] -> Purchase -> Plan\n- balances[feature_id] -> Balance -> Feature\n - pooled balance: fed by entity grants, spent by any entity\n - rollover, expiry, usage windows live here\n- licenses[] -> CustomerLicense -> seats granted / in use / remaining\n- flags[feature_id] -> Flag -> Feature\n- billing_controls -> customer-level usage controls\n- entities[] -> Entity -> same runtime shape scoped under Customer\n\nEntity\n- belongs to -> Customer\n- subscriptions[] -> Subscription -> Plan\n- purchases[] -> Purchase -> Plan\n- balances[feature_id] -> Balance -> Feature\n- flags[feature_id] -> Flag -> Feature\n- license assignment -> holds one seat from the customer's pool\n\nFrom config to customer state:\nPlan + Customer --billing.attach--> Subscription or Purchase\nPlan + Customer + entity_id --billing.attach--> Entity-scoped Subscription or Purchase\nParent plan's licenses --licenses.attach--> seat assigned to an Entity\nSubscription/Purchase -> Balance or Flag provisioning\n```\n\nTwo relationships changed recently — worth stating plainly because older docs describe the old way:\n\n**Versions are groups of customers, not history.** A plan's versions used to be numbered steps in time, and the newest was always live. Now each version is a definition that some group of customers lives on, and exactly one is **active** — the one attach uses when no version is named. Which changes are edits and which are new versions, drafts, and how customers move: the plan definition below.\n\n**Plans connect to other plans.** A plan can have variants (an annual twin storing only its differences), and it can offer licenses (a small seat plan it hands out per seat). So plans form a graph, not a flat list.\n\nUse these definitions as the mental model when designing or changing Autumn\npricing. Reason in terms of features, plans, plan items, customers/entities, and\nbilling controls before writing any config or calling the API — most modeling\nmistakes come from conflating a feature with a plan item, or a plan-level price\nwith a per-feature price.\n\n## Definitions\n\nLoad the matching definition when reasoning about that object.\n\nFor defining a feature — the atomic unit Autumn gates, tracks, or bills, and its types, read `references/feature.md`.\n\nFor defining a plan — the attachable package of items and pricing, read `references/plan.md`.\n\nFor modeling plan items, or when you need concrete API request-body examples (included usage, prepaid, usage-based, tiers), read `references/plan-items.md`.\n\nFor using customize, reading plan update previews, or representing a small diff from a base plan, read `references/customize.md`.\n\nFor reasoning about free trials and when billing begins, read `references/trials.md`.\n\nFor distinguishing a customer from an entity (seats, sub-accounts) and their runtime billing state, read `references/customer-entity.md`.\n\nFor reasoning about billing controls — runtime caps, alerts, overage, and top-ups, read `references/billing-controls.md`.\n\nFor reasoning about licenses — seat plans a parent plan hands out, seat pools, assigning and releasing seats, read `references/licenses.md`.",
|
|
262
|
+
references: [
|
|
263
|
+
{
|
|
264
|
+
path: "references/feature.md",
|
|
265
|
+
contents:
|
|
266
|
+
"### Feature\n\n- Feature is the atomic thing Autumn gates, tracks, or bills.\n- `id` is used in plan items, check/track calls, balances, and flags.\n- `name` and optional `display` labels are for dashboard and billing UI.\n\n</intro>\n\n<relationships>\n\n- `Plan Item -> Feature`: defines how a plan grants or bills the feature.\n- `Balance -> Feature`: runtime state for metered features.\n- `Flag -> Feature`: runtime access for boolean features.\n\n</relationships>\n\n<types>\n\n- `boolean`: on/off access, exposed as flags.\n- `metered`, `consumable: true`: usage is spent and can reset, e.g. API calls or AI messages.\n- `metered`, `consumable: false`: persistent quantity, e.g. seats or storage.\n- `credit_system`: user-defined currency with credit costs for metered consumable features.\n- `ai_credit_system`: a monetary (dollar) balance for AI/LLM token usage, priced from Models.dev model pricing + a configured markup.\n\n</types>\n\n<credit-systems>\n\n- Classic `credit_system`: one shared balance for several metered features; `credit_schema` maps each `metered_feature_id` to a `credit_cost`. Track via the underlying `feature_id`.\n- A schema entry's cost can be flat (`credit_cost`) or a graduated rate card: `tier_behavior: \"graduated\"` with tiers, so an action costs fewer credits at higher volume. Useful for enterprise credit deals.\n- `invoice_credit`: credits that appear as line items on the invoice; requires a price of exactly one currency unit per credit. Activation is currently gated — confirm before promising it.\n- Direct balances always drain before credit-system balances, whatever their intervals.\n- `ai_credit_system`: a monetary balance (units = dollars) for AI/LLM token usage; no `credit_schema`. Cost = Models.dev model pricing + markup.\n - Markups, low to high priority: `default_markup` (global %), `provider_markups` (keyed by the model id's provider prefix), `model_markups` (per model). No markup = Models.dev base cost; `-100` = free (recorded, not deducted).\n - Model ids are `provider/model` (e.g. `anthropic/claude-opus-4-5`, `openrouter/anthropic/...`, `custom/...`). Standard models auto-price from Models.dev; `custom/...` models must set `input_cost`/`output_cost` ($/M tokens) and bill input/output only.\n - Track usage with `trackTokens` (modelId + token counts); Autumn converts to dollars and deducts.\n\n</credit-systems>\n\n<feature-rules>\n\n- Do not create duplicate features for the same resource; use Plan Items to vary allowance, interval, package, or price.\n- Example: `tokens` should be one feature, not separate `monthly_tokens` and `one_time_tokens` features.\n\n</feature-rules>\n\n<additional>\n\n- `event_names`: optional aliases so one `track` request can target usage for multiple features.\n- `credit_schema`: classic `credit_system` only (not `ai_credit_system`); maps `metered_feature_id` to `credit_cost`.\n- `archived`: deprecated config; may still exist in grandfathered plans or subscriptions.\n- Legacy pricing-agent wording: `single_use` means metered consumable; `continuous_use` means metered non-consumable.\n\n</additional>\n\n<useful-docs>\n\n- Concepts overview: https://docs.useautumn.com/documentation/concepts/overview\n- Features concept: https://docs.useautumn.com/documentation/concepts/features\n- Credit systems: https://docs.useautumn.com/documentation/modelling-pricing/credit-systems\n\n</useful-docs>",
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
path: "references/plan.md",
|
|
270
|
+
contents:
|
|
271
|
+
'### Plan\n\n- Plan is the attachable package: Free, Pro, Enterprise, Credit Pack, Add-on, etc.\n- A plan answers two questions: what should this customer get, and how should Autumn treat it when attached?\n- Most "what they get" detail lives in `items[]`; most lifecycle behavior lives on plan-level fields.\n\n</intro>\n\n<relationships>\n\n- `Plan -> Plan Item`: a plan has many items; items define feature grants, limits, prepaid packages, and overage prices.\n- `Subscription -> Plan`: recurring or free plan attached to a customer or entity.\n- `Purchase -> Plan`: one-off plan attached to a customer or entity.\n- `Customer/Entity + Plan --billing.attach--> Subscription/Purchase`: attach turns plan configuration into customer state.\n- Plans also connect to plans, both edges built on customize: a **variant** is a derived plan storing its diff from a base; a **license** is a parent\'s link to a child plan it hands out per seat, optionally customized per parent. See the variants section below and the Licenses concept.\n\n</relationships>\n\n<composition>\n\n- Use `price` for the plan-level/base charge, such as $20/month for Pro or a one-off flat fee.\n- Use `items[]` as the packaging of the plan: feature grants, seats, overages, prepaid packs, boolean access, and add-on contents.\n- Common pattern: `Plan.price` is the platform/package fee; `Plan.items[]` define the packaged value and any feature-level billing.\n- `price: null` does not always mean free; the plan can still be paid if its items contain usage-based or prepaid prices.\n- If the pricing question is "what does this feature grant or bill?", answer it in Plan Item, not Plan.\n\n</composition>\n\n<plan-types>\n\n- Recurring plan: has at least one recurring paid price or recurring lifecycle; attach creates a subscription.\n- Free plan: has no paid prices; attach creates a free subscription.\n- One-off plan: has at least one paid price and all paid prices are one-off; attach creates a purchase.\n- One-off examples: $10 flat purchase, or $10 for 100 prepaid credits.\n- If any price is monthly or yearly, e.g. $10/month, it is not a one-off plan.\n\n</plan-types>\n\n<default-behavior>\n\n- `auto_enable` automatically attaches the plan when a subject is created.\n- Use it for free/default access, not normal paid plans.\n- Common examples: free tier, limited-time trial access plan, entity default tier.\n- If multiple defaults exist across groups, Autumn can assign one default per group.\n- Never use `auto_enable: true` for paid plans; `Plan.price` must be null and plan items should not contain paid prepaid or usage-based prices.\n\n</default-behavior>\n\n<versions>\n\n- A version is one definition of a plan that a group of customers lives on. A plan\'s versions sit side by side — they are not a timeline, and a newer one is not "the" plan.\n- The test for a new version: **should existing customers keep the old terms?** Yes (a base price increase they are grandfathered on) → a new version; they stay on theirs. No (a feature everyone gets) → an edit to the version they are on, or to every version at once — not a new version.\n- Exactly one version of a plan is **active**: the one `billing.attach` puts a customer on when no version is named, and the one reads resolve to by default. Promoting a version moves that pointer; it moves no customer.\n- A version that is not active is a **draft**: minted, not yet sold. Two flows: mint and promote in one step, or mint as a draft, review it, promote later. Drafts also stage customer groups while migrating from another billing system.\n- `version_slug` is the version\'s name (`v1`, `2026-q3`), unique within the plan. Renaming a slug renames the version; it never mints one. The server also numbers versions in creation order — an internal label, not a meaning.\n- Customers do not move when the pointer moves. Editing a version in place changes what its customers have; moving customers to another version is a **migration**, drafted and run on its own.\n- Variants and licenses are versioned with the plan. A variant\'s customize is a diff over one base version. A license link is pinned to one child version; moving a parent onto another child version is an explicit change to that link. How a catalog update expresses these is the `autumn-catalog` skill\'s.\n- A plan can also have **aliases**: after a plan id rename, the old id still resolves to the plan.\n\n</versions>\n\n<variants>\n\n- Variants group related plans under one base definition and store each variant\'s diff as `variant_details.customize`.\n- `plans.list` returns a flat plan list; each variant plan points back to its base through `variant_details`.\n- In a catalog update, variants are defined or customized under the base plan\'s `variants`, never as top-level plans.\n- A base edit does not reach a variant on its own: each variant either follows the change or keeps its current definition, and the update preview says which for every variant.\n- Common variant uses: billing intervals, A/B price packages, and volume ladders.\n- A variant\'s stored diff can change the price, replace the item list (`items`) or patch it (`add_items` / `remove_items`), and change the trial. A variant cannot be the default plan or have variants of its own.\n\nAnnual interval variant:\n\n```json\n{\n "variant_plan_id": "pro_annual",\n "name": "Pro Annual",\n "customize": {\n "price": { "amount": 200, "interval": "year" }\n }\n}\n```\n\nA/B testing variant:\n\n```json\n{\n "variant_plan_id": "pro_b",\n "name": "Pro B",\n "customize": {\n "price": { "amount": 29, "interval": "month" },\n "add_items": [{ "feature_id": "analytics" }]\n }\n}\n```\n\nMetered volume variant:\n\n```json\n{\n "variant_plan_id": "pro_100k",\n "name": "Pro 100k",\n "customize": {\n "price": { "amount": 35, "interval": "month" },\n "remove_items": [\n { "feature_id": "emails", "billing_method": "usage_based" }\n ],\n "add_items": [\n {\n "feature_id": "emails",\n "included": 100000,\n "price": {\n "amount": 0.9,\n "billing_units": 1000,\n "billing_method": "usage_based",\n "interval": "month"\n }\n }\n ]\n }\n}\n```\n\n</variants>\n\n<trial-behavior>\n\n- This covers how to MODEL trials in the catalog. For how to put a customer on a trial at attach time (card-required, no-card, revert), see the Trials concept.\n- For card-required trials, put `free_trial` on the real paid plan.\n- For no-card trials, prefer a separate limited-time trial plan, e.g. `pro_trial`, plus the real paid `pro` — it gives temporary access, expires automatically, and lets the user later enter the normal checkout flow for `pro`.\n\n</trial-behavior>\n\n<replacement-behavior>\n\n- By default, attaching a plan replaces the customer\'s current plan in the same group.\n- Use `group` when customers can have one active plan from each independent product line.\n- Example: one `support` plan and one `sales` plan can coexist, but two `support` plans should transition.\n- Groups are not needed for simple pricing with one main subscription line.\n\n</replacement-behavior>\n\n<add-on-behavior>\n\n- `add_on` makes the plan additive instead of a replacement.\n- Use add-ons for top-up packs, feature packs, extra concurrency, extra storage, or recurring bolt-ons.\n- Add-ons can be attached alongside other add-ons; repeated attachment can be useful for top-ups or stacked purchases.\n- Add-ons do not participate in normal upgrade/downgrade transitions.\n\n</add-on-behavior>\n\n<useful-docs>\n\n- Concepts overview: https://docs.useautumn.com/documentation/concepts/overview\n- Plans concept: https://docs.useautumn.com/documentation/concepts/plans\n- Free plans: https://docs.useautumn.com/documentation/modelling-pricing/free-plans\n- Recurring plans: https://docs.useautumn.com/documentation/modelling-pricing/recurring\n- Trials: https://docs.useautumn.com/documentation/modelling-pricing/trials\n- Add-ons: https://docs.useautumn.com/documentation/modelling-pricing/add-ons\n\n</useful-docs>',
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
path: "references/plan-items.md",
|
|
275
|
+
contents:
|
|
276
|
+
'### Plan Item\n\n- Plan Item is the join between a Plan and a Feature.\n- It defines what the customer gets for that feature, and whether usage or quantity is billed.\n- Plan items turn a Feature into a customer-facing allowance, limit, prepaid package, or overage price.\n\n**Relationships**\n\n- `Plan -> Plan Item`: a plan has many items.\n- `Plan Item -> Feature`: `feature_id` identifies the feature being granted or billed.\n- `Subscription/Purchase -> Balance`: metered plan items become runtime balances when attached.\n- `Subscription/Purchase -> Flag`: boolean plan items become runtime flags when attached.\n\n## Patterns\n\n### Included or unlimited\n\n- Free allowance that comes with the plan.\n- For consumable features, `reset` controls the cycle, e.g. 5k credits/month on Pro.\n- `unlimited` means the feature is available without a tracked limit.\n- For `ai_credit_system` items, `included` and the balance are in dollars (`included: 10` = $10).\n\n### Boolean\n\n- Pass only `feature_id`; set neither `included` nor `unlimited`. `feature_id` alone grants access.\n- Grants access rather than quantity.\n- Boolean plan items cannot be paid today; charge through `Plan.price` or another metered feature instead.\n\n### Prepaid (consumable)\n\n- Customer buys or subscribes to a consumable quantity upfront, commonly credits.\n- Use for selectable monthly buckets, volume-priced buckets, one-off credit packs, and auto top-up purchase prices.\n- Selectable monthly bucket example: $10 per 1k credits/month, customer chooses 5k credits for $50/month.\n- Volume-priced bucket example: customer selects a monthly credit bucket whose quantity maps to a flat tier price.\n- One-off credit pack example: $10 per 1k lifetime credits.\n- Auto top-up example: same one-off prepaid item is purchased automatically when customer balance falls below threshold.\n- The purchased quantity becomes prepaid balance and is drawn down as usage is tracked.\n\n### Prepaid (non-consumable)\n\n- Customer commits to a persistent quantity upfront, commonly seats or static limits.\n- Quantity does not reset each cycle.\n- The committed quantity is still charged every billing cycle.\n- Can be used as a value the app reads and gates against, even if usage is not tracked.\n- Example: concurrency limit of 10, where the app checks the allowed value but does not track consumption.\n- Mid-cycle quantity changes can create prorated charges or credits.\n\n### Usage-based (consumable)\n\n- Customer is billed in arrears for usage beyond included units.\n- Common for overage, e.g. $0.01/credit after included credits are exhausted.\n- Can be tiered, e.g. 1k API calls free, then $0.02/call up to 5k, then $0.01/call after that.\n\n### Usage-based (non-consumable)\n\n- Customer is billed in arrears for measured persistent usage.\n- Common for storage or compute capacity tracked through the cycle, e.g. $0.05/GB-month for storage used.\n- Usage does not reset like consumable balance, but the billing calculation happens each cycle.\n- If the quantity is only a static entitlement like concurrency, do not use usage-based pricing unless the app reports measured usage.\n\n## Tiers\n\n- `price.tiers` set per-bracket pricing; `tier_behavior` is `volume` or graduated (the default).\n- Volume (`tier_behavior: "volume"`): tiers are `{ amount: 0, to, flat_amount }`. `to` is the cumulative TOTAL the customer gets at that tier — it includes the item\'s `included` free amount, not just the paid amount. The whole selected bucket is billed the tier\'s `flat_amount`.\n- Graduated/per-unit: the customer pays `amount` per `billing_units` within each bracket, and `included` free units are added on top of what they buy.\n\n## Composition\n\n- A single item can combine included units with paid usage, e.g. 5k credits/month then $0.01/credit.\n- A single item can combine included units with prepaid quantity, e.g. 3 seats included then $10/seat prepaid.\n- The same feature can appear in multiple items when the items differ by reset interval or billing method.\n- Monthly allowance plus one-off prepaid top-up item is common for auto top-ups.\n- Prepaid monthly credit bucket plus usage-based overage item is valid when the same feature needs both selected quantity and overage pricing.\n- For per-unit pricing with a base subscription fee, use `Plan.price` for the base fee and a Plan Item for the per-unit feature price.\n\n## Examples\n\n- Included monthly credits:\n ```json\n { "feature_id": "AI_CREDITS", "included": 5000, "reset": { "interval": "month" }, "price": null }\n ```\n- Usage-based overage after included credits:\n ```json\n { "feature_id": "AI_CREDITS", "included": 5000, "reset": { "interval": "month" }, "price": { "amount": 0.01, "interval": "month", "billing_units": 1, "billing_method": "usage_based" } }\n ```\n- Tiered usage-based API calls:\n ```json\n { "feature_id": "api_calls", "included": 1000, "reset": { "interval": "month" }, "price": { "tiers": [{ "to": 5000, "amount": 0.02 }, { "to": "inf", "amount": 0.01 }], "interval": "month", "billing_units": 1, "billing_method": "usage_based" } }\n ```\n Customer gets 1k calls free, then pays tiered overage at the end of the cycle.\n- Base fee plus per-unit seats:\n ```json\n { "plan_price": { "amount": 10, "interval": "month" }, "item": { "feature_id": "seats", "included": 1, "reset": null, "price": { "amount": 10, "interval": "month", "billing_units": 1, "billing_method": "usage_based" } } }\n ```\n Creates $10/month base price with 1 included seat, then $10 per additional seat.\n- Prepaid selectable monthly credit bucket:\n ```json\n { "feature_id": "AI_CREDITS", "included": 5000, "reset": { "interval": "month" }, "price": { "amount": 10, "interval": "month", "billing_units": 1000, "billing_method": "prepaid" } }\n ```\n The customer passes `feature_quantities` to choose total monthly credits; quantity includes included units.\n- Prepaid volume-priced bucket:\n ```json\n { "feature_id": "AI_CREDITS", "included": 5000, "reset": { "interval": "month" }, "price": { "tiers": [{ "to": 31000, "amount": 0, "flat_amount": 250 }, { "to": "inf", "amount": 0, "flat_amount": 10000 }], "tier_behavior": "volume", "interval": "month", "billing_units": 1, "billing_method": "prepaid" } }\n ```\n Use when the user selects a recurring bucket size and the bucket maps to a flat monthly price.\n- One-off prepaid top-up item:\n ```json\n { "feature_id": "AI_CREDITS", "included": 0, "reset": null, "price": { "amount": 10, "interval": "one_off", "billing_units": 1000, "billing_method": "prepaid" } }\n ```\n Use for credit packs and auto top-ups; auto top-up threshold and quantity live on customer billing controls.\n- Prepaid seats:\n ```json\n { "feature_id": "seats", "included": 5, "reset": null, "price": { "amount": 10, "interval": "month", "billing_units": 1, "billing_method": "prepaid" } }\n ```\n Use when the customer commits to a seat quantity upfront; mid-cycle quantity changes can prorate.\n- Usage-based storage:\n ```json\n { "feature_id": "storage_gb", "included": 100, "reset": null, "price": { "amount": 0.05, "interval": "month", "billing_units": 1, "billing_method": "usage_based" } }\n ```\n Use when persistent usage is measured through the cycle and invoiced in arrears.\n- Pooled entity grant:\n ```json\n { "feature_id": "AI_CREDITS", "included": 10000, "reset": { "interval": "month" }, "pooled": true, "price": null }\n ```\n On a plan attached per entity (workspace, project): each entity\'s 10k joins one shared customer balance that any entity — or the customer directly — can spend. Without `pooled`, each entity keeps its own separate balance.\n\n## Advanced\n\n- `rollover`: for consumable features with reset intervals; unused balance can carry forward subject to cap (absolute `max` or `max_percentage` of the grant) and expiry rules. Rollovers remember the originally granted amount, not just what\'s left.\n- For paid consumable items, `price.interval` determines both the billing cycle and the reset cycle.\n- `proration`: mainly relevant to prepaid quantity changes, especially non-consumable or seat-like items.\n- `max_purchase`: less common cap on purchasable units; customer billing controls are often used for spend or purchase limits.\n- `pooled`: on entity-attached plans, the item\'s grant joins one shared customer balance instead of a per-entity one (`pooled: true` on the item, in config and API alike). Rollovers on the contributing item carry into the pool. Customer-level purchases (credit packs, overage — often an add-on plan) stack beside the pool and are spendable by all entities. A per-entity cap on a pooled balance is a usage limit (billing control), not a separate balance. Not allowed on license plans.\n- `entity_feature_id`: legacy/deprecated per-entity balance scoping; prefer entity-scoped plan attachments. Never mention it unless the user\'s config already has it.\n- Auto top-ups require a one-off prepaid item for the feature; customer billing controls configure threshold and quantity.\n- Balances can carry an `expires_at`; expired balances stop counting. Tracked usage can also be windowed (UTC usage windows) for time-boxed caps.',
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
path: "references/customize.md",
|
|
280
|
+
contents:
|
|
281
|
+
'# Customize\n\n`customize` is a patch over a catalog plan. Use it for customer-specific terms, variant definitions, plan update previews, migration drafts, and catalog update previews.\n\n## Rules\n\n- Base price changes go in `customize.price`.\n- Plan item changes are PATCH-style: use `add_items` and `remove_items` in API params.\n- Avoid full `items` replacement unless the API or config workflow specifically requires it.\n- Each remove entry is a filter. Include `billing_method`, `interval`, or `interval_count` when `feature_id` alone could match multiple items.\n- Taking a feature away is always `remove_items`, never an `add_items` entry with `included: 0` — that grants the feature with a zero allowance instead of withholding it, and a boolean feature has no allowance to set. "no approval chains", "without SSO", "0 seats" on a boolean all mean remove.\n- Replace an item by removing the old item and adding the new one in the same patch.\n- Prefer the smallest diff that preserves the plan\'s existing structure.\n- When raising `included` on a tiered item, keep the ladder valid: every tier boundary (`to`) must be strictly greater than the new `included`. Drop or shift any boundary at or below it instead of zeroing its price.\n\n## API examples\n\nChange base price:\n\n```json\n{ "customize": { "price": { "amount": 50, "interval": "month" } } }\n```\n\nAdd a boolean feature:\n\n```json\n{ "customize": { "add_items": [{ "feature_id": "sso" }] } }\n```\n\nRemove a feature:\n\n```json\n{ "customize": { "remove_items": [{ "feature_id": "audit_logs" }] } }\n```\n\nChange included amount:\n\n```json\n{\n "customize": {\n "remove_items": [{ "feature_id": "credits" }],\n "add_items": [{ "feature_id": "credits", "included": 5000 }]\n }\n}\n```\n\nChange only the monthly item when the same feature also has a lifetime item:\n\n```json\n{\n "customize": {\n "remove_items": [\n {\n "feature_id": "credits",\n "billing_method": "prepaid",\n "interval": "month"\n }\n ],\n "add_items": [\n {\n "feature_id": "credits",\n "included": 5000,\n "reset": { "interval": "month" }\n }\n ]\n }\n}\n```\n\nChange prepaid to usage-based:\n\n```json\n{\n "customize": {\n "remove_items": [{ "feature_id": "credits" }],\n "add_items": [\n {\n "feature_id": "credits",\n "included": 0,\n "price": {\n "amount": 0.01,\n "interval": "month",\n "billing_method": "usage_based"\n }\n }\n ]\n }\n}\n```',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
path: "references/trials.md",
|
|
285
|
+
contents:
|
|
286
|
+
'### Trials\n\n- A trial gives a customer temporary, free access to a plan. It can be passed in during the billing call, or it can come with the plan configuration itself (which can be overriden).\n- Set a trial with `free_trial` on attach: `{ duration_length, duration_type (day|month|year), card_required, on_end }`. Always pass `duration_type`. \n- Default to card not required (if there is no paid plan), and on_end: revert (if there is a paid plan).\n\nIf the customer the customer is NOT on a paid plan (free plan or no plan at all). 2 options:\n\n- No-card trial (default to this): attach with `free_trial` and set `card_required` false. The subscription starts with no card and ends at trial end if none is added. While on it, the customer cannot upgrade or attach another plan until they add a card via the Stripe billing portal.\n- No-card trials cannot be combined with `invoice_mode` (attach rejects it). If an invoice is needed, use `card_required: true`.\n- Card-required trial: attach with `free_trial` and `card_required: true` and `long_lived_checkout` If the customer has no payment method, the attach returns a checkout URL to collect a card; they are charged when the trial ends. This should be done with a long-lived checkout URL param.\n\nThe customer already has an active (Stripe) subscription — common in sales-led trials.\n\n- On end: revert (default to this): attach the new plan with `on_end: "revert"` . This grants the plan in Autumn without touching the Stripe subscription; at trial end Autumn moves the customer back to their original plan, preserving the existing billing cycle.\n- To end a revert trial early, cancel it with `updateSubscription` and `cancel_action: "cancel_immediately"`; Autumn restores the previous plan. Do not remove `free_trial` (that converts the trial to paid) or re-attach the old plan.\n- On end: bill -- attaching a plan with a trial (or updating the subscription to add one) resets the Stripe billing anchor/cycle. This can be undesired so warn the user if they request this.\n- Card required param is ignored if there is already an active sub.\n\n\nUpdating or ending a trial\n- Call update_subscription on the trialing plan with the new trial nested under `customize`: `{ customer_id, subscription_id, customize: { free_trial: { duration_length, duration_type, card_required, on_end } } }`. Never put `free_trial` at the top level of an update: the update endpoint rejects a request whose only change is a top-level `free_trial` ("At least one update parameter must be provided"), while `customize.free_trial` is accepted. The duration is counted from now, not from the original start. A 14-day extension on day 10 of a 14-day trial gives 14 more days, not 4.\n- For a bill-on-end trial, pass `customize: { free_trial: null }` to end the trial immediately and start paid billing. Never do this for a revert trial: it activates the trial plan at full price instead of restoring the old plan. Cancel a revert trial instead (see above).',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
path: "references/customer-entity.md",
|
|
290
|
+
contents:
|
|
291
|
+
"### Customer and Entity\n\n- Customer and Entity are the runtime view of billing state: what plans are attached, what access exists, what usage has happened, and what billing controls apply.\n- A Customer is the primary subject being billed or entitled, usually a user, account, workspace, or organization.\n- Entities are optional child subjects under a customer, such as deployments, seats, users, projects, or sub-accounts.\n- Depending on the org's configuration, entities may not be used at all; if they are in play, billing state can exist at both customer and entity scope.\n- Features, Plans, and Plan Items define configuration; Customer and Entity show how that configuration materializes for one real subject.\n\n</intro>\n\n<shared-shape>\n\n- `subscriptions[]` and `purchases[]` show which plans this customer or entity has subscribed to or bought.\n- These arrays describe the join between subject and plan: status, start/end dates, expiry, schedule state, quantity, and attached plan context.\n- `flags[feature_id]` and `balances[feature_id]` show runtime feature state for this subject.\n- Boolean features materialize as flags: access is on or off.\n- Flag fields mostly mirror the API reference and are straightforward, so this card does not expand every field.\n- Metered and credit features materialize as balances: aggregate granted, included, usage, remaining, reset timing, and related runtime details.\n\n <balances>\n\n - Balances are the runtime state for metered and credit-system features.\n - The parent balance object is an aggregate view for one feature.\n - Each breakdown item is the actual balance source: usually from attaching a plan item, or from a standalone grant created with `balances.create`.\n - Plan source shape: `Customer/Entity -> Balance Breakdown -> Plan Item`.\n - `granted` is included grant plus prepaid grant.\n - `remaining` is the positive balance left from included/prepaid grants and never goes below 0.\n - `usage` is how much has been used; if usage exceeds granted, the subject is in overage.\n - A pooled balance is one customer-level balance fed by entity-attached plans whose items have `pooled: true`. Every entity — and the customer — sees the same number, and any of them can spend it. Customer-level purchases (packs, overage) stack beside the pool.\n - Other balance fields, such as reset timing and unlimited status, are usually self-explanatory from the API reference.\n\n </balances>\n\n</shared-shape>\n\n<customer-vs-entity-scope>\n\n- Customer-level state belongs to the parent customer.\n- Entity-level state belongs to one specific entity under the customer.\n- Customer-level check/track calls do not inherit entity-level subscriptions, purchases, balances, or flags.\n- Entity-level check/track calls can use customer-level state plus matching entity-level state.\n- If a feature is granted only at entity level, include `entity_id` when checking or tracking it.\n- If all entities share the same allowance, model the allowance at customer level and omit `entity_id` for shared usage.\n- Legacy `entity_feature_id` scoped one plan item's balance across many entities under the customer; this model is deprecated.\n- Prefer attaching plans at entity scope when each entity needs its own tier, balance, or subscription state.\n\n</customer-vs-entity-scope>\n\n<entity-patterns>\n\n- Entity-level balances: one customer subscription grants per-entity limits, useful when all entities get the same features and limits.\n- Entity-level subscriptions: attach plans with `entity_id`, useful when each entity can have its own tier.\n- Pooled balances: entity-attached plans with pooled items feed one shared customer balance any entity can spend — see the pooled-balances concept.\n- License seats: an entity can hold one seat from the customer's license pool — see the licenses concept.\n- Entity-level controls can override customer-level controls for that entity where supported.\n\n</entity-patterns>\n\n<identity>\n\n- Customer `id` and entity `id` should be identifiers from the user's own app database.\n- Users do not need to store a separate Autumn-only ID for customers or entities.\n\n</identity>\n\n<additional>\n\n- Billing controls can change usage behavior after balances are provisioned; see the Billing Controls section in this Concepts resource.\n- Use `expand` to pull related details when needed, such as attached plans, features, entities, invoices, payment method, or billing-control runtime state.\n\n</additional>\n\n<useful-docs>\n\n- Creating customers: https://docs.useautumn.com/documentation/customers/creating-customers\n- Managing customers: https://docs.useautumn.com/documentation/customers/managing-customers\n- Entities: https://docs.useautumn.com/documentation/customers/feature-entities\n- Balances concept: https://docs.useautumn.com/documentation/concepts/balances\n- Subscriptions concept: https://docs.useautumn.com/documentation/concepts/subscriptions\n- Checking access: https://docs.useautumn.com/documentation/customers/check\n- Tracking usage: https://docs.useautumn.com/documentation/customers/tracking-usage\n\n</useful-docs>",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
path: "references/billing-controls.md",
|
|
295
|
+
contents:
|
|
296
|
+
'### Billing Controls\n\n- Billing controls are policy on top of what a plan grants: they change how usage is capped, alerted, allowed past the balance, or topped up — never what the plan includes.\n- They live at three levels: on a **plan** (defaults every subscriber inherits), on a **customer** (their own settings), and on an **entity** (per seat/workspace overrides). Plan-level controls are part of the catalog.\n- Each control is a per-feature list entry: at most one entry per feature (usage limits: per feature + filter combination).\n\n</intro>\n\n<control-types>\n\n- `usage_limits`: a hard cap over a time window, independent of balance — "1,000 emails a month, but never more than 200 a day". Fields: `feature_id`, `limit`, `interval` (`day`/`week`/`month`/`year`), optional `anchor` (`billing_cycle` default, or `utc` calendar windows), optional `filter` on event properties (only matching events count — the tool for capping one action inside a shared credit system, e.g. 10 `action_1` calls/day). The cap\'s window is independent of the allowance\'s reset cadence (a daily cap on a monthly grant), and an entity-level entry is how you cap one entity\'s draw on a pooled balance (100 shared credits, 10 per workspace).\n- `spend_limits`: caps and controls **overage** for a feature. `overage_limit` is how much overage may accrue past the balance — in feature units (`limit_type: "absolute"`) or as a percent of the main-plan allowance (`limit_type: "usage_percentage"`). `skip_overage_billing: true` lets overage accrue but never invoices it.\n- `usage_alerts`: notify at a threshold; never block. `threshold_type` is `usage`, `usage_percentage`, `remaining`, or `remaining_percentage`; `threshold` is a count for the absolute types, a percent otherwise. An escalation ladder of several alerts on one feature (80% / 100% / 120%) is normal — give each a `name`.\n- `overage_allowed`: whether usage may continue past the granted balance at all. Enabled with no overage price = uncapped, unbilled overage; explicitly disabled = hard stop even where an overage price exists.\n- `auto_topups`: automatically buy prepaid quantity when the balance drops below `threshold` — buys `quantity` units, optionally rate-limited by `purchase_limit` (N top-ups per hour/day/week/month) and `invoice_mode` (send an invoice instead of charging the card). Requires the feature to have a one-off prepaid purchase item on some plan. Customer and plan level only — never on entities. Repeated card failures suspend it and emit a `billing.auto_topup_failed` webhook.\n\n</control-types>\n\n<the-three-overage-knobs>\n\nThree controls sound alike but answer different questions — conflating them is the common failure:\n\n| Question | Knob |\n|---|---|\n| Can usage go past the balance at all? | `overage_allowed.enabled` |\n| How far past? | `spend_limits.overage_limit` |\n| Do we invoice what we let them incur? | `spend_limits.skip_overage_billing` |\n\nEvery overage behavior is a combination of the three:\n\n| They want | The combination |\n|---|---|\n| Hard stop at the balance | nothing — the default when no overage price exists |\n| Billed overage, up to a ceiling | `overage_limit` on top of the item\'s overage price |\n| A free buffer past the grant, then stop | `enabled: true` + `skip_overage_billing: true` + `overage_limit` for the buffer size |\n| Overage allowed, never billed, uncapped | `overage_allowed.enabled: true` with no overage price |\n| End-user "bill me for overage" toggle | one spend-limits entry per state: toggle off = the free-buffer row; toggle on = `skip_overage_billing: false` (or drop the entry) |\n\n**Where overage capability comes from — read the item, not the vibe.** When a plan item carries a usage-based price, overage capability ships on the plan: every subscriber can run past the grant and is billed at the item\'s rate, and controls only modulate that (`overage_limit` caps it, `skip_overage_billing` stops invoicing it). When the item is a pure grant (no usage price), zero balance is a hard stop by default; the capability must be granted per customer with `overage_allowed.enabled: true` — and because no price exists, that overage is **permitted, not billed**, so pair it with a `spend_limits.overage_limit` to cap how far the unbilled run goes. Billing the excess is a catalog decision (add a usage price to the item), not a billing-control one.\n\n**The two percentage bases — the standing numeric trap.** A spend limit\'s `usage_percentage` measures the *overage* against the allowance: on 3,000 credits, `overage_limit: 20` = 300 units of headroom, 3,300 total — "20% over" is `20`, never `120`. An alert\'s `usage_percentage` measures *total usage*: `threshold: 120` fires at 3,600.\n\n</the-three-overage-knobs>\n\n<hierarchy>\n\n- Plan-level controls are **inherited defaults**, resolved when access is checked — they are never copied onto the customer. Changing the plan\'s controls changes every subscriber that hasn\'t set their own.\n- Precedence per feature: **entity > customer > plan**. A customer\'s entry for a feature shadows the plan\'s entry wholesale — there is no field-level merging.\n- When several attached plans carry an entry for the same feature: the **most restrictive** wins for `usage_limits`, `spend_limits`, and `overage_allowed`; for `auto_topups` and `usage_alerts` the most recently attached plan wins.\n- Customer API responses tag each control with its `source` (`plan` or `customer`) so you can tell an inherited default from an explicit setting.\n\n</hierarchy>\n\n<wire-shape>\n\nPlan create/update and catalog params take `billing_controls` with the five snake_case lists:\n\n```json\n{\n "billing_controls": {\n "usage_limits": [{ "feature_id": "emails", "limit": 200, "interval": "day" }],\n "spend_limits": [{ "feature_id": "emails", "enabled": true, "overage_limit": 500 }],\n "usage_alerts": [{ "feature_id": "emails", "threshold": 80, "threshold_type": "usage_percentage" }],\n "overage_allowed": [{ "feature_id": "emails", "enabled": true }],\n "auto_topups": [{ "feature_id": "credits", "enabled": true, "threshold": 100, "quantity": 1000 }]\n }\n}\n```\n\nIn `autumn.config.ts` the same shape goes through the `billingControls()` builder on a plan. Updates replace per control list, not per entry — sending `usage_limits` replaces all usage limits, other lists untouched.\n\n</wire-shape>\n\n<agent-rules>\n\n- Inspect current customer/entity state before changing runtime billing controls; check the plan\'s controls before assuming a customer needs their own.\n- Prefer plan-level controls for anything true of every subscriber; reach for customer/entity entries only for per-customer exceptions.\n- For auto top-ups, verify the feature has a one-off prepaid purchase path first.\n- Alerts never block; spend limits are feature units (or percent), not dollars, unless the feature\'s units are dollars.\n\n</agent-rules>\n\n<useful-docs>\n\n- Billing controls: https://docs.useautumn.com/documentation/customers/billing-controls\n- Auto top-ups: https://docs.useautumn.com/documentation/modelling-pricing/auto-top-ups\n- Spend limits and usage alerts: https://docs.useautumn.com/documentation/modelling-pricing/spend-limits\n\n</useful-docs>',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
path: "references/licenses.md",
|
|
300
|
+
contents:
|
|
301
|
+
'# Licenses\n\nA license lets a parent plan hand out another plan per seat. "Team is $40/seat, each seat gets 100 summaries" → the seat is its own plan, and the team plan offers it through a license.\n\n## The three objects\n\n- **Child plan** — the actual product for the child: an ordinary plan whose items are what one seat gets. It needs its own `group`, otherwise attaching it would replace its parent.\n- **License** — the link plus the customized definition: the parent\'s `licenses: [{ license_plan_id, included }]` entry. `included` is how many seats come free with the parent. The license can also customize the child *for this parent only* — a different price, items added or removed — while the child plan itself stays shared.\n- **CustomerLicense** — the runtime record per customer: how many seats they have (`granted` = included + paid), how many are in use (`usage`), how many remain (`remaining`). Its identity is stable across plan versions, so seats never jump around when plans change.\n\n```json\n{\n "plan_id": "team",\n "licenses": [\n {\n "license_plan_id": "seat",\n "included": 2,\n "customize": { "price": { "amount": 40, "interval": "month" } }\n }\n ]\n}\n```\n\n## Why the license is a customization, not a copy\n\nThe child plan is defined once; each parent\'s license describes its own take on it. That buys three things:\n\n- **Sharing** — Team and Enterprise can both offer `seat`, one at $40 and one at $30, without two seat plans:\n\n```json\n[\n { "plan_id": "team", "licenses": [{ "license_plan_id": "seat", "included": 2 }] },\n { "plan_id": "scale", "licenses": [{ "license_plan_id": "seat", "included": 2, "customize": { "price": { "amount": 30, "interval": "month" } } }] }\n]\n```\n\n- **Propagation** — edit the child (add a boolean feature to `seat`) and the change can follow upward to every parent that offers it. Each parent chooses: follow the update, or pin its current version. A parent\'s own declared customize wins over what propagates.\n- **Clean transitions** — a customer moving from Team to Scale, both offering `seat`: each seat assignment carries over intuitively, because the license identity is stable and both parents point at the same child.\n\nA license\'s customize can change the price and add/remove items — nothing else, and licenses don\'t nest (a child plan can\'t offer licenses of its own).\n\n## How seats move\n\n- **Buy** — seat count is set on the *parent* (`license_quantities` on attach/update). The quantity is the total, including the free `included` seats. Buying a priced license attaches it at the customer level automatically.\n- **Assign** — `licenses.attach` gives a seat to an entity (creating it if you pass a `feature_id`). Idempotent; errors when no seats are free.\n- **Release** — `licenses.release` frees the seat. It does **not** change what the customer pays — they still own the seat, it\'s just unassigned.\n\nEmpty seats are normal — that\'s the point: capacity is bought before you know who fills it.\n\n## When licenses are the right model\n\nOne question: **does a seat grant anything?** A seat that carries its own allowance or plan → license. Seats that are only a count you bill → per-unit priced item, no entities. Entities that appear one by one, each picking its own plan → attach plans per entity, no license.\n\n## Not yet available\n\n- Overflow billing (`prepaid_only: false` — auto-billing seats beyond the bought pool) is not available yet.\n- License plans can\'t contain pooled items.',
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
];
|