diff --git a/apps/docs/mintlify/assets/guides/entity-balances/team-dark.png b/apps/docs/mintlify/assets/guides/entity-balances/team-dark.png new file mode 100644 index 000000000..2fa9d367f Binary files /dev/null and b/apps/docs/mintlify/assets/guides/entity-balances/team-dark.png differ diff --git a/apps/docs/mintlify/assets/guides/entity-balances/team-light.png b/apps/docs/mintlify/assets/guides/entity-balances/team-light.png new file mode 100644 index 000000000..2af3d059a Binary files /dev/null and b/apps/docs/mintlify/assets/guides/entity-balances/team-light.png differ diff --git a/apps/docs/mintlify/assets/guides/per-seat/free-dark.png b/apps/docs/mintlify/assets/guides/per-seat/free-dark.png new file mode 100644 index 000000000..209b798c4 Binary files /dev/null and b/apps/docs/mintlify/assets/guides/per-seat/free-dark.png differ diff --git a/apps/docs/mintlify/assets/guides/per-seat/free-light.png b/apps/docs/mintlify/assets/guides/per-seat/free-light.png new file mode 100644 index 000000000..7e4fc0935 Binary files /dev/null and b/apps/docs/mintlify/assets/guides/per-seat/free-light.png differ diff --git a/apps/docs/mintlify/assets/guides/per-seat/pro-dark.png b/apps/docs/mintlify/assets/guides/per-seat/pro-dark.png new file mode 100644 index 000000000..061d01cd6 Binary files /dev/null and b/apps/docs/mintlify/assets/guides/per-seat/pro-dark.png differ diff --git a/apps/docs/mintlify/assets/guides/per-seat/pro-light.png b/apps/docs/mintlify/assets/guides/per-seat/pro-light.png new file mode 100644 index 000000000..28acfe157 Binary files /dev/null and b/apps/docs/mintlify/assets/guides/per-seat/pro-light.png differ diff --git a/apps/docs/mintlify/assets/guides/prepaid/free-dark.png b/apps/docs/mintlify/assets/guides/prepaid/free-dark.png index cb8d6d2c3..31d6a3a0e 100644 Binary files a/apps/docs/mintlify/assets/guides/prepaid/free-dark.png and b/apps/docs/mintlify/assets/guides/prepaid/free-dark.png differ diff --git a/apps/docs/mintlify/assets/guides/prepaid/free-light.png b/apps/docs/mintlify/assets/guides/prepaid/free-light.png index 8423c2796..7658e2053 100644 Binary files a/apps/docs/mintlify/assets/guides/prepaid/free-light.png and b/apps/docs/mintlify/assets/guides/prepaid/free-light.png differ diff --git a/apps/docs/mintlify/cli/config.mdx b/apps/docs/mintlify/cli/config.mdx index 38cdce681..d9d3ff7cb 100644 --- a/apps/docs/mintlify/cli/config.mdx +++ b/apps/docs/mintlify/cli/config.mdx @@ -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) - + **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 }` ### 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, - Array of `planFeature()` objects defining what's included. + Array of `item()` objects defining what's included. - + Automatically assign this plan to new customers. Typically used for free plans. - + Allow this plan to be purchased alongside other plans (instead of replacing them). - + 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 Group related plans together. Plans in the same group replace each other on upgrade/downgrade. -## 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)` - + The `id` of the feature to include. @@ -174,7 +174,7 @@ Plan features define what each plan includes -- usage limits, pricing, and billi 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` @@ -183,32 +183,32 @@ Plan features define what each plan includes -- usage limits, pricing, and billi 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"` 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"` ### 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 - Price per `billing_units`. Mutually exclusive with `tiers`. + Price per `billingUnits`. Mutually exclusive with `tiers`. Tiered pricing. Each entry: `{ to: number | "inf", amount: number }`. Mutually exclusive with `amount`. - + `"usage_based"` | `"prepaid"` - `"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`. - - Units per price. Eg, $5 per 100 credits = `amount: 5, billing_units: 100`. + + Units per price. Eg, $5 per 100 credits = `amount: 5, billingUnits: 100`. - + Maximum quantity that can be purchased. @@ -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', }, }), ], diff --git a/apps/docs/mintlify/docs.json b/apps/docs/mintlify/docs.json index a5735050b..11ca36bf7 100644 --- a/apps/docs/mintlify/docs.json +++ b/apps/docs/mintlify/docs.json @@ -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" ] diff --git a/apps/docs/mintlify/documentation/customers/updating-subscriptions.mdx b/apps/docs/mintlify/documentation/customers/updating-subscriptions.mdx index 4d7862c39..3d6012d7c 100644 --- a/apps/docs/mintlify/documentation/customers/updating-subscriptions.mdx +++ b/apps/docs/mintlify/documentation/customers/updating-subscriptions.mdx @@ -17,7 +17,7 @@ Prepaid features are features where customers pay upfront for a quantity (e.g., ```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: { diff --git a/apps/docs/mintlify/documentation/getting-started/setup/convex.mdx b/apps/docs/mintlify/documentation/external-providers/convex.mdx similarity index 100% rename from apps/docs/mintlify/documentation/getting-started/setup/convex.mdx rename to apps/docs/mintlify/documentation/external-providers/convex.mdx diff --git a/apps/docs/mintlify/documentation/getting-started/migration.mdx b/apps/docs/mintlify/documentation/getting-started/migration.mdx index 169d79e0c..6f780b2f4 100644 --- a/apps/docs/mintlify/documentation/getting-started/migration.mdx +++ b/apps/docs/mintlify/documentation/getting-started/migration.mdx @@ -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. @@ -82,7 +82,7 @@ If preserving exact usage counts is critical for your business, reach out to us **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). diff --git a/apps/docs/mintlify/documentation/getting-started/setup/react.mdx b/apps/docs/mintlify/documentation/getting-started/setup.mdx similarity index 62% rename from apps/docs/mintlify/documentation/getting-started/setup/react.mdx rename to apps/docs/mintlify/documentation/getting-started/setup.mdx index 66c9c0752..a4a4533e7 100644 --- a/apps/docs/mintlify/documentation/getting-started/setup/react.mdx +++ b/apps/docs/mintlify/documentation/getting-started/setup.mdx @@ -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. - - - 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. - - In this example we'll create the pricing for a premium AI chatbot. We're going to have: - A Free 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 ### 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 +``` + @@ -60,7 +58,14 @@ yarn add autumn-js + +{/* REACT DOCS */} + + +Autumn's client-side [hooks](/react/hooks/useCustomer) allow you to handle billing directly from your frontend. + + ### Add Endpoints Server-side @@ -148,9 +153,14 @@ return new Response(JSON.stringify(result.response), { + +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. + + + ### Add Provider Client-side Client side, wrap your application with the `` 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. | - @@ -253,6 +254,7 @@ You will see your user under the [customers](https://app.useautumn.com/customers + ### Stripe Payment Flow Call `attach` to attach the Pro 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. - **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. + + +{/* SERVER SDK DOCS */} + + + + +### Create an Autumn customer + +When the customer signs up, create an Autumn customer for them. Autumn will automatically enable the Free plan, since you marked it with the `auto-enable` flag. + + + +```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" +}' +``` + + + + +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. + + +In the Autumn dashboard, you will see your user under the [customers](https://app.useautumn.com/customers) page. + + + + +### Stripe Payment Flow + +Call `billing.attach` to attach the Pro plan to the customer. Redirect the customer to the returned `paymentUrl` to complete payment or confirm the plan change. + + + +```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" +}' +``` + + + + +Use Stripe's test card `4242 4242 4242 4242` to make a purchase in sandbox. You can enter any Expiry and CVV. + + +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. + + + **`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 + + + + + **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 - diff --git a/apps/docs/mintlify/documentation/getting-started/setup/sdk.mdx b/apps/docs/mintlify/documentation/getting-started/setup/sdk.mdx deleted file mode 100644 index 94d7c3c3d..000000000 --- a/apps/docs/mintlify/documentation/getting-started/setup/sdk.mdx +++ /dev/null @@ -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 Free plan that gives users 5 chat messages per month for free -- A Pro plan that gives users 100 chat messages per month for $20 per month. - - - -### 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. - - - - - - -### 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... -``` - - - -```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 -``` - - - - - - -### Create an Autumn customer - -When the customer signs up, create an Autumn customer for them. Autumn will automatically enable the Free plan, since you marked it with the `auto-enable` flag. - - - -```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" -}' -``` - - - - -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. - - -In the Autumn dashboard, you will see your user under the [customers](https://app.useautumn.com/customers) page. - - - - -### Stripe Payment Flow - -Call `billing.attach` to attach the Pro plan to the customer. Redirect the customer to the returned `paymentUrl` to complete payment or confirm the plan change. - - - -```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" -}' -``` - - - - -Use Stripe's test card `4242 4242 4242 4242` to make a purchase in sandbox. You can enter any Expiry and CVV. - - -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. - - - **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 - - - - - -**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. - - -Enforce usage limits and feature permissions using Autumn's `check` and `track` functions - - - - - - - - - - - - - - - diff --git a/apps/docs/mintlify/examples/entity-balances.mdx b/apps/docs/mintlify/examples/entity-balances.mdx new file mode 100644 index 000000000..e4c3c2792 --- /dev/null +++ b/apps/docs/mintlify/examples/entity-balances.mdx @@ -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 + + + +#### 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 + + + + +#### 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. + + + + + + + + + +## Implementation + + + +#### Create an Autumn Customer + +When an organization signs up, create an Autumn customer. + + + +```jsx React +import { useCustomer } from "autumn-js/react"; + +const App = () => { + const { customer } = useCustomer(); + + console.log("Autumn customer:", customer); + + return

Welcome, {customer?.name || "user"}!

; +}; +``` + +```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" + }' +``` + +
+
+ + +#### 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. + + + +```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" + }' +``` + + + + +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. + + + + + +#### Attach the Team Plan + +When the customer upgrades to Team, attach the plan. The checkout URL will show the \$30 base price. + + + +```jsx React +import { useCustomer, CheckoutDialog } from "autumn-js/react"; + +export default function UpgradeButton() { + const { checkout } = useCustomer(); + + return ( + + ); +} +``` + +```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" + }' +``` + + + + + +#### 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. + + + +```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" + }' +``` + + + + +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. + + + + + +#### Check Access Per Entity + +Before generating a meeting summary, check if that specific user has remaining balance. + + + +```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 ; +} +``` + +```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" + }' +``` + + + + +```json +{ + "allowed": true, + "customer_id": "org_123", + "feature_id": "meeting_summaries", + "entity_id": "user_alice", + "balance": 47, + "usage": 3, + "included_usage": 50, + "unlimited": false +} +``` + + + + + +#### Track Usage Per Entity + +When a user generates a summary, track the usage against their entity. + + + +```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 + }' +``` + + + + + + +#### Check Customer-level Balance (Optional) + +You can also check the total balance across all entities, useful for admin dashboards. + + + +```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" + }' +``` + + + + +```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). + + + + + +#### 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. + + + +```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" +``` + + + + +Deleting an entity automatically decrements the seat count. The customer's total meeting summary balance will also decrease accordingly. + + + +
+ +## 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 diff --git a/apps/docs/mintlify/examples/credits.mdx b/apps/docs/mintlify/examples/monetary-credits.mdx similarity index 100% rename from apps/docs/mintlify/examples/credits.mdx rename to apps/docs/mintlify/examples/monetary-credits.mdx diff --git a/apps/docs/mintlify/examples/pay-as-you-go-overages.mdx b/apps/docs/mintlify/examples/pay-as-you-go-overages.mdx new file mode 100644 index 000000000..7baffcdd8 --- /dev/null +++ b/apps/docs/mintlify/examples/pay-as-you-go-overages.mdx @@ -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 + + + +#### Create Feature + +Create a `metered` `consumable` feature called "notifications". + + + + +#### 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. + + + + +#### 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 + + +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. + + + + + +## Implementation + + + +#### 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. + + + +```jsx React +import { useCustomer } from "autumn-js/react"; + +const App = () => { + const { customer } = useCustomer(); + + console.log("Autumn customer:", customer); + + return

Welcome, {customer?.name || "user"}!

; +}; +``` + +```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" + }' +``` + +
+
+ + +#### Check Access + +Before sending a notification, check if the customer has remaining capacity. + + + +```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" + }' +``` + + + + +```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. + + + + + +#### Track Usage + +After sending a notification, track the usage. + + + +```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 + }' +``` + + + + + + +#### 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. + + +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. + + + + + +```jsx React +import { useCustomer } from "autumn-js/react"; + +export default function EnableOveragesButton() { + const { attach } = useCustomer(); + + return ( + + ); +} +``` + +```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" + }' +``` + + + + +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. + + + + + +#### 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. + + +```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. + + + +
+ +## 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 | diff --git a/apps/docs/mintlify/examples/per-seat.mdx b/apps/docs/mintlify/examples/per-seat.mdx new file mode 100644 index 000000000..2e2c24f90 --- /dev/null +++ b/apps/docs/mintlify/examples/per-seat.mdx @@ -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 + + + +#### Create Feature + +Create a `metered` `non-consumable` feature called "seats". Non-consumable features are for persistent resources like seats, GB storage, or workspaces. + + + + +#### Create Free Plan + +Create a free plan with 3 included seats. Set `auto-enable` so new customers automatically get this plan. + + + + + + + + +#### 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 + + + + + + + + + + + +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. + + + +## Implementation + + + +#### 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. + + + +```jsx React +import { useCustomer } from "autumn-js/react"; + +const App = () => { + const { customer } = useCustomer(); + + console.log("Autumn customer:", customer); + + return

Welcome, {customer?.name || "user"}!

; +}; +``` + +```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" + }' +``` + +
+
+ + +#### Check Seat Access + +Before adding a new team member, check if the customer has available seats. + + + +```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" + }' +``` + + + + +```json +{ + "allowed": true, + "customer_id": "org_123", + "feature_id": "seats", + "balance": 1, + "usage": 2, + "included_usage": 3, + "unlimited": false, + "overage_allowed": false +} +``` + + + + +#### 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. + + + +```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 + }' +``` + + + + +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. + + + + + +#### 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`. + + +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`. + + + + +```jsx React +import { useCustomer, CheckoutDialog } from "autumn-js/react"; + +export default function UpgradeButton() { + const { checkout } = useCustomer(); + + return ( + + ); +} +``` + +```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 + }] + }' +``` + + + + +If the customer only needs the 5 included seats, pass `quantity: 0` or omit the options entirely. + + + + + +#### Update Seat Quantity + +How you update seat quantity depends on your billing model: + + + + +For prepaid seats, use the `attach` endpoint with updated `options` to change the seat quantity. This will handle proration based on your configuration. + + + +```jsx React +import { useCustomer, CheckoutDialog } from "autumn-js/react"; + +export default function AddSeatsButton() { + const { checkout } = useCustomer(); + + return ( + + ); +} +``` + +```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 + }] + }' +``` + + + + +**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 + + + + + +For usage-based seats, simply track the actual seat usage. Billing happens automatically at the end of each billing cycle based on usage. + + + +```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 + }' +``` + + + + +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. + + + + + + + +#### Decrease Seat Quantity + +When team members leave, you'll want to decrease the seat count. + + + + +Update the `options` with a lower quantity. Depending on your proration configuration, the customer may receive a credit. + + + +```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 + }] + }' +``` + + + + + + +Track the removal or set the new total directly. + + + +```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 + }' +``` + + + + + + +
+ +## 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 | diff --git a/apps/docs/mintlify/examples/prepaid.mdx b/apps/docs/mintlify/examples/prepaid.mdx index 777490a99..bb8d4a1b0 100644 --- a/apps/docs/mintlify/examples/prepaid.mdx +++ b/apps/docs/mintlify/examples/prepaid.mdx @@ -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: #### 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. @@ -33,7 +33,8 @@ Create a `metered` `consumable` feature for our premium messages, so we can trac #### 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**
+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. 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 -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**
+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. @@ -61,7 +65,7 @@ Now we'll create our top-up plan. We'll add a price to our premium messages feat #### 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. @@ -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" \ #### 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. -```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" \ ```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 } ``` -#### 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. -```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" \ ```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" } ``` @@ -269,25 +285,28 @@ curl -X POST "https://api.useautumn.com/v1/track" \ #### 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. ```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 ( @@ -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" \ -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. + +```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 +} +``` + + +Once the customer completes the payment, they will have an additional 200 premium messages available to use. + + +#### 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. + + + +```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 ( +
+

Monthly: {monthlyBalance?.balance ?? 0} remaining

+

Prepaid: {prepaidBalance?.balance ?? 0} remaining

+

Total: {messages?.balance ?? 0} messages available

+
+); +``` + +```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] +``` + +
+ + +```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 + } + ] + } + }, +} +``` +
diff --git a/apps/docs/mintlify/snippets/create-plans.mdx b/apps/docs/mintlify/snippets/create-plans.mdx index 358223e72..8247ef6b9 100644 --- a/apps/docs/mintlify/snippets/create-plans.mdx +++ b/apps/docs/mintlify/snippets/create-plans.mdx @@ -2,6 +2,88 @@ Browse our [Examples](/examples) for guides on setting up credit systems, top ups and other common pricing models.
+ + +Run the following command in your root directory: + + +```bash bun +bunx atmn init +``` +```bash npm +npx atmn init +``` +```bash pnpm +pnpm dlx 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](/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. + + +```bash bun +bunx atmn push +``` +```bash npm +npx atmn push +``` +```bash pnpm +pnpm dlx atmn push +``` + + + + If you already have products created in the dashboard, run `atmn pull` to + pull them into your local config. + + + Create your [Autumn account](https://app.useautumn.com/), and the Free and Pro plans in the [Plans](https://app.useautumn.com/products) tab. @@ -53,77 +135,6 @@ Create your [Autumn account](https://app.useautumn.com/), and the Free and Pro p - - - -The CLI is in beta. The dashboard is the source of truth for plans and features and has the most up-to-date configurations. - - -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 -``` - - - If you already have products created in the dashboard, run `npx atmn pull` to - pull them into your local config. - - - - diff --git a/apps/docs/mintlify/snippets/learn-hooks-tip.mdx b/apps/docs/mintlify/snippets/learn-hooks-tip.mdx index a0607a4ad..f7feb7c46 100644 --- a/apps/docs/mintlify/snippets/learn-hooks-tip.mdx +++ b/apps/docs/mintlify/snippets/learn-hooks-tip.mdx @@ -1,3 +1,3 @@ - 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. \ No newline at end of file diff --git a/apps/docs/mintlify/welcome.mdx b/apps/docs/mintlify/welcome.mdx index d49893403..502a3c3f1 100644 --- a/apps/docs/mintlify/welcome.mdx +++ b/apps/docs/mintlify/welcome.mdx @@ -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**
+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**
+`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". + + +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). + + + -It gives you easier setup, saves months of engineering time, more flexibility and more reliability. -## Core concepts +## Core concepts 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. -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. diff --git a/bun.lock b/bun.lock index b5b04eabc..36f6ec32b 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], diff --git a/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx b/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx index b961ea3f6..4d6f0fe6f 100644 --- a/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx +++ b/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx @@ -168,7 +168,7 @@ export function CustomerListTable({ icon={} onClick={() => { window.open( - "https://docs.useautumn.com/documentation/getting-started/setup/react", + "https://docs.useautumn.com/documentation/getting-started/setup", "_blank", ); }} diff --git a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx index acc35fc8c..b17dd3e20 100644 --- a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx +++ b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx @@ -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", ) }