This commit is contained in:
Ayush Rodrigues
2026-03-09 16:26:15 +00:00
parent 8774f1843e
commit 2aac9b3a8b
26 changed files with 2568 additions and 550 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -6,7 +6,7 @@ description: "Define features, plans, and pricing in autumn.config.ts"
Your `autumn.config.ts` file is the source of truth for your pricing. It exports features and plans using helper functions from the `atmn` package.
```ts autumn.config.ts
import { feature, plan, planFeature } from 'atmn';
import { feature, item, plan } from 'atmn';
export const messages = feature({ ... });
export const pro = plan({ ... });
@@ -38,10 +38,10 @@ Features define what can be gated, metered, or billed in your app.
- `false` -- usage is ongoing (seats, storage, workspaces)
</ParamField>
<ParamField body="credit_schema" type="array">
<ParamField body="creditSchema" type="array">
**Required for `credit_system` features.** Maps metered features to credit costs.
Each entry: `{ metered_feature_id: string, credit_cost: number }`
Each entry: `{ meteredFeatureId: string, creditCost: number }`
</ParamField>
### Feature types
@@ -99,9 +99,9 @@ export const credits = feature({
id: 'credits',
name: 'AI Credits',
type: 'credit_system',
credit_schema: [
{ metered_feature_id: basicModel.id, credit_cost: 1 },
{ metered_feature_id: premiumModel.id, credit_cost: 5 },
creditSchema: [
{ meteredFeatureId: basicModel.id, creditCost: 1 },
{ meteredFeatureId: premiumModel.id, creditCost: 5 },
],
});
```
@@ -131,35 +131,35 @@ Plans combine features with pricing to create your subscription tiers, add-ons,
</ParamField>
<ParamField body="items" type="array">
Array of `planFeature()` objects defining what's included.
Array of `item()` objects defining what's included.
</ParamField>
<ParamField body="auto_enable" type="boolean" default="false">
<ParamField body="autoEnable" type="boolean" default="false">
Automatically assign this plan to new customers. Typically used for free plans.
</ParamField>
<ParamField body="add_on" type="boolean" default="false">
<ParamField body="addOn" type="boolean" default="false">
Allow this plan to be purchased alongside other plans (instead of replacing them).
</ParamField>
<ParamField body="free_trial" type="object">
<ParamField body="freeTrial" type="object">
Free trial before billing starts:
- `duration_length: number` -- eg, `14`
- `duration_type: string` -- `"day"` | `"month"` | `"year"`
- `card_required: boolean` -- whether a card is needed to start the trial
- `durationLength: number` -- eg, `14`
- `durationType: string` -- `"day"` | `"month"` | `"year"`
- `cardRequired: boolean` -- whether a card is needed to start the trial
</ParamField>
<ParamField body="group" type="string">
Group related plans together. Plans in the same group replace each other on upgrade/downgrade.
</ParamField>
## Plan features
## Plan items
Plan features define what each plan includes -- usage limits, pricing, and billing behavior.
Plan items define what each plan includes -- usage limits, pricing, and billing behavior.
### `planFeature(config)`
### `item(config)`
<ParamField body="feature_id" type="string" required>
<ParamField body="featureId" type="string" required>
The `id` of the feature to include.
</ParamField>
@@ -174,7 +174,7 @@ Plan features define what each plan includes -- usage limits, pricing, and billi
<ParamField body="reset" type="object">
How often the included amount resets:
- `interval: string` -- `"hour"` | `"day"` | `"week"` | `"month"` | `"quarter"` | `"semi_annual"` | `"year"`
- `interval_count: number` -- defaults to `1`
- `intervalCount: number` -- defaults to `1`
</ParamField>
<ParamField body="price" type="object">
@@ -183,32 +183,32 @@ Plan features define what each plan includes -- usage limits, pricing, and billi
<ParamField body="proration" type="object">
How to handle mid-cycle quantity changes:
- `on_increase:` `"prorate"` | `"charge_immediately"`
- `on_decrease:` `"prorate"` | `"refund_immediately"` | `"no_action"`
- `onIncrease:` `"prorate"` | `"charge_immediately"`
- `onDecrease:` `"prorate"` | `"refund_immediately"` | `"no_action"`
</ParamField>
<ParamField body="rollover" type="object">
Carry unused balance forward:
- `max: number` -- maximum rollover amount
- `expiry_duration_type:` `"month"` | `"forever"`
- `expiry_duration_length: number` -- ignored if type is `"forever"`
- `expiryDurationType:` `"month"` | `"forever"`
- `expiryDurationLength: number` -- ignored if type is `"forever"`
</ParamField>
### Pricing patterns
The `price` object on a plan feature supports different billing models:
The `price` object on a plan item supports different billing models:
**Usage-based** -- charge based on actual usage:
```ts
planFeature({
feature_id: seats.id,
item({
featureId: seats.id,
included: 5,
price: {
amount: 10,
interval: 'month',
billing_method: 'usage_based',
billing_units: 1,
billingMethod: 'usage_based',
billingUnits: 1,
},
})
```
@@ -216,12 +216,12 @@ planFeature({
**Prepaid** -- customer buys a fixed quantity upfront:
```ts
planFeature({
feature_id: credits.id,
item({
featureId: credits.id,
price: {
amount: 5,
billing_units: 100,
billing_method: 'prepaid',
billingUnits: 100,
billingMethod: 'prepaid',
},
})
```
@@ -229,15 +229,15 @@ planFeature({
**Tiered** -- price changes based on usage volume:
```ts
planFeature({
feature_id: apiCalls.id,
item({
featureId: apiCalls.id,
price: {
tiers: [
{ to: 1000, amount: 0.01 },
{ to: 10000, amount: 0.008 },
{ to: 'inf', amount: 0.005 },
],
billing_method: 'usage_based',
billingMethod: 'usage_based',
interval: 'month',
},
})
@@ -246,26 +246,26 @@ planFeature({
#### Price fields
<ParamField body="amount" type="number">
Price per `billing_units`. Mutually exclusive with `tiers`.
Price per `billingUnits`. Mutually exclusive with `tiers`.
</ParamField>
<ParamField body="tiers" type="array">
Tiered pricing. Each entry: `{ to: number | "inf", amount: number }`. Mutually exclusive with `amount`.
</ParamField>
<ParamField body="billing_method" type="enum" required>
<ParamField body="billingMethod" type="enum" required>
`"usage_based"` | `"prepaid"`
</ParamField>
<ParamField body="interval" type="enum">
`"week"` | `"month"` | `"quarter"` | `"semi_annual"` | `"year"`. Omit for one-time charges. Not needed if the plan feature has a top-level `reset`.
`"week"` | `"month"` | `"quarter"` | `"semi_annual"` | `"year"`. Omit for one-time charges. Not needed if the plan item has a top-level `reset`.
</ParamField>
<ParamField body="billing_units" type="number" default="1">
Units per price. Eg, $5 per 100 credits = `amount: 5, billing_units: 100`.
<ParamField body="billingUnits" type="number" default="1">
Units per price. Eg, $5 per 100 credits = `amount: 5, billingUnits: 100`.
</ParamField>
<ParamField body="max_purchase" type="number">
<ParamField body="maxPurchase" type="number">
Maximum quantity that can be purchased.
</ParamField>
@@ -274,7 +274,7 @@ planFeature({
A complete config with a free plan, a paid plan with a trial, and a credits top-up add-on:
```ts autumn.config.ts
import { feature, plan, planFeature } from 'atmn';
import { feature, item, plan } from 'atmn';
// Features
export const messages = feature({
@@ -301,15 +301,15 @@ export const sso = feature({
export const free = plan({
id: 'free',
name: 'Free',
auto_enable: true,
autoEnable: true,
items: [
planFeature({
feature_id: messages.id,
item({
featureId: messages.id,
included: 5,
reset: { interval: 'month' },
}),
planFeature({
feature_id: seats.id,
item({
featureId: seats.id,
included: 1,
}),
],
@@ -319,29 +319,29 @@ export const pro = plan({
id: 'pro',
name: 'Pro',
price: { amount: 20, interval: 'month' },
free_trial: {
duration_length: 14,
duration_type: 'day',
card_required: true,
freeTrial: {
durationLength: 14,
durationType: 'day',
cardRequired: true,
},
items: [
planFeature({
feature_id: messages.id,
item({
featureId: messages.id,
included: 1000,
reset: { interval: 'month' },
}),
planFeature({
feature_id: seats.id,
item({
featureId: seats.id,
included: 5,
price: {
amount: 10,
interval: 'month',
billing_method: 'usage_based',
billing_units: 1,
billingMethod: 'usage_based',
billingUnits: 1,
},
}),
planFeature({
feature_id: sso.id,
item({
featureId: sso.id,
}),
],
});
@@ -349,14 +349,14 @@ export const pro = plan({
export const topUp = plan({
id: 'top_up',
name: 'Message Top-Up',
add_on: true,
addOn: true,
items: [
planFeature({
feature_id: messages.id,
item({
featureId: messages.id,
price: {
amount: 5,
billing_units: 100,
billing_method: 'prepaid',
billingUnits: 100,
billingMethod: 'prepaid',
},
}),
],

View File

@@ -50,14 +50,7 @@
{
"group": "Getting Started",
"pages": [
{
"group": "Setup and payments",
"pages": [
"documentation/getting-started/setup/react",
"documentation/getting-started/setup/sdk",
"documentation/getting-started/setup/convex"
]
},
"documentation/getting-started/setup",
"documentation/getting-started/gating",
"documentation/getting-started/display-billing"
]
@@ -103,6 +96,7 @@
"group": "Additional Resources",
"pages": [
"documentation/webhooks",
"documentation/external-providers/convex",
"documentation/external-providers/revenuecat",
"documentation/external-providers/vercel-marketplace"
]
@@ -113,8 +107,11 @@
"tab": "Examples",
"icon": "graduation-cap",
"pages": [
"examples/credits",
"examples/monetary-credits",
"examples/prepaid",
"examples/per-seat",
"examples/pay-as-you-go-overages",
"examples/entity-balances",
"examples/trial-card-required",
"examples/trial-card-not-required"
]

View File

@@ -17,7 +17,7 @@ Prepaid features are features where customers pay upfront for a quantity (e.g.,
<Expandable title="Example plan with prepaid feature">
```typescript autumn.config.ts
import { feature, plan, planItem } from "atmn";
import { feature, item, plan } from "atmn";
export const seats = feature({
id: "seats",
@@ -34,7 +34,7 @@ export const team = plan({
interval: "month",
},
items: [
planItem({
item({
featureId: seats.id,
included: 5, // 5 seats included
price: {

View File

@@ -20,7 +20,7 @@ Start by integrating Autumn in your development environment. Replace your existi
- Install the Autumn SDK and configure your API keys
- Replace Stripe checkout, subscription management, and usage tracking with Autumn equivalents
See our [setup guides](/documentation/getting-started/setup/react) for detailed integration instructions.
See our [setup guide](/documentation/getting-started/setup) for detailed integration instructions.
</Step>
@@ -82,7 +82,7 @@ If preserving exact usage counts is critical for your business, reach out to us
<Info>
**Forward deploy service**
If you're processing $1M+ ARR, we can handle the migration and deployment for you at no extra charge. We'll work directly with your engineering team to ensure a smooth transition.
We can handle the migration and deployment for you. We'll work directly with your engineering team to ensure a smooth transition and rolling deployment. Only review from your team is required.
Contact us on [Discord](https://discord.gg/STqxY92zuS) or at hey@useautumn.com to learn more.
Contact us at support@useautumn.com, or book a call with us [here](https://cal.com/ayrod).
</Info>

View File

@@ -1,15 +1,9 @@
---
title: "Using React hooks"
description: "Implement your React + Node.js app's payments and pricing model"
title: "Setup and payments"
description: "Implement your app's payments and pricing model"
---
import CreatePlans from '/snippets/create-plans.mdx';
Autumn's client-side [hooks](/react/hooks/useCustomer) allow you to handle billing directly from your frontend.
<Info>
Client libraries are supported for React and Node.js apps. Please use our [Server-side SDK](/documentation/getting-started/setup/sdk) for other frameworks and languages.
</Info>
In this example we'll create the pricing for a premium AI chatbot. We're going to have:
- A <Badge color="green">Free</Badge> plan that gives users 5 chat messages per month for free
@@ -28,7 +22,7 @@ Create a plan for each pricing tier that your app offers. In our example we'll c
<Step>
### Installation
[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.
[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.
```bash .env
AUTUMN_SECRET_KEY=am_sk_test_42424242...
@@ -52,6 +46,10 @@ pnpm add autumn-js
yarn add autumn-js
```
```bash pip
pip install autumn-sdk
```
</CodeGroup>
<Note>
@@ -60,7 +58,14 @@ yarn add autumn-js
</Note>
</Step>
</Steps>
{/* REACT DOCS */}
<View title="React" icon="react">
Autumn's client-side [hooks](/react/hooks/useCustomer) allow you to handle billing directly from your frontend.
<Steps>
<Step>
### Add Endpoints Server-side
@@ -148,9 +153,14 @@ return new Response(JSON.stringify(result.response), {
</CodeGroup>
<Check>
Autumn's customer ID is the same as your internal user or org ID generated from your auth provider. No need to store any extra IDs.
</Check>
</Step>
<Step>
### Add Provider Client-side
Client side, wrap your application with the `<AutumnProvider>` component.
@@ -174,15 +184,6 @@ export default function RootLayout({ children }: {
}
```
The provider accepts the following props:
| Prop | Description |
|------|-------------|
| `backendUrl` | Base URL for the backend server (e.g., `https://api.example.com`). Defaults to current origin. |
| `pathPrefix` | Path prefix for the Autumn routes. Defaults to `/api/autumn`, or `/api/auth/autumn` if `useBetterAuth` is true. |
| `useBetterAuth` | Use better-auth integration. Sets `pathPrefix` to `/api/auth/autumn` and `includeCredentials` to true by default. |
| `includeCredentials` | Include credentials (cookies) in cross-origin requests. Defaults to true if `useBetterAuth` is true. |
</Step>
<Step>
@@ -253,6 +254,7 @@ You will see your user under the [customers](https://app.useautumn.com/customers
</Step>
<Step>
### Stripe Payment Flow
Call `attach` to attach the <Badge color="blue">Pro</Badge> plan to the customer. The customer is redirected to an Autumn checkout page where they can review prorations and plan changes before confirming. Once they've paid, Autumn will grant access to "100 messages per month" defined in Step 1.
@@ -272,8 +274,8 @@ export default function PurchaseButton() {
onClick={async () => {
await attach({
planId: "pro",
redirectMode: "always",
});
// Hook automatically redirects to Autumn checkout
}}
>
Select Pro Plan
@@ -287,16 +289,153 @@ This will handle any plan changes scenario (upgrades, downgrades, one-time topup
Upgrades will happen immediately, and downgrades will be scheduled for the next billing cycle.
<Note>
**Default behavior:** For new subscriptions, the hook redirects to **Stripe Checkout**. For plan changes (upgrades/downgrades), it redirects to **Autumn Checkout** where customers can review prorations before confirming.
The **`redirectMode: "always"`** flag will always return a payment URL.
**Build your own UI:** If you want to handle the checkout for plan changes yourself:
1. Call [previewAttach](/api-reference/billing/previewAttach) to get line items and pricing details
2. Call `attach` with `redirectMode: "if_required"` — this charges the customer automatically if they have a payment method on file
New purchases redirect to Stripe Checkout to enter payment details, and subsequent charges redirect to an Autumn hosted, one-click confirmation page.
You can build your own billing confirmation flows by using the `previewAttach` function.
</Note>
</Step>
</Steps>
</View>
{/* SERVER SDK DOCS */}
<View title="Server SDK" icon="server">
<Steps>
<Step>
### Create an Autumn customer
When 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.
<CodeGroup dropdown>
```typescript
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const customer = await autumn.customers.getOrCreate({
customerId: "user_or_org_id_from_auth",
name: "John Doe",
email: "john@example.com",
});
```
```python
import asyncio
from autumn_sdk import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
customer = await autumn.customers.get_or_create(
customer_id="user_or_org_id_from_auth",
name="John Doe",
email="john@example.com",
)
asyncio.run(main())
```
```bash cURL
curl --request POST \
--url https://api.useautumn.com/v1/customers \
--header 'Authorization: Bearer am_sk_42424242' \
--header 'Content-Type: application/json' \
--data '{
"customer_id": "user_or_org_id_from_auth",
"name": "John Doe",
"email": "john@example.com"
}'
```
</CodeGroup>
<Check>
Autumn's customer ID is the same as your internal user or org ID generated from your auth provider. No need to store any extra IDs.
</Check>
In the Autumn dashboard, you will see your user under the [customers](https://app.useautumn.com/customers) page.
</Step>
<Step>
### Stripe Payment Flow
Call `billing.attach` to attach the <Badge color="blue">Pro</Badge> plan to the customer. Redirect the customer to the returned `paymentUrl` to complete payment or confirm the plan change.
<CodeGroup dropdown>
```typescript
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242'
});
const response = await autumn.billing.attach({
customerId: "user_or_org_id_from_auth",
planId: "pro",
redirectMode: "always",
});
// Redirect customer to complete payment or confirm plan change
redirect(response.paymentUrl);
```
```python
import asyncio
from autumn_sdk import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
response = await autumn.billing.attach(
customer_id='user_or_org_id_from_auth',
plan_id='pro',
redirect_mode='always',
)
asyncio.run(main())
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/attach' \
-H 'Authorization: Bearer am_sk_42424242' \
-H 'Content-Type: application/json' \
-d '{
"customer_id": "user_or_org_id_from_auth",
"plan_id": "pro",
"redirect_mode": "always"
}'
```
</CodeGroup>
<Note>
Use Stripe's test card `4242 4242 4242 4242` to make a purchase in sandbox. You can enter any Expiry and CVV.
</Note>
This can be used for any plan changes scenario (upgrades, downgrades, one-time topups, renewals, etc).
Upgrades will happen immediately, and downgrades will be scheduled for the next billing cycle.
<Note>
**`redirectMode: "always"`** (shown above) always returns a `paymentUrl`, redirecting the customer to review and confirm the plan change — whether it's a new subscription, upgrade, or downgrade.
**Build your own UI:** If you want to handle checkout yourself:
1. Call [previewAttach](/api-reference/billing/previewAttach) to get line items and pricing details
2. Call `attach` with `redirectMode: "if_required"` — this only returns a `paymentUrl` when a payment action is needed (e.g., no card on file), otherwise charges automatically
</Note>
</Step>
</Steps>
</View>
**Next: Track and limit usage**
@@ -308,4 +447,3 @@ Now that the plan is enabled and you've handled payments, you can now make sure
>
Enforce usage limits and feature permissions using Autumn's `check` and `track` functions
</Card>

View File

@@ -1,209 +0,0 @@
---
title: "Using SDK"
description: "Implement your app's payments and pricing model using the Autumn's server-side SDK"
---
import CreatePlans from '/snippets/create-plans.mdx';
In this example we'll create the pricing for a premium AI chatbot. We're going to have:
- A <Badge color="green">Free</Badge> plan that gives users 5 chat messages per month for free
- A <Badge color="blue">Pro</Badge> plan that gives users 100 chat messages per month for $20 per month.
<Steps>
<Step>
### Create your pricing plans
Create a plan for each tier that your app offers. In our example we'll create a "Free" and "Pro" plan.
<CreatePlans />
</Step>
<Step>
### Installation
[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.
```bash .env
AUTUMN_SECRET_KEY=am_sk_test_42424242...
```
<CodeGroup>
```bash bun
bun add autumn-js
```
```bash npm
npm install autumn-js
```
```bash pnpm
pnpm add autumn-js
```
```bash yarn
yarn add autumn-js
```
```bash pip
pip install autumn-sdk
```
</CodeGroup>
</Step>
<Step>
### Create an Autumn customer
When 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.
<CodeGroup dropdown>
```typescript
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const customer = await autumn.customers.getOrCreate({
customerId: "user_or_org_id_from_auth",
name: "John Doe",
email: "john@example.com",
});
```
```python
import asyncio
from autumn_sdk import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
customer = await autumn.customers.get_or_create(
customer_id="user_or_org_id_from_auth",
name="John Doe",
email="john@example.com",
)
asyncio.run(main())
```
```bash cURL
curl --request POST \
--url https://api.useautumn.com/v1/customers \
--header 'Authorization: Bearer am_sk_42424242' \
--header 'Content-Type: application/json' \
--data '{
"customer_id": "user_or_org_id_from_auth",
"name": "John Doe",
"email": "john@example.com"
}'
```
</CodeGroup>
<Check>
Autumn's customer ID is the same as your internal user or org ID generated from your auth provider, so you can use the same ID for everything.
</Check>
In the Autumn dashboard, you will see your user under the [customers](https://app.useautumn.com/customers) page.
</Step>
<Step>
### Stripe Payment Flow
Call `billing.attach` to attach the <Badge color="blue">Pro</Badge> plan to the customer. Redirect the customer to the returned `paymentUrl` to complete payment or confirm the plan change.
<CodeGroup dropdown>
```typescript
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242'
});
const response = await autumn.billing.attach({
customerId: "user_or_org_id_from_auth",
planId: "pro",
});
// Redirect customer to complete payment or confirm plan change
redirect(response.paymentUrl);
```
```python
import asyncio
from autumn_sdk import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
response = await autumn.billing.attach(
customer_id='user_or_org_id_from_auth',
plan_id='pro'
)
asyncio.run(main())
```
```bash cURL
curl -X POST 'https://api.useautumn.com/v1/attach' \
-H 'Authorization: Bearer am_sk_42424242' \
-H 'Content-Type: application/json' \
-d '{
"customer_id": "user_or_org_id_from_auth",
"plan_id": "pro"
}'
```
</CodeGroup>
<Note>
Use Stripe's test card `4242 4242 4242 4242` to make a purchase in sandbox. You can enter any Expiry and CVV.
</Note>
This can be used for any plan changes scenario (upgrades, downgrades, one-time topups, renewals, etc).
Upgrades will happen immediately, and downgrades will be scheduled for the next billing cycle.
<Note>
**Default behavior:** For new subscriptions, `paymentUrl` points to **Stripe Checkout**. For plan changes (upgrades/downgrades), it points to **Autumn Checkout** where customers can review prorations before confirming.
**Build your own UI:** If you want to handle the checkout for plan changes yourself:
1. Call [previewAttach](/api-reference/billing/previewAttach) to get line items and pricing details
2. Call `attach` with `redirectMode: "if_required"` — this charges the customer automatically if they have a payment method on file
</Note>
</Step>
</Steps>
**Next: Track and limit usage**
Now that the plan is enabled and you've handled payments, you can now make sure that customers have the correct access and limits based on their plan.
<Card
title="Track and limit usage"
href='/documentation/getting-started/gating'
>
Enforce usage limits and feature permissions using Autumn's `check` and `track` functions
</Card>

View File

@@ -0,0 +1,619 @@
---
title: Entity-level balances
description: Grant usage limits per entity, such as 50 credits per user per month
---
Entities are a resource that lives under a parent customer, that can have it's own plans and feature balances.
Entity-level balances let you set usage limits that apply to each entity (like users, workspaces, or projects) individually. Instead of a single shared pool, each entity gets their own balance.
This is useful when you want to ensure fair usage across team members or isolate resource consumption per workspace.
## Example case
We have an AI meeting notes product with team-based pricing:
- **Team plan**: \$30 per seat per month
- **Each seat gets**: 50 meeting summaries per month
If a team has 8 users, they pay \$30 \times 8 = \$240/month, and each user gets their own 50 summaries.
## Configure Pricing
<Steps>
<Step>
#### Create Features
Create two features:
1. **Seats** - A `metered` `non-consumable` feature to track team members
2. **Meeting Summaries** - A `metered` `consumable` feature for the number of meeting summaries generated
</Step>
<Step>
#### Create Team Plan
Create a Team plan with:
1. A **\$30/month base price**
2. **Seats**: 1 included, then \$30/seat for additional (usage-based)
3. **Meeting Summaries**: 50 per month, linked to the Seats feature as a **per-entity feature**, under "Advanced"
The key configuration is setting the meeting summaries feature to point to "seats". When the entity is created, this creates a per-entity balance where each seat gets 50 summaries.
<Frame>
<img src="/assets/guides/entity-balances/team-light.png" className="block dark:hidden" />
<img src="/assets/guides/entity-balances/team-dark.png" className="hidden dark:block" />
</Frame>
</Step>
</Steps>
## Implementation
<Steps>
<Step>
#### Create an Autumn Customer
When an organization signs up, create an Autumn customer.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
const App = () => {
const { customer } = useCustomer();
console.log("Autumn customer:", customer);
return <h1>Welcome, {customer?.name || "user"}!</h1>;
};
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const { data, error } = await autumn.customers.create({
id: "org_123",
name: "Acme Corp",
email: "admin@acme.com",
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
customer = await autumn.customers.create(
id="org_123",
name="Acme Corp",
email="admin@acme.com",
)
asyncio.run(main())
```
```bash cURL
curl --request POST \
--url https://api.useautumn.com/v1/customers \
--header 'Authorization: Bearer am_sk_42424242' \
--header 'Content-Type: application/json' \
--data '{
"id": "org_123",
"name": "Acme Corp",
"email": "admin@acme.com"
}'
```
</CodeGroup>
</Step>
<Step>
#### Create Initial Entity
Create an entity for the admin user who is signing up. Since no plan is attached yet, this just registers the entity — no balance is granted yet. This should be done server-side for security.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Create entity for the initial admin user
await autumn.entities.create("org_123", {
id: "user_admin",
name: "Admin User",
feature_id: "seats",
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Create entity for the initial admin user
await autumn.features.create_entity(
customer_id="org_123",
id="user_admin",
name="Admin User",
feature_id="seats",
)
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/customers/org_123/entities" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"id": "user_admin",
"name": "Admin User",
"feature_id": "seats"
}'
```
</CodeGroup>
<Note>
Since no plan is attached yet, this entity exists but has no balances. Once the Team plan is attached, this entity will automatically receive its 50 meeting summaries.
</Note>
</Step>
<Step>
#### Attach the Team Plan
When the customer upgrades to Team, attach the plan. The checkout URL will show the \$30 base price.
<CodeGroup>
```jsx React
import { useCustomer, CheckoutDialog } from "autumn-js/react";
export default function UpgradeButton() {
const { checkout } = useCustomer();
return (
<button
onClick={async () => {
await checkout({
productId: "team",
dialog: CheckoutDialog,
});
}}
>
Upgrade to Team
</button>
);
}
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const { data } = await autumn.checkout({
customer_id: "org_123",
product_id: "team",
});
if (data.url) {
// Redirect to Stripe checkout
}
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
response = await autumn.checkout(
customer_id="org_123",
product_id="team",
)
if response.url:
# Redirect to Stripe checkout
pass
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/checkout" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"product_id": "team"
}'
```
</CodeGroup>
</Step>
<Step>
#### Create Entities (Seats)
When team members are added, create entities. This tracks seat usage and initializes their per-entity balance.
This will also bill the customer a prorated amount for the seat price. You can configure this proration behavior (full vs prorated, immediately vs next cycle) in the "Advanced" section of the plan feature when editing the plan.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Create entities for team members
await autumn.entities.create("org_123", [
{ id: "user_alice", name: "Alice Smith", feature_id: "seats" },
{ id: "user_bob", name: "Bob Jones", feature_id: "seats" },
{ id: "user_charlie", name: "Charlie Brown", feature_id: "seats" },
]);
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Create entities for team members
await autumn.features.create_entity(
customer_id="org_123",
id="user_alice",
name="Alice Smith",
feature_id="seats",
)
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/customers/org_123/entities" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"id": "user_alice",
"name": "Alice Smith",
"feature_id": "seats"
}'
```
</CodeGroup>
<Note>
Creating an entity automatically increments the seat count. If you create 4 entities, your seat usage will be 4.
After you create an entity, navigate to the Autumn customer page, and you will see it created at the top of the page.
</Note>
</Step>
<Step>
#### Check Access Per Entity
Before generating a meeting summary, check if that specific user has remaining balance.
<CodeGroup>
```tsx React
import { useEntity } from "autumn-js/react";
function MeetingSummaryButton({ meetingId }: { meetingId: string }) {
// Pass the current user's entity ID
const { allowed, check } = useEntity("user_alice");
const handleSummarize = async () => {
const { data } = await check({ featureId: "meeting_summaries" });
if (!data?.allowed) {
alert("You've used all your meeting summaries this month");
return;
}
// Generate the summary
await generateSummary(meetingId);
};
return <button onClick={handleSummarize}>Summarize Meeting</button>;
}
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Check Alice's individual balance
const { data } = await autumn.check({
customer_id: "org_123",
feature_id: "meeting_summaries",
entity_id: "user_alice",
});
if (!data.allowed) {
console.log("Alice has used all her meeting summaries");
} else {
console.log(`Alice has ${data.balance} summaries remaining`);
}
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Check Alice's individual balance
response = await autumn.check(
customer_id="org_123",
feature_id="meeting_summaries",
entity_id="user_alice",
)
if not response.allowed:
print("Alice has used all her meeting summaries")
else:
print(f"Alice has {response.balance} summaries remaining")
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/check" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "meeting_summaries",
"entity_id": "user_alice"
}'
```
</CodeGroup>
<Expandable title="check response (entity-level)">
```json
{
"allowed": true,
"customer_id": "org_123",
"feature_id": "meeting_summaries",
"entity_id": "user_alice",
"balance": 47,
"usage": 3,
"included_usage": 50,
"unlimited": false
}
```
</Expandable>
</Step>
<Step>
#### Track Usage Per Entity
When a user generates a summary, track the usage against their entity.
<CodeGroup>
```tsx React
import { useEntity } from "autumn-js/react";
const { track } = useEntity("user_alice");
// After generating a meeting summary
await track({
featureId: "meeting_summaries",
value: 1,
});
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Track usage for Alice
await autumn.track({
customer_id: "org_123",
feature_id: "meeting_summaries",
entity_id: "user_alice",
value: 1,
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Track usage for Alice
await autumn.track(
customer_id="org_123",
feature_id="meeting_summaries",
entity_id="user_alice",
value=1,
)
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/track" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "meeting_summaries",
"entity_id": "user_alice",
"value": 1
}'
```
</CodeGroup>
</Step>
<Step>
#### Check Customer-level Balance (Optional)
You can also check the total balance across all entities, useful for admin dashboards.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Check total balance across all users (omit entity_id)
const { data } = await autumn.check({
customer_id: "org_123",
feature_id: "meeting_summaries",
});
// With 3 users at 50 each = 150 total
console.log(`Team has ${data.balance} total summaries remaining`);
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Check total balance across all users
response = await autumn.check(
customer_id="org_123",
feature_id="meeting_summaries",
)
print(f"Team has {response.balance} total summaries remaining")
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/check" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "meeting_summaries"
}'
```
</CodeGroup>
<Expandable title="check response (customer-level)">
```json
{
"allowed": true,
"customer_id": "org_123",
"feature_id": "meeting_summaries",
"balance": 141,
"usage": 9,
"included_usage": 150,
"unlimited": false
}
```
The total is the sum of all entity balances (3 users × 50 = 150 included).
</Expandable>
</Step>
<Step>
#### Remove Entities
When a team member leaves, delete their entity. This decrements seat usage and removes their balance.
It will also create a pro-rated refund for the seat price. You can configure this proration behavior in the "Advanced" section of the plan feature when editing the plan.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Remove Bob from the team
await autumn.entities.delete("org_123", "user_bob");
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Remove Bob from the team
await autumn.features.delete_entity("org_123", "user_bob")
asyncio.run(main())
```
```bash cURL
curl -X DELETE "https://api.useautumn.com/v1/customers/org_123/entities/user_bob" \
-H "Authorization: Bearer am_sk_42424242"
```
</CodeGroup>
<Note>
Deleting an entity automatically decrements the seat count. The customer's total meeting summary balance will also decrease accordingly.
</Note>
</Step>
</Steps>
## Summary
| Level | Check/Track With | Use Case |
|-------|------------------|----------|
| **Entity-level** | `entity_id: "user_alice"` | Individual user limits, fair usage |
| **Customer-level** | No `entity_id` | Admin dashboards, total consumption |
Entity-level balances are ideal when you want to:
- Ensure fair usage across team members
- Isolate consumption per workspace or project
- Bill per-entity while providing entity-specific limits

View File

@@ -0,0 +1,396 @@
---
title: Pay-as-you-go overages
description: Let free plan users optionally add a card to pay for usage overages instead of getting blocked
---
Free plan users can optionally add a payment method so that if they exceed their included usage, they're billed for the overage rather than blocked. This is done by having two plans: a Free plan (no overages) and a Pay-as-you-go plan (with overage pricing).
This is useful when you want to:
- Avoid blocking engaged free users who exceed limits
- Convert free users to paying customers through natural usage growth
- Offer a "soft limit" experience without requiring upfront payment
## Example case
We have a product with the following pricing:
- **Free plan**: 1,000 notifications per month included, blocked when exceeded
- **Pay-as-you-go plan**: 1,000 notifications per month included, $1 per 1,000 notifications beyond the included amount
If a free user exceeds 1,000 notifications, they get blocked.
If they've switched to Pay-as-you-go (by adding a card), they're charged $1 per 1,000 notifications at the end of the billing period.
## Configure Pricing
<Steps>
<Step>
#### Create Feature
Create a `metered` `consumable` feature called "notifications".
</Step>
<Step>
#### Create Free Plan
Create a free plan with 1,000 notifications included per month. Set `auto-enable` so new customers automatically start on this plan.
</Step>
<Step>
#### Create Pay-as-you-go Plan
Create a Pay-as-you-go plan with the same 1,000 notifications included, but with overage pricing:
- **Grant amount**: 1,000 notifications
- **Price**: $1 per 1,000 notifications per month
- **Billing method**: Usage-based
<Warning>
In advanced, toggle **off** the "Reset usage when enabled" flag. This ensures that when a user switches from Free to Pay-as-you-go, their existing usage carries over instead of resetting to 0.
</Warning>
</Step>
</Steps>
## Implementation
<Steps>
<Step>
#### Create an Autumn Customer
When your user signs up, create an Autumn customer. This will automatically assign them the Free plan with 1,000 included notifications.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
const App = () => {
const { customer } = useCustomer();
console.log("Autumn customer:", customer);
return <h1>Welcome, {customer?.name || "user"}!</h1>;
};
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const { data, error } = await autumn.customers.create({
id: "user_123",
name: "Jane Doe",
email: "jane@example.com",
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
customer = await autumn.customers.create(
id="user_123",
name="Jane Doe",
email="jane@example.com",
)
asyncio.run(main())
```
```bash cURL
curl --request POST \
--url https://api.useautumn.com/customers \
--header 'Authorization: Bearer am_sk_42424242' \
--header 'Content-Type: application/json' \
--data '{
"id": "user_123",
"name": "Jane Doe",
"email": "jane@example.com"
}'
```
</CodeGroup>
</Step>
<Step>
#### Check Access
Before sending a notification, check if the customer has remaining capacity.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
export function SendNotification() {
const { check } = useCustomer();
const handleSendNotification = async () => {
const { data } = await check({ featureId: "notifications" });
if (!data?.allowed) {
// User is over limit on Free plan
// Prompt them to switch to Pay-as-you-go
alert("You've run out of notifications. Add a payment method to continue.");
return;
}
// Proceed with sending notification
};
}
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const { data } = await autumn.check({
customer_id: "user_123",
feature_id: "notifications",
});
if (!data.allowed) {
console.log("User is over limit on Free plan");
// Prompt them to switch to Pay-as-you-go
}
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
response = await autumn.check(
customer_id="user_123",
feature_id="notifications",
)
if not response.allowed:
print("User is over limit on Free plan")
# Prompt them to switch to Pay-as-you-go
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/check" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "user_123",
"feature_id": "notifications"
}'
```
</CodeGroup>
<Expandable title="check response (Free plan user)">
```json
{
"allowed": true,
"customer_id": "user_123",
"feature_id": "notifications",
"balance": 870,
"usage": 130,
"included_usage": 1000,
"unlimited": false,
"overage_allowed": false
}
```
When `balance` reaches 0 and `overage_allowed` is `false`, the user will be blocked.
</Expandable>
</Step>
<Step>
#### Track Usage
After sending a notification, track the usage.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
await autumn.track({
customer_id: "user_123",
feature_id: "notifications",
value: 1,
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
await autumn.track(
customer_id="user_123",
feature_id="notifications",
value=1,
)
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/track" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "user_123",
"feature_id": "notifications",
"value": 1
}'
```
</CodeGroup>
</Step>
<Step>
#### Switch to Pay-as-you-go
When the user is approaching or has exceeded their limit, prompt them to switch to the Pay-as-you-go plan. Use `attach` with `setup_payment: true` to collect their card without charging upfront.
<Tip>
You can retrieve the user's notification balance from the `check` or `customer` method, and use this to conditionally prompt them to add a payment method.
</Tip>
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
export default function EnableOveragesButton() {
const { attach } = useCustomer();
return (
<button
onClick={async () => {
const { data } = await attach({
productId: "pay_as_you_go",
setupPayment: true,
});
if (data?.url) {
window.location.href = data.url;
}
}}
>
Enable Pay-as-you-go
</button>
);
}
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const { data } = await autumn.attach({
customer_id: "user_123",
product_id: "pay_as_you_go",
setup_payment: true,
success_url: "https://your-app.com/settings",
});
if (data.url) {
// Redirect user to Stripe setup page
}
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
response = await autumn.attach(
customer_id="user_123",
product_id="pay_as_you_go",
setup_payment=True,
success_url="https://your-app.com/settings",
)
if response.url:
# Redirect user to Stripe setup page
pass
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/attach" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "user_123",
"product_id": "pay_as_you_go",
"setup_payment": true,
"success_url": "https://your-app.com/settings"
}'
```
</CodeGroup>
<Note>
Once the user completes the setup, they'll be switched from the Free plan to the Pay-as-you-go plan. Because "Reset usage when enabled" is off, their existing usage carries over. Any usage beyond 1,000 notifications will be billed at the end of the billing period.
</Note>
</Step>
<Step>
#### Overages are Now Enabled
After switching to Pay-as-you-go, the user's `check` response will show `overage_allowed: true`. They can continue using the feature beyond their included limit.
<Expandable title="check response (Pay-as-you-go user)">
```json
{
"allowed": true,
"customer_id": "user_123",
"feature_id": "notifications",
"balance": -200,
"usage": 1200,
"included_usage": 1000,
"unlimited": false,
"overage_allowed": true
}
```
The user has sent 1,200 notifications (200 over the limit). They will be billed $0.20 at the end of the billing period.
</Expandable>
</Step>
</Steps>
## Summary
| Plan | Over Limit | Result |
|------|------------|--------|
| Free | No | ✅ Allowed |
| Free | Yes | ❌ Blocked |
| Pay-as-you-go | No | ✅ Allowed |
| Pay-as-you-go | Yes | ✅ Allowed, billed at end of period |

View File

@@ -0,0 +1,772 @@
---
title: Per-seat pricing
description: Implement per-seat pricing with free included seats and paid additional seats
---
Per-seat pricing is a common model for B2B SaaS products where customers pay based on the number of users or team members using the product. This guide covers how to implement per-seat pricing with free included seats and paid additional seats.
## Example case
We have a B2B collaboration tool with the following pricing:
- **Free tier**: 3 seats included for free
- **Pro tier**: \$20/month base price with 5 seats included, plus \$10/seat/month for additional seats
For additional seats, there are two ways to configure pricing in Autumn:
| Billing Model | Description |
|-----------------|---------------------------------------------------------------------------------------------|
| **Prepaid** | Customer commits to a fixed number of seats upfront and pays immediately |
| **Usage-based** | Customer pays for actual seats used at the end of each billing cycle |
## Configure Pricing
<Steps>
<Step>
#### Create Feature
Create a `metered` `non-consumable` feature called "seats". Non-consumable features are for persistent resources like seats, GB storage, or workspaces.
</Step>
<Step>
#### Create Free Plan
Create a free plan with 3 included seats. Set `auto-enable` so new customers automatically get this plan.
<Frame>
<img src="/assets/guides/per-seat/free-light.png" className="block dark:hidden" />
<img src="/assets/guides/per-seat/free-dark.png" className="hidden dark:block" />
</Frame>
</Step>
<Step>
#### Create Pro Plan
Create a Pro plan with a $20/month base price and 5 included seats.
For additional seats beyond the included 5, add a priced feature at $10/seat/month. Choose your billing model:
- **Prepaid**: Customer selects quantity upfront, charged immediately
- **Pay per use**: Customer is billed for actual usage at end of billing cycle
<Frame>
<img src="/assets/guides/per-seat/pro-light.png" className="block dark:hidden" />
<img src="/assets/guides/per-seat/pro-dark.png" className="hidden dark:block" />
</Frame>
</Step>
</Steps>
<Accordion title="Configure Proration (optional)">
For non-consumable features like seats, you can configure how price changes are handled mid-billing cycle.
**On Increase** (adding seats):
| Option | Behavior |
|--------|----------|
| `prorate_immediately` | Charge prorated amount now (default) |
| `bill_immediately` | Charge full amount now |
| `prorate_next_cycle` | Add prorated amount to next invoice |
| `bill_next_cycle` | Add full amount to next invoice |
**On Decrease** (removing seats):
| Option | Behavior |
|--------|----------|
| `prorate_immediately` | Credit prorated amount now (default) |
| `prorate_next_cycle` | Credit on next invoice |
| `no_prorations` | No refund or credit |
You can configure these in the "Advanced" section when adding the priced feature to your plan.
</Accordion>
## Implementation
<Steps>
<Step>
#### Create an Autumn Customer
When your user signs up, create an Autumn customer. This will automatically assign them the Free plan with 3 included seats.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
const App = () => {
const { customer } = useCustomer();
console.log("Autumn customer:", customer);
return <h1>Welcome, {customer?.name || "user"}!</h1>;
};
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const { data, error } = await autumn.customers.create({
id: "org_123",
name: "Acme Corp",
email: "admin@acme.com",
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
customer = await autumn.customers.create(
id="org_123",
name="Acme Corp",
email="admin@acme.com",
)
asyncio.run(main())
```
```bash cURL
curl --request POST \
--url https://api.useautumn.com/customers \
--header 'Authorization: Bearer am_sk_42424242' \
--header 'Content-Type: application/json' \
--data '{
"id": "org_123",
"name": "Acme Corp",
"email": "admin@acme.com"
}'
```
</CodeGroup>
</Step>
<Step>
#### Check Seat Access
Before adding a new team member, check if the customer has available seats.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
export function AddTeamMember() {
const { check } = useCustomer();
const handleAddMember = async () => {
const { data } = check({ featureId: "seats" });
if (!data?.allowed) {
// Prompt upgrade or purchase more seats
alert("No seats available. Please upgrade your plan.");
return;
}
// Proceed with adding team member
};
}
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const { data } = await autumn.check({
customer_id: "org_123",
feature_id: "seats",
});
if (!data.allowed) {
console.log("No seats available");
// Prompt upgrade or purchase more seats
}
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
response = await autumn.check(
customer_id="org_123",
feature_id="seats",
)
if not response.allowed:
print("No seats available")
# Prompt upgrade or purchase more seats
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/check" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "seats"
}'
```
</CodeGroup>
<Expandable title="check response">
```json
{
"allowed": true,
"customer_id": "org_123",
"feature_id": "seats",
"balance": 1,
"usage": 2,
"included_usage": 3,
"unlimited": false,
"overage_allowed": false
}
```
</Expandable>
</Step>
<Step>
#### Track Seat Usage
When team members are added, track seat usage. You can use the `track` endpoint to increment usage, or the `usage` endpoint to set the total directly.
Remember to track the usage for the initial user as well, after customer creation.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Increment by 1 when a seat is added
await autumn.track({
customer_id: "org_123",
feature_id: "seats",
value: 1,
});
// Or set the total directly
await autumn.usage({
customer_id: "org_123",
feature_id: "seats",
value: 2, // Total seats now in use
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
# Increment by 1 when a seat is added
await autumn.track(
customer_id="org_123",
feature_id="seats",
value=1,
)
# Or set the total directly
await autumn.features.set_usage(
customer_id="org_123",
feature_id="seats",
value=2, # Total seats now in use
)
asyncio.run(main())
```
```bash cURL
# Increment by 1
curl -X POST "https://api.useautumn.com/v1/track" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "seats",
"value": 1
}'
# Or set total directly
curl -X POST "https://api.useautumn.com/v1/usage" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "seats",
"value": 2
}'
```
</CodeGroup>
<Note>
Seat usage tracked on the Free tier will **carry over** when the customer upgrades to Pro. If a customer is using 2 seats on Free and upgrades to Pro, those 2 seats remain in use and count against Pro's included seats.
</Note>
</Step>
<Step>
#### Upgrade to Pro
When the customer upgrades to Pro, use the `checkout` endpoint. If they need additional paid seats beyond the 5 included, pass the quantity in `options`.
<Warning>
The `quantity` in options represents the **additional paid seats only**, not total seats. Pro includes 5 seats, so if the customer wants 8 total seats, pass `quantity: 3`.
</Warning>
<CodeGroup>
```jsx React
import { useCustomer, CheckoutDialog } from "autumn-js/react";
export default function UpgradeButton() {
const { checkout } = useCustomer();
return (
<button
onClick={async () => {
// Customer wants 8 total seats (5 included + 3 paid)
await checkout({
productId: "pro",
dialog: CheckoutDialog,
options: [{
featureId: "seats",
quantity: 3, // 3 paid seats beyond the 5 included
}],
});
}}
>
Upgrade to Pro
</button>
);
}
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Customer wants 8 total seats (5 included + 3 paid)
const { data } = await autumn.checkout({
customer_id: "org_123",
product_id: "pro",
options: [{
feature_id: "seats",
quantity: 3, // 3 paid seats beyond the 5 included
}],
});
if (data.url) {
// Redirect to Stripe checkout
}
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Customer wants 8 total seats (5 included + 3 paid)
response = await autumn.checkout(
customer_id="org_123",
product_id="pro",
options=[{
"feature_id": "seats",
"quantity": 3, # 3 paid seats beyond the 5 included
}],
)
if response.url:
# Redirect to Stripe checkout
pass
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/checkout" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"product_id": "pro",
"options": [{
"feature_id": "seats",
"quantity": 3
}]
}'
```
</CodeGroup>
<Tip>
If the customer only needs the 5 included seats, pass `quantity: 0` or omit the options entirely.
</Tip>
</Step>
<Step>
#### Update Seat Quantity
How you update seat quantity depends on your billing model:
<Tabs>
<Tab title="Prepaid">
For prepaid seats, use the `attach` endpoint with updated `options` to change the seat quantity. This will handle proration based on your configuration.
<CodeGroup>
```jsx React
import { useCustomer, CheckoutDialog } from "autumn-js/react";
export default function AddSeatsButton() {
const { checkout } = useCustomer();
return (
<button
onClick={async () => {
// Increase from 3 paid seats to 5 paid seats
// (8 total → 10 total)
await checkout({
productId: "pro",
dialog: CheckoutDialog,
options: [{
featureId: "seats",
quantity: 5, // New paid seat count
}],
});
}}
>
Add More Seats
</button>
);
}
```
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Increase from 3 paid seats to 5 paid seats (8 total → 10 total)
const { data } = await autumn.attach({
customer_id: "org_123",
product_id: "pro",
options: [{
feature_id: "seats",
quantity: 5, // New paid seat count
}],
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Increase from 3 paid seats to 5 paid seats (8 total → 10 total)
response = await autumn.attach(
customer_id="org_123",
product_id="pro",
options=[{
"feature_id": "seats",
"quantity": 5, # New paid seat count
}],
)
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/attach" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"product_id": "pro",
"options": [{
"feature_id": "seats",
"quantity": 5
}]
}'
```
</CodeGroup>
<Info>
**Prepaid quantity math:**
- Pro includes 5 seats
- Current: `quantity: 3` → 8 total seats (5 + 3)
- Updated: `quantity: 5` → 10 total seats (5 + 5)
- Customer is charged prorated amount for 2 additional seats
</Info>
</Tab>
<Tab title="Usage-based">
For usage-based seats, simply track the actual seat usage. Billing happens automatically at the end of each billing cycle based on usage.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// When a new team member is added
await autumn.track({
customer_id: "org_123",
feature_id: "seats",
value: 1,
});
// Or set the exact count
await autumn.usage({
customer_id: "org_123",
feature_id: "seats",
value: 10, // Now using 10 seats total
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
# When a new team member is added
await autumn.track(
customer_id="org_123",
feature_id="seats",
value=1,
)
# Or set the exact count
await autumn.features.set_usage(
customer_id="org_123",
feature_id="seats",
value=10, # Now using 10 seats total
)
asyncio.run(main())
```
```bash cURL
# Increment when adding a seat
curl -X POST "https://api.useautumn.com/v1/track" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "seats",
"value": 1
}'
# Or set the exact count
curl -X POST "https://api.useautumn.com/v1/usage" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "seats",
"value": 10
}'
```
</CodeGroup>
<Info>
With usage-based billing, if the customer uses 10 seats and Pro includes 5, they'll be charged for 5 additional seats ($50) at the end of the billing cycle.
</Info>
</Tab>
</Tabs>
</Step>
<Step>
#### Decrease Seat Quantity
When team members leave, you'll want to decrease the seat count.
<Tabs>
<Tab title="Prepaid">
Update the `options` with a lower quantity. Depending on your proration configuration, the customer may receive a credit.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Decrease from 5 paid seats to 2 paid seats (10 total → 7 total)
const { data } = await autumn.attach({
customer_id: "org_123",
product_id: "pro",
options: [{
feature_id: "seats",
quantity: 2, // New paid seat count
}],
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
# Decrease from 5 paid seats to 2 paid seats (10 total → 7 total)
response = await autumn.attach(
customer_id="org_123",
product_id="pro",
options=[{
"feature_id": "seats",
"quantity": 2, # New paid seat count
}],
)
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/attach" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"product_id": "pro",
"options": [{
"feature_id": "seats",
"quantity": 2
}]
}'
```
</CodeGroup>
</Tab>
<Tab title="Usage-based">
Track the removal or set the new total directly.
<CodeGroup>
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
// Decrement by 1 when a seat is removed
await autumn.track({
customer_id: "org_123",
feature_id: "seats",
value: -1,
});
// Or set the new total
await autumn.usage({
customer_id: "org_123",
feature_id: "seats",
value: 7, // Now using 7 seats
});
```
```python Python
import asyncio
from autumn import Autumn
autumn = Autumn('am_sk_42424242')
async def main():
# Decrement by 1 when a seat is removed
await autumn.track(
customer_id="org_123",
feature_id="seats",
value=-1,
)
# Or set the new total
await autumn.features.set_usage(
customer_id="org_123",
feature_id="seats",
value=7, # Now using 7 seats
)
asyncio.run(main())
```
```bash cURL
# Decrement by 1
curl -X POST "https://api.useautumn.com/v1/track" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "seats",
"value": -1
}'
# Or set new total
curl -X POST "https://api.useautumn.com/v1/usage" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "org_123",
"feature_id": "seats",
"value": 7
}'
```
</CodeGroup>
</Tab>
</Tabs>
</Step>
</Steps>
## Summary
| Billing Model | Add Seats | Remove Seats | When Billed |
|---------------|-----------|--------------|-------------|
| **Prepaid** | `attach` with new `quantity` | `attach` with lower `quantity` | Immediately (prorated) |
| **Usage-based** | `track` or `usage` | `track` (negative) or `usage` | End of billing cycle |

View File

@@ -1,11 +1,11 @@
---
title: Prepaid top-ups
description: Let customers purchase prepaid packages and top-ups.
title: One-time top ups
description: Let customers purchase a prepaid package to top up their balance when it falls low.
---
If a user hits a usage limit you granted them, they may be willing to purchase a top-up.
These are typically one-time purchases (or less commonly, recurring add-ons) that grant a fixed usage of a feature.
These are typically one-time purchases (but can also be recurring add-ons) that grant a fixed usage of a feature.
This gives users full spend control and allows your business to be paid upfront. For these reasons, it tends to be a more popular alternative to usage-based pricing -- eg, OpenAI uses this model for their API.
@@ -13,8 +13,8 @@ This gives users full spend control and allows your business to be paid upfront.
In this example, we have an AI chatbot that offers:
- 10 premium messages for free
- An option for customers to top-up premium messages in packages of $10 per 100 messages.
- 10 messages per month for free
- An option for customers to top-up messages in packages of $10 per 100 messages.
## Configure Pricing
@@ -22,7 +22,7 @@ In this example, we have an AI chatbot that offers:
<Step>
#### Create Features
Create a `metered` `consumable` feature for our premium messages, so we can track its balance.
Create a `metered` `consumable` feature for our messages, so we can track their balance.
<Frame>
<img src="/assets/guides/prepaid/features-light.png" className="block dark:hidden" />
@@ -33,7 +33,8 @@ Create a `metered` `consumable` feature for our premium messages, so we can trac
<Step>
#### Create Free and Top-up Plans
Create our free plan, and assign 10 premium messages to it. These are "one-off" credits, that will not reset periodically.
**Free Plan** <br />
Create a free plan, and assign 10 messages to it. We'll add an interval of "month", so that the user is granted 10 periodically.
<Tip>
Make sure to set the `auto-enable` flag on the free plan, so that it is automatically assigned to new customers.
@@ -43,9 +44,12 @@ Make sure to set the `auto-enable` flag on the free plan, so that it is automati
<img src="/assets/guides/prepaid/free-dark.png" className="hidden dark:block" />
</Frame>
Now we'll create our top-up plan. We'll add a price to our premium messages feature, at $10 per 100 messages. These are "one-off" purchases, with a `prepaid` billing method.
**Top up Plan** <br />
Now we'll create our top-up plan. Again, we'll assign the messages feature, but this time with a `prepaid` price of $10 per 100 messages.
`prepaid` features require a `quantity` to be sent in when a customer attaches this product, so the customer can specify how many premium messages they want to top up with.
Since these messages have interval "one-off", the messages will last forever (unlike our Free plan messages, which reset every month).
Features with a `prepaid` price require a `quantity` to be passed in when a customer purchases the plan, so the customer can specify how many messages they want to top up with.
<Frame>
<img src="/assets/guides/prepaid/topup-light.png" className="block dark:hidden" />
@@ -61,7 +65,7 @@ Now we'll create our top-up plan. We'll add a price to our premium messages feat
<Step>
#### Create an Autumn Customer
When your user signs up, create an Autumn customer. This will automatically assign them the Free plan, and grant them 10 premium messages.
When your user signs up, create an Autumn customer. This will automatically assign them the Free plan, and grant them the 10 monthly messages.
<CodeGroup>
@@ -69,7 +73,7 @@ When your user signs up, create an Autumn customer. This will automatically assi
import { useCustomer } from "autumn-js/react";
const App = () => {
const { data: customer } = useCustomer();
const { customer } = useCustomer();
console.log("Autumn customer:", customer);
@@ -77,36 +81,43 @@ const App = () => {
};
```
```typescript TypeScript
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const customer = await autumn.customers.getOrCreate({
customerId: "user_123",
const { data, error } = await autumn.customers.create({
id: "user_or_org_id_from_auth",
name: "John Yeo",
email: "john@example.com",
});
```
```python Python
from autumn_sdk import Autumn
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_test_1234")
autumn = Autumn('am_sk_42424242')
customer = await autumn.customers.get_or_create(
customer_id="user_123",
name="John Yeo",
email="john@example.com",
)
async def main():
customer = await autumn.customers.create(
id="user_or_org_id_from_auth",
name="John Yeo",
email="john@example.com",
)
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/customers" \
-H "Authorization: Bearer am_sk_test_1234" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "user_123",
curl --request POST \
--url https://api.useautumn.com/customers \
--header 'Authorization: Bearer am_sk_42424242' \
--header 'Content-Type: application/json' \
--data '{
"id": "user_or_org_id_from_auth",
"name": "John Yeo",
"email": "john@example.com"
}'
@@ -118,21 +129,21 @@ curl -X POST "https://api.useautumn.com/v1/customers" \
<Step>
#### Checking for access
Every time our user wants to send a premium message, we'll first check if they have enough premium messages remaining.
Before our user sends a message, we'll first check if they have a balance of messages remaining.
<CodeGroup>
```jsx React
```jsx React wrap
import { useCustomer } from "autumn-js/react";
export function CheckPremiumMessage() {
const { check, refetch } = useCustomer();
const handleCheckAccess = async () => {
const { allowed } = check({ featureId: "premium_messages" });
const { data } = await check({ featureId: "messages" });
if (!allowed) {
alert("You've run out of premium messages");
if (!data?.allowed) {
alert("You've run out of messages");
} else {
// proceed with sending message
await refetch();
@@ -141,43 +152,50 @@ export function CheckPremiumMessage() {
}
```
```typescript TypeScript
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const response = await autumn.customers.check({
customerId: "user_123",
featureId: "premium_messages",
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
if (!response.allowed) {
console.log("User has run out of premium messages");
const { data } = await autumn.check({
customer_id: "user_or_org_id_from_auth",
feature_id: "messages",
});
if (!data.allowed) {
console.log("User has run out of messages");
return;
}
```
```python Python
from autumn_sdk import Autumn
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_test_1234")
autumn = Autumn("am_sk_1234567890")
response = await autumn.customers.check(
customer_id="user_123",
feature_id="premium_messages",
)
async def main():
response = await autumn.check(
customer_id="user_or_org_id_from_auth",
feature_id="messages",
)
if not response.allowed:
print("User has run out of messages")
return
if not response.allowed:
print("User has run out of premium messages")
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/check" \
-H "Authorization: Bearer am_sk_test_1234" \
-H "Authorization: Bearer am_sk_1234567890" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "user_123",
"feature_id": "premium_messages"
"customer_id": "user_or_org_id_from_auth",
"feature_id": "messages"
}'
```
@@ -186,61 +204,67 @@ curl -X POST "https://api.useautumn.com/v1/check" \
<Expandable title="check response">
```json
{
"customer_id": "user_or_org_id_from_auth",
"feature_id": "messages",
"code": "feature_found",
"allowed": true,
"customerId": "user_123",
"requiredBalance": 1,
"balance": {
"featureId": "premium_messages",
"granted": 10,
"remaining": 10,
"usage": 0,
"unlimited": false,
"overageAllowed": false,
"nextResetAt": null
}
"balance": 10,
"usage": 0,
"included_usage": 10,
"unlimited": false,
"interval": null,
"interval_count": 1,
"next_reset_at": 2803498203,
"overage_allowed": false
}
```
</Expandable>
</Step>
<Step>
#### Tracking premium messages
#### Tracking messages used
Now let's implement our usage tracking and use up our premium messages. In this example, we're using 5 premium messages.
After the user has used a message, record it in Autumn to decrease their remaining balance. In this example, the user used 5 messages.
<CodeGroup>
```typescript TypeScript
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
await autumn.customers.track({
customerId: "user_123",
featureId: "premium_messages",
await autumn.track({
customer_id: "user_or_org_id_from_auth",
feature_id: "messages",
value: 5,
});
```
```python Python
from autumn_sdk import Autumn
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_test_1234")
autumn = Autumn("am_sk_42424242")
await autumn.customers.track(
customer_id="user_123",
feature_id="premium_messages",
value=5,
)
async def main():
await autumn.track(
customer_id="user_or_org_id_from_auth",
feature_id="messages",
value=5,
)
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/track" \
-H "Authorization: Bearer am_sk_test_1234" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "user_123",
"feature_id": "premium_messages",
"customer_id": "user_or_org_id_from_auth",
"feature_id": "messages",
"value": 5
}'
```
@@ -250,17 +274,9 @@ curl -X POST "https://api.useautumn.com/v1/track" \
<Expandable title="track response">
```json
{
"customerId": "user_123",
"value": 5,
"balance": {
"featureId": "premium_messages",
"granted": 10,
"remaining": 5,
"usage": 5,
"unlimited": false,
"overageAllowed": false,
"nextResetAt": null
}
"code": "event_received",
"customer_id": "user_or_org_id_from_auth",
"feature_id": "messages"
}
```
</Expandable>
@@ -269,25 +285,28 @@ curl -X POST "https://api.useautumn.com/v1/track" \
<Step>
#### Purchasing top-ups
When users run out of premium messages, they can purchase additional messages using our top-up plan. In this example, the user is purchasing 200 premium messages, which will cost them $20.
When users run out of messages, they can purchase additional messages using our top-up plan. In this example, the user is purchasing 200 premium messages, which will cost them $20.
<CodeGroup>
```jsx React
import { useCustomer } from "autumn-js/react";
import { useCustomer, CheckoutDialog } from "autumn-js/react";
export default function TopUpButton() {
const { attach } = useCustomer();
const { checkout } = useCustomer();
return (
<button
onClick={() => attach({
planId: "top_up",
featureQuantities: [{
featureId: "premium_messages",
quantity: 200,
}],
})}
onClick={async () => {
await checkout({
productId: "top_up",
dialog: CheckoutDialog,
options: [{
featureId: "messages",
quantity: 200,
}],
});
}}
>
Buy More Messages
</button>
@@ -295,48 +314,64 @@ export default function TopUpButton() {
}
```
```typescript TypeScript
```typescript Node.js
import { Autumn } from "autumn-js";
const autumn = new Autumn({ secretKey: "am_sk_test_1234" });
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const response = await autumn.billing.attach({
customerId: "user_123",
planId: "top_up",
featureQuantities: [{
featureId: "premium_messages",
const { data } = await autumn.checkout({
customer_id: "user_or_org_id_from_auth",
product_id: "top_up",
options: [{
feature_id: "messages",
quantity: 200,
}],
});
redirect(response.paymentUrl);
if (data.url) {
// Redirect user to Stripe checkout URL
} else {
// Show purchase preview to user
}
```
```python Python
from autumn_sdk import Autumn
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_test_1234")
autumn = Autumn("am_sk_42424242")
response = await autumn.billing.attach(
customer_id="user_123",
plan_id="top_up",
feature_quantities=[{
"feature_id": "premium_messages",
"quantity": 200,
}],
)
# Redirect to response.payment_url
async def main():
response = await autumn.checkout(
customer_id="user_or_org_id_from_auth",
product_id="top-up",
options=[{
"feature_id": "messages",
"quantity": 200,
}],
)
if response.url:
# Redirect user to Stripe checkout URL
pass
else:
# Show purchase preview to user
pass
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.useautumn.com/v1/attach" \
-H "Authorization: Bearer am_sk_test_1234" \
curl -X POST "https://api.useautumn.com/v1/checkout" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "user_123",
"plan_id": "top_up",
"feature_quantities": [{
"feature_id": "premium_messages",
"customer_id": "user_or_org_id_from_auth",
"product_id": "top-up",
"options": [{
"feature_id": "messages",
"quantity": 200
}]
}'
@@ -344,7 +379,221 @@ curl -X POST "https://api.useautumn.com/v1/attach" \
</CodeGroup>
Once the customer completes the payment, they will have an additional 200 premium messages available to use. You can display this to the user by getting balances from the customer endpoint.
<Expandable title="checkout response">
```json
{
"customer_id": "user_or_org_id_from_auth",
"lines": [
{
"description": "Top-up - 200 premium messages",
"amount": 20,
"item": {
"type": "feature",
"feature_id": "premium-messages",
"feature_type": "prepaid",
"feature": {
"id": "premium-messages",
"name": "Premium messages",
"type": "metered",
"display": {
"singular": "premium message",
"plural": "premium messages"
}
},
"quantity": 200,
"price": 10,
"price_per": 100,
"display": {
"primary_text": "200 premium messages",
"secondary_text": "$10 per 100 messages"
}
}
}
],
"product": {
"id": "top-up",
"name": "Top-up",
"group": null,
"env": "sandbox",
"is_add_on": false,
"is_default": false,
"archived": false,
"version": 1,
"created_at": 1766428038264,
"items": [
{
"type": "feature",
"feature_id": "premium-messages",
"feature_type": "prepaid",
"feature": {
"id": "premium-messages",
"name": "Premium messages",
"type": "metered",
"display": {
"singular": "premium message",
"plural": "premium messages"
}
},
"price": 10,
"price_per": 100,
"display": {
"primary_text": "$10 per 100 messages"
}
}
],
"free_trial": null,
"base_variant_id": null,
"scenario": "attach",
"properties": {
"is_free": false,
"is_one_off": true,
"has_trial": false,
"updateable": false
}
},
"total": 20,
"currency": "usd",
"url": "https://checkout.stripe.com/c/pay/.......",
"has_prorations": false
}
```
</Expandable>
Once the customer completes the payment, they will have an additional 200 premium messages available to use.
</Step>
<Step>
#### Displaying balances to the user
You can display to the user by getting balances from the `customer` method. Under the `customer.features` record, you'll be able to retrieve a current balance, total granted, and a `breakdown` of their monthly vs top-up messages.
<CodeGroup>
```jsx React [expandable]
import { useCustomer } from "autumn-js/react";
const { customer } = useCustomer();
const messages = customer?.features?.messages;
// Get breakdown of monthly vs prepaid balances
const monthlyBalance = messages?.breakdown?.find(
(b) => b.interval === "month"
);
const prepaidBalance = messages?.breakdown?.find(
(b) => b.interval === "lifetime"
);
// Display both balances to the user
return (
<div>
<p>Monthly: {monthlyBalance?.balance ?? 0} remaining</p>
<p>Prepaid: {prepaidBalance?.balance ?? 0} remaining</p>
<p>Total: {messages?.balance ?? 0} messages available</p>
</div>
);
```
```typescript Node.js [expandable]
import { Autumn } from "autumn-js";
const autumn = new Autumn({
secretKey: 'am_sk_42424242',
});
const { data } = await autumn.customer({
customer_id: "user_or_org_id_from_auth",
});
const messages = data?.features?.messages;
// Extract monthly vs prepaid balances from the breakdown
const monthlyBalance = messages?.breakdown?.find(
(b) => b.interval === "month"
);
const prepaidBalance = messages?.breakdown?.find(
(b) => b.interval === "lifetime"
);
console.log(`Monthly: ${monthlyBalance?.balance ?? 0} remaining`);
console.log(`Prepaid: ${prepaidBalance?.balance ?? 0} remaining`);
console.log(`Total: ${messages?.balance ?? 0} messages available`);
```
```python Python [expandable]
import asyncio
from autumn import Autumn
autumn = Autumn("am_sk_42424242")
async def main():
response = await autumn.customer(
customer_id="user_or_org_id_from_auth",
)
messages = response.features.get("messages", {})
breakdown = messages.get("breakdown", [])
# Extract monthly vs prepaid balances
monthly = next((b for b in breakdown if b.get("interval") == "month"), None)
prepaid = next((b for b in breakdown if b.get("interval") == "lifetime"), None)
print(f"Monthly: {monthly['balance'] if monthly else 0} remaining")
print(f"Prepaid: {prepaid['balance'] if prepaid else 0} remaining")
print(f"Total: {messages.get('balance', 0)} messages available")
asyncio.run(main())
```
```bash cURL
curl -X GET "https://api.useautumn.com/v1/customers/user_or_org_id_from_auth" \
-H "Authorization: Bearer am_sk_42424242" \
-H "Content-Type: application/json"
# get features usage object from customer.features.[feature_id]
```
</CodeGroup>
<Expandable title="customer response">
```json
{
"features": {
"messages": {
"id": "messages",
"type": "single_use",
"name": "Messages",
"interval": "multiple",
"interval_count": null,
"unlimited": false,
"balance": 205,
"usage": 5,
"included_usage": 210,
"next_reset_at": null,
"overage_allowed": false,
"breakdown": [
{
"interval": "month",
"interval_count": 1,
"balance": 5,
"usage": 5,
"included_usage": 10,
"next_reset_at": 1772191445539,
"overage_allowed": false
},
{
"interval": "lifetime",
"interval_count": 1,
"balance": 200,
"usage": 0,
"included_usage": 200,
"next_reset_at": null,
"overage_allowed": false
}
]
}
},
}
```
</Expandable>
</Step>
</Steps>

View File

@@ -2,6 +2,88 @@
Browse our [Examples](/examples) for guides on setting up credit systems, top ups and other common pricing models.
</Tip>
<Tabs>
<Tab title="CLI">
Run the following command in your root directory:
<CodeGroup>
```bash bun
bunx atmn init
```
```bash npm
npx atmn init
```
```bash pnpm
pnpm dlx atmn init
```
</CodeGroup>
This 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.
```typescript autumn.config.ts [expandable]
import { feature, item, plan } from "atmn";
// Features
export const messages = feature({
id: "messages",
name: "Messages",
type: "metered",
consumable: true,
});
// Plans
export const free = plan({
id: "free",
name: "Free",
autoEnable: true,
items: [
// 5 messages per month
item({
featureId: messages.id,
included: 5,
reset: { interval: "month" },
}),
],
});
export const pro = plan({
id: "pro",
name: "Pro",
price: {
amount: 20,
interval: "month",
},
items: [
// 100 messages per month
item({
featureId: messages.id,
included: 100,
reset: { interval: "month" },
}),
],
});
```
Then, push your changes to Autumn's sandbox environment.
<CodeGroup>
```bash bun
bunx atmn push
```
```bash npm
npx atmn push
```
```bash pnpm
pnpm dlx atmn push
```
</CodeGroup>
<Tip>
If you already have products created in the dashboard, run `atmn pull` to
pull them into your local config.
</Tip>
</Tab>
<Tab title="Dashboard">
Create your [Autumn account](https://app.useautumn.com/), and the Free and Pro plans in the [Plans](https://app.useautumn.com/products) tab.
<AccordionGroup>
@@ -53,77 +135,6 @@ Create your [Autumn account](https://app.useautumn.com/), and the Free and Pro p
</Accordion>
</AccordionGroup>
</Tab>
<Tab title="CLI">
<Info>
The CLI is in beta. The dashboard is the source of truth for plans and features and has the most up-to-date configurations.
</Info>
Run the following command in your root directory:
```bash
npx atmn init
```
This 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](/api-reference/cli/config) to build your own.
```typescript autumn.config.ts [expandable]
import { feature, plan, planItem } from "atmn";
// Features
export const messages = feature({
id: "messages",
name: "Messages",
type: "metered",
consumable: true,
});
// Plans
export const free = plan({
id: "free",
name: "Free",
autoEnable: true,
items: [
// 5 messages per month
planItem({
featureId: messages.id,
included: 5,
reset: { interval: "month" },
}),
],
});
export const pro = plan({
id: "pro",
name: "Pro",
price: {
amount: 20,
interval: "month",
},
items: [
// 100 messages per month
planItem({
featureId: messages.id,
included: 100,
reset: { interval: "month" },
}),
],
});
```
Then, push your changes to Autumn's sandbox environment.
```bash
npx atmn push
```
<Tip>
If you already have products created in the dashboard, run `npx atmn pull` to
pull them into your local config.
</Tip>
</Tab>
</Tabs>

View File

@@ -1,3 +1,3 @@
<Tip>
Learn how to setup Autumn hooks in the [Getting Started](/documentation/getting-started/setup/react) guide.
Learn how to setup Autumn hooks in the [Getting Started](/documentation/getting-started/setup) guide.
</Tip>

View File

@@ -1,14 +1,14 @@
---
title: Welcome to Autumn
sidebarTitle: Introduction
description: "Open source, drop-in system of record for AI billing and monetization."
description: "The open source, drop-in system of record for AI monetization."
---
## What is Autumn?
Autumn is an infrastructure layer between your application and Stripe. It acts as your source of truth for subscription status, usage metering and credit balances.
Autumn is a pricing and billing layer between your application and Stripe. It acts as your source of truth for customer subscription statuses, usage metering and credit balances.
Your app queries Autumn in real-time to check if a customer is allowed to do something (eg, send an AI message, access SSO, etc).
Instead of billing logic living in your code and database, your app can query Autumn in real-time to check if a customer is allowed to do something (eg, send an AI message, access SSO, etc).
```mermaid actions={false}
flowchart TD
@@ -18,54 +18,97 @@ flowchart TD
end
subgraph autumn["Autumn"]
C["Autumn Server"]
C["Autumn API"]
D["Dashboard or CLI"]
E["Database & Cache"]
end
F["Stripe"]
subgraph stripe["Stripe"]
F["Stripe billing API"]
end
A -- "Autumn hooks (optional)" --> B
B -- "Balance checks + usage tracking" --> C
B -- "Plan changes, access checks, usage tracking" --> C
C -- "Customer state" --> E
C -- "Payments" --> F
C -- "Subscriptions" --> F
F -- "Webhooks" --> C
D -- "Configure Pricing" --> C
```
## Why use Autumn?
AI made pricing and billing significantly harder for engineers to build and maintain. For reference, OpenAI wrote a [blog post](https://openai.com/index/beyond-rate-limits/) about the system they built in-house. Some of the things you will need to build and maintain are:
AI made pricing and billing significantly harder for developers to build and maintain. For reference, OpenAI wrote a [blog post](https://openai.com/index/beyond-rate-limits/) about their in-house system. Here are some of the things you will need:
| Feature | Requirements |
|-----------------------|--------------------------------------------------------------------------------------------------|
| Subscription logic | Checkouts, prorated upgrades, scheduled downgrades, add-ons, trials. 10+ webhook cases to handle. |
| Credit system | Monthly limits, rollovers, promotional grants with exipry, waterfall deduction rules, real-time enforcement |
| Spend controls | Auto top ups, spend caps, per-seat allowances, usage analytics, observability |
| Enterprise plans | Tiered pricing, custom credit grants, pilots, expansion logic |
| Edge cases | Plan switching, failed payments, 3DS, race conditions, refunds |
| Subscription logic | Checkouts, prorated upgrades, scheduled downgrades, add-ons, trials. 10+ webhook cases to handle. |
| Credit system | Real-time enforcement, periodic vs one-time grants, rollovers, expiration, concurrency control |
| Controls and observability | Auto top ups, spend caps, per-seat allowances, usage analytics, event logs |
| Versioning and grandfathering | Various price IDs, migration scripts, backwards compatibility |
| Enterprise and custom plans | Custom code, tiered pricing, custom credit grants, pilots, expansion logic |
| Edge cases | Plan switching, monthly/annual changes, failed payments, 3DS, race conditions, refunds |
At some point you or your GTM team will want to change your pricing, and you will need to rebuild everything.
Autumn offloads all this logic out of your codebase. After you set it up, everything about your pricing can be managed through our dashboard.
Autumn replaces this, and offloads all this logic out of your codebase. After you set it up, everything about your pricing can be managed through our dashboard or config file.
It's faster to setup, more flexible, more reliable and saves teams months of engineering time.
## How is this different?
Stripe has its own metered billing product, and there are others out there too like Metronome, Orb, Lago etc. All of these products allow you to record and bill for usage after an action is taken, making them good for end of month invoicing.
However, prepaid credits and usage limits are becoming the default standard for AI monetization. This needs to work in real-time.
Put simply, Autumn's key differentiator is the `check` function: a low-latency API called designed to be called _before_ an action is taken, to gate access. This sounds trivial, but is a totally different product in 2 ways:
**Functionally** <br />
Because check runs before the action, Autumn becomes your system of record for pricing and entitlements.
With other providers, billing is asynchronous: you send usage, they generate invoices later, and _you own the logic and state in between_ — who gets access, when limits apply, how downgrades work etc.
Autumn owns that layer. Credit ledgers, real-time enforcement and spend caps work out of the box. Edge cases around upgrades, downgrades, and failed payments are handled automatically — users always get access to what they've paid for.
That also makes scaling challenges much simpler. Changing pricing, migrating plans, handling downgrades, setting up custom enterprise contracts with rollovers, or launching team billing with per-seat allowances become configuration changes instead of messy logic rewrites.
**Architecturally** <br />
`check` is designed to be called inline, before every AI generation, API request, or feature gate. We achieve response times of under 50ms via multi-region caching, with atomic handling of concurrent requests so credit balances and usage limits stay consistent even at scale.
Unlike other billing providers, Autumn is built on top of Stripe billing instead of replacing it. Your subscriptions, customers, and payment details live in your own Stripe account, so you're never "locked in".
<Check>
While Autumn's core focus is credit-based AI monetization, it can be used for any SaaS pricing model. Many of our users have no usage-based features at all, and just prefer the simplicity and developer experience (eg, no webhooks).
</Check>
It gives you easier setup, saves months of engineering time, more flexibility and more reliability.
## Core concepts
## Core concepts
<Steps>
<Step title="Model your pricing in Autumn">
Model your pricing plans in the Autumn UI, or through a config file. Define your free, paid and any add-on pricing tiers.
A typical billing provider (eg, Stripe) would let you configure your pricing -- then send you webhooks to handle the rest. In Autumn, you define _both pricing and the features that customers get access to_.
You can link features to these plans and define their usage limits: both recurring (monthly, yearly) and one-time grants.
You can link features to these plans and define their usage limits: both recurring (monthly, yearly), one-time top ups, rollovers, etc.
</Step>
<Step title="Handle payments">
The `checkout` function will return a Stripe checkout URL, or confirmation data for an upgrade/downgrade for the plans you defined in step 1.
The `attach` function will return a Stripe checkout URL, or confirmation data for an upgrade/downgrade for the plans you defined in step 1.
Once paid, the Autumn will grant access to the features on their plan.
@@ -87,25 +130,6 @@ If Autumn tells you they're `allowed` access, let them use the feature. Afterwar
Autumn also provides APIs to easily get customer billing data (to display on a billing page), open Stripe billing portal, display usage analytics, handle org billing, setup referral programs and more.
## Why use Autumn?
Reliable billing is hard to setup and maintain. When integrating Stripe directly, you are responsible for:
- **Syncing subscription state**: active, overdue payments, cancelling, 5-10 webhooks
- **Plan switching**: upgrades, scheduled downgrades, add ons
- **Usage limits**: monthly recurring limits, one-time limits, spend limits, credits
- **Custom plans**: and plan versioning, pricing migrations
The problem gets worse as your codebase and pricing changes. Billing code becomes a sprawling mess of edge cases, race conditions and takes time away from building your product.
Autumn is a managed service that replaces this logic. Your server makes queries to Autumn, and it tells you if a customer can "do something" (eg, send a chatbot message, access premium features, etc), based on your pricing plan configuration.
This gives you flexibility to make pricing changes, or define custom plans for important customers without touching any code.
## What Autumn is not
- **A Stripe billing replacement**: although you don't need to deal with Stripe's APIs, you are still using (and paying for) Stripe's subscriptions, payments and invoicing. You bring your own Stripe account and are never "locked in" to our system.

View File

@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "autumn",
@@ -4565,6 +4564,10 @@
"@asyncapi/parser/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
"@autumn/openapi/@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
"@autumn/server/@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
"@autumn/server/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
"@autumn/server/autumn-js": ["autumn-js@0.1.76", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call", "convex"] }, "sha512-WAJ+7NNPTli7swoeL5cgHuZCUiFsrjhM2Coc0UXzN9YCfTmaN8VeAOsfV+vtsyp5Pk37VJD8iW7Uyd5GgOn4IA=="],
@@ -4573,6 +4576,8 @@
"@autumn/shared/@date-fns/utc": ["@date-fns/utc@2.1.0", "", {}, "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA=="],
"@autumn/shared/@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
"@autumn/vite/@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="],
"@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
@@ -5401,6 +5406,8 @@
"@useautumn/sdk/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"@useautumn/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@vercel/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
@@ -5787,6 +5794,10 @@
"@asyncapi/parser/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@autumn/openapi/@types/bun/bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
"@autumn/server/@types/bun/bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
"@autumn/server/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"@autumn/server/autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
@@ -5811,6 +5822,8 @@
"@autumn/server/ink/widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="],
"@autumn/shared/@types/bun/bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
"@autumn/vite/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
@@ -6627,12 +6640,16 @@
"yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@autumn/openapi/@types/bun/bun-types/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
"@autumn/server/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"@autumn/server/ink/cli-truncate/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
"@autumn/server/ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"@autumn/shared/@types/bun/bun-types/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
@@ -6935,8 +6952,12 @@
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@autumn/openapi/@types/bun/bun-types/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"@autumn/server/ink/cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"@autumn/shared/@types/bun/bun-types/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@4.1.3", "", { "dependencies": { "@smithy/protocol-http": "^4.1.8", "@smithy/querystring-builder": "^3.0.11", "@smithy/types": "^3.7.2", "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-6SxNltSncI8s689nvnzZQc/dPXcpHQ34KUj6gR/HBroytKOd/isMG3gJF/zBE1TBmTT18TXyzhg3O3SOOqGEhA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@3.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA=="],

View File

@@ -168,7 +168,7 @@ export function CustomerListTable({
icon={<ArrowSquareOutIcon size={16} />}
onClick={() => {
window.open(
"https://docs.useautumn.com/documentation/getting-started/setup/react",
"https://docs.useautumn.com/documentation/getting-started/setup",
"_blank",
);
}}

View File

@@ -167,7 +167,7 @@ export function CustomerProductsTable() {
className="px-1! ml-2"
onClick={() =>
window.open(
"https://docs.useautumn.com/documentation/getting-started/setup/react",
"https://docs.useautumn.com/documentation/getting-started/setup",
"_blank",
)
}