From 73d3d23939eed41bae40cebeb3d7da75e7f5260f Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Wed, 11 Mar 2026 11:35:01 +0000 Subject: [PATCH] docs wip again --- .../api-reference/plans/createPlan.mdx | 2 +- .../api-reference/plans/updatePlan.mdx | 2 +- apps/docs/mintlify/docs.json | 55 ++-- .../overview.mdx => concepts/balances.mdx} | 75 ++++- .../{pricing => concepts}/features.mdx | 4 +- .../plan-items.mdx} | 4 +- .../{pricing => concepts}/plans.mdx | 4 +- .../{pricing => concepts}/rewards.mdx | 0 .../subscriptions.mdx} | 2 +- .../{pricing => concepts}/versioning.mdx | 0 .../customers/balances/balance-stacking.mdx | 71 ----- .../{balances => }/managing-balances.mdx | 2 +- .../documentation/customers/payment-flow.mdx | 269 +++++++++++++++++ .../customers/subscription-lifecycle.mdx | 255 +++++++++++++++++ .../subscriptions/accepting-payments.mdx | 168 ----------- .../subscriptions/managing-subscriptions.mdx | 103 ------- .../mintlify/documentation/edge-cases.mdx | 111 +++++++ .../external-providers/revenuecat.mdx | 2 +- .../getting-started/display-billing.mdx | 87 ++++++ .../documentation/getting-started/gating.mdx | 42 +-- .../documentation/getting-started/setup.mdx | 5 +- .../modelling-pricing/add-ons.mdx | 187 ++++++++++++ .../modelling-pricing/auto-top-ups.mdx | 161 +++++++++++ .../credit-systems.mdx} | 4 +- .../modelling-pricing/free-plans.mdx | 135 +++++++++ .../modelling-pricing/graduated-pricing.mdx | 106 +++++++ .../modelling-pricing/one-off-purchases.mdx | 138 +++++++++ .../modelling-pricing/pay-per-use.mdx | 183 ++++++++++++ .../modelling-pricing/per-unit-pricing.mdx | 212 ++++++++++++++ .../modelling-pricing/proration.mdx | 112 ++++++++ .../modelling-pricing/recurring.mdx | 253 ++++++++++++++++ .../modelling-pricing/rollovers.mdx | 128 +++++++++ .../modelling-pricing/sub-entity-balances.mdx | 240 ++++++++++++++++ .../modelling-pricing/sub-entity-plans.mdx | 270 ++++++++++++++++++ .../modelling-pricing/trials.mdx | 224 +++++++++++++++ .../modelling-pricing/volume-based-tiers.mdx | 129 +++++++++ apps/docs/mintlify/welcome.mdx | 2 +- 37 files changed, 3338 insertions(+), 409 deletions(-) rename apps/docs/mintlify/documentation/{customers/balances/overview.mdx => concepts/balances.mdx} (54%) rename apps/docs/mintlify/documentation/{pricing => concepts}/features.mdx (90%) rename apps/docs/mintlify/documentation/{pricing/plan-features.mdx => concepts/plan-items.mdx} (96%) rename apps/docs/mintlify/documentation/{pricing => concepts}/plans.mdx (97%) rename apps/docs/mintlify/documentation/{pricing => concepts}/rewards.mdx (100%) rename apps/docs/mintlify/documentation/{customers/subscriptions/overview.mdx => concepts/subscriptions.mdx} (91%) rename apps/docs/mintlify/documentation/{pricing => concepts}/versioning.mdx (100%) delete mode 100644 apps/docs/mintlify/documentation/customers/balances/balance-stacking.mdx rename apps/docs/mintlify/documentation/customers/{balances => }/managing-balances.mdx (97%) create mode 100644 apps/docs/mintlify/documentation/customers/payment-flow.mdx create mode 100644 apps/docs/mintlify/documentation/customers/subscription-lifecycle.mdx delete mode 100644 apps/docs/mintlify/documentation/customers/subscriptions/accepting-payments.mdx delete mode 100644 apps/docs/mintlify/documentation/customers/subscriptions/managing-subscriptions.mdx create mode 100644 apps/docs/mintlify/documentation/edge-cases.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/add-ons.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/auto-top-ups.mdx rename apps/docs/mintlify/documentation/{pricing/credits.mdx => modelling-pricing/credit-systems.mdx} (97%) create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/free-plans.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/graduated-pricing.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/one-off-purchases.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/pay-per-use.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/per-unit-pricing.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/proration.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/recurring.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/rollovers.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/sub-entity-balances.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/sub-entity-plans.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/trials.mdx create mode 100644 apps/docs/mintlify/documentation/modelling-pricing/volume-based-tiers.mdx diff --git a/apps/docs/mintlify/api-reference/plans/createPlan.mdx b/apps/docs/mintlify/api-reference/plans/createPlan.mdx index c4ef018c6..42686f0fe 100644 --- a/apps/docs/mintlify/api-reference/plans/createPlan.mdx +++ b/apps/docs/mintlify/api-reference/plans/createPlan.mdx @@ -7,7 +7,7 @@ import { DynamicParamField } from "/components/dynamic-param-field.jsx"; import { DynamicResponseField } from "/components/dynamic-response-field.jsx"; import { DynamicResponseExample } from "/components/dynamic-response-example.jsx"; -Creates a new plan with optional base price and feature configurations. See [How plans work](/documentation/pricing/plans) for concepts and [Adding features to plans](/documentation/pricing/plan-features) for item configuration. +Creates a new plan with optional base price and feature configurations. See [Plans](/documentation/concepts/plans) for concepts and [Plan Items](/documentation/concepts/plan-items) for item configuration. ### Plan Configuration diff --git a/apps/docs/mintlify/api-reference/plans/updatePlan.mdx b/apps/docs/mintlify/api-reference/plans/updatePlan.mdx index 8e1c14ba5..01764e0d7 100644 --- a/apps/docs/mintlify/api-reference/plans/updatePlan.mdx +++ b/apps/docs/mintlify/api-reference/plans/updatePlan.mdx @@ -7,7 +7,7 @@ import { DynamicParamField } from "/components/dynamic-param-field.jsx"; import { DynamicResponseField } from "/components/dynamic-response-field.jsx"; import { DynamicResponseExample } from "/components/dynamic-response-example.jsx"; -Updates an existing plan. By default, creates a new version of the plan. See [Adding features to plans](/documentation/pricing/plan-features) for item configuration. +Updates an existing plan. By default, creates a new version of the plan. See [Plan Items](/documentation/concepts/plan-items) for item configuration. Updates create a new plan version by default. Existing customers remain on their current version until their subscription renews or they explicitly upgrade. diff --git a/apps/docs/mintlify/docs.json b/apps/docs/mintlify/docs.json index 1898cc11e..c3e2875ab 100644 --- a/apps/docs/mintlify/docs.json +++ b/apps/docs/mintlify/docs.json @@ -56,35 +56,43 @@ ] }, { - "group": "Configure Pricing", + "group": "Concepts", "pages": [ - "documentation/pricing/plans", - "documentation/pricing/features", - "documentation/pricing/plan-features", - "documentation/pricing/credits", - "documentation/pricing/versioning", - "documentation/pricing/rewards" + "documentation/concepts/plans", + "documentation/concepts/features", + "documentation/concepts/plan-items", + "documentation/concepts/subscriptions", + "documentation/concepts/balances", + "documentation/concepts/versioning", + "documentation/concepts/rewards" + ] + }, + { + "group": "Modelling Pricing", + "pages": [ + "documentation/modelling-pricing/recurring", + "documentation/modelling-pricing/one-off-purchases", + "documentation/modelling-pricing/free-plans", + "documentation/modelling-pricing/trials", + "documentation/modelling-pricing/credit-systems", + "documentation/modelling-pricing/per-unit-pricing", + "documentation/modelling-pricing/rollovers", + "documentation/modelling-pricing/proration", + "documentation/modelling-pricing/pay-per-use", + "documentation/modelling-pricing/auto-top-ups", + "documentation/modelling-pricing/add-ons", + "documentation/modelling-pricing/graduated-pricing", + "documentation/modelling-pricing/volume-based-tiers", + "documentation/modelling-pricing/sub-entity-balances", + "documentation/modelling-pricing/sub-entity-plans" ] }, { "group": "Manage Customers", "pages": [ - { - "group": "Subscriptions", - "pages": [ - "documentation/customers/subscriptions/overview", - "documentation/customers/subscriptions/accepting-payments", - "documentation/customers/subscriptions/managing-subscriptions" - ] - }, - { - "group": "Balances", - "pages": [ - "documentation/customers/balances/overview", - "documentation/customers/balances/balance-stacking", - "documentation/customers/balances/managing-balances" - ] - }, + "documentation/customers/payment-flow", + "documentation/customers/subscription-lifecycle", + "documentation/customers/managing-balances", "documentation/customers/check", "documentation/customers/tracking-usage", "documentation/customers/feature-entities", @@ -95,6 +103,7 @@ { "group": "Additional Resources", "pages": [ + "documentation/edge-cases", "documentation/webhooks", "documentation/external-providers/convex", "documentation/external-providers/revenuecat", diff --git a/apps/docs/mintlify/documentation/customers/balances/overview.mdx b/apps/docs/mintlify/documentation/concepts/balances.mdx similarity index 54% rename from apps/docs/mintlify/documentation/customers/balances/overview.mdx rename to apps/docs/mintlify/documentation/concepts/balances.mdx index 8df65e969..e37afc1e4 100644 --- a/apps/docs/mintlify/documentation/customers/balances/overview.mdx +++ b/apps/docs/mintlify/documentation/concepts/balances.mdx @@ -12,14 +12,11 @@ Balances are created in two ways: ```mermaid flowchart LR - F[Feature] -->|added to plan| PF[Plan Feature] + F[Feature] -->|added to plan| PF[Plan Item] PF -->|plan attached to customer| B[Customer Balance] ``` -Customers can also [Stack Balances](/documentation/customers/balance-stacking) of the same feature from multiple plans (eg, an add-on plan), or different reset intervals (eg, monthly credits and one-time top-ups). - - ## Core Fields Each balance has the following key fields: @@ -80,7 +77,7 @@ Non-consumable features don't reset. Instead, they support **proration** when qu ### Credit Systems -A [credit system](/documentation/pricing/credits) lets multiple features draw from a single shared balance. +A [credit system](/documentation/modelling-pricing/credit-systems) lets multiple features draw from a single shared balance. When you check or track usage, you use the underlying feature ID (e.g., `premium_message`), but the balance is deducted from the credit system. @@ -97,9 +94,75 @@ For example, if you have a credit system with a credit cost of 2 credits per API A balance can be positive or negative: - **Positive balance**: Customer has unused allowance remaining -- **Negative balance**: Customer has used more than their allowance (only possible if [overage](/documentation/pricing/plan-features#priced-features) is enabled) +- **Negative balance**: Customer has used more than their allowance (only possible if [overage](/documentation/concepts/plan-items#priced-features) is enabled) Features can only have a negative balance if they have a usage-based price that allows overage. Otherwise, tracking stops when balance reaches 0. +## Balance Stacking + +A single feature can have balances from multiple sources - different plans, add-ons, or standalone grants. + +Autumn combines these into a single parent balance while tracking each source separately in a `breakdown` array, grouped by plan and interval. + +> **Example**
+> A customer has a feature, `messages`, with the following balances: +> - Pro plan: 500 messages per month +> - Top-up add-on: 200 lifetime messages +> +> Their total available balance is 700 `messages`. + +### The Breakdown Array + +Each balance source is tracked separately in the `breakdown` array. This lets you see exactly where the balance came from and how much remains from each source. + +```json expandable +{ + "balances": { + "messages": { + "included_usage": 700, + "balance": 700, + "usage": 0, + "breakdown": [ + { + "id": "ent_abc123", + "product_id": "pro", + "included_usage": 500, + "balance": 500, + "usage": 0, + "interval": "month", + "next_reset_at": 1745193600000 + }, + { + "id": "ent_def456", + "product_id": "top-up", + "included_usage": 200, + "balance": 200, + "usage": 0, + "interval": "one_off", + "next_reset_at": null + } + ] + } + } +} +``` + +### Deduction Order + +When usage is tracked, Autumn deducts from balances in a specific order based on their reset interval. **Shorter intervals are deducted first** by default. + +The order is: `hour` (shortest) > `day` > `week` > `month` > `quarter` > `semi_annual` > `year` > `one_off` (lifetime - never resets). + +This ensures that expiring balances are used before permanent ones. + + +If you need the deduction order reversed (longest interval first), please [contact us](https://discord.gg/STqxY92zuS). + + +> **Example**
+> Suppose a customer has two balances for messages: 500 monthly and 200 lifetime. They have a total of 700 messages.
+> - The customer uses 400 messages. The monthly balance (the shorter interval) is used up first, leaving 100 in monthly and 200 in lifetime (300 total).
+> - The customer uses another 200 messages. The remaining 100 monthly is depleted, and the next 100 is deducted from the lifetime balance. Now, monthly is 0, lifetime is 100 (100 total).
+> - On the next cycle, the monthly balance resets to 500, and the lifetime remains at 100, for a new total of 600.
diff --git a/apps/docs/mintlify/documentation/pricing/features.mdx b/apps/docs/mintlify/documentation/concepts/features.mdx similarity index 90% rename from apps/docs/mintlify/documentation/pricing/features.mdx rename to apps/docs/mintlify/documentation/concepts/features.mdx index efe739ed7..b806fbf6f 100644 --- a/apps/docs/mintlify/documentation/pricing/features.mdx +++ b/apps/docs/mintlify/documentation/concepts/features.mdx @@ -1,5 +1,5 @@ --- -title: "How features work" +title: "Features" description: "Learn about features in Autumn and how to create them" --- @@ -20,7 +20,7 @@ When adding features to a plan, you will be able to set reset cycles for `consum Under the "advanced" section of the feature creation sheet, you can also define [event names](/documentation/customers/tracking-usage#using-event-names). This gives you more control over how events interact with customer balances in Autumn. -Metered features can each act as their own, standlone balance, or be added to a [credit system](/documentation/pricing/credits). This lets you define credit costs per feature, and let many features draw from a common credit balance. +Metered features can each act as their own, standlone balance, or be added to a [credit system](/documentation/modelling-pricing/credit-systems). This lets you define credit costs per feature, and let many features draw from a common credit balance. ## Boolean features diff --git a/apps/docs/mintlify/documentation/pricing/plan-features.mdx b/apps/docs/mintlify/documentation/concepts/plan-items.mdx similarity index 96% rename from apps/docs/mintlify/documentation/pricing/plan-features.mdx rename to apps/docs/mintlify/documentation/concepts/plan-items.mdx index a7b203572..19909d053 100644 --- a/apps/docs/mintlify/documentation/pricing/plan-features.mdx +++ b/apps/docs/mintlify/documentation/concepts/plan-items.mdx @@ -1,5 +1,5 @@ --- -title: "Adding features to plans" +title: "Plan Items" description: "Configure what customers get access to when they purchase a plan" --- @@ -9,6 +9,8 @@ There are 2 types of plan features: - **Included Features**: features provided at no additional cost, either as a granted usage limit or a boolean flag - **Priced Features**: features that are billed for, either as a prepaid quantity or a usage-based price. Priced features can also have an included amount. +When a customer purchases a plan, the items in the plan become [balances](/documentation/concepts/balances) under the customer. + ## Included Features diff --git a/apps/docs/mintlify/documentation/pricing/plans.mdx b/apps/docs/mintlify/documentation/concepts/plans.mdx similarity index 97% rename from apps/docs/mintlify/documentation/pricing/plans.mdx rename to apps/docs/mintlify/documentation/concepts/plans.mdx index 582335ecf..7aecf636e 100644 --- a/apps/docs/mintlify/documentation/pricing/plans.mdx +++ b/apps/docs/mintlify/documentation/concepts/plans.mdx @@ -1,5 +1,5 @@ --- -title: "How plans work" +title: "Plans" description: "Learn about plans in Autumn and how to create them" --- @@ -17,7 +17,7 @@ When you create a plan, you can set its price: ## Plan Features -Plans are made up of a list of [features](/documentation/pricing/features). These can be: +Plans are made up of a list of [features](/documentation/concepts/features). These can be: - **Included Features** - features that come with the plan for no additional cost. These can be boolean flags, or metered features with a limit. - **Priced Features** - features that are billable based on usage of a feature. These can also have an included amount, and a prepaid or usage-based price. diff --git a/apps/docs/mintlify/documentation/pricing/rewards.mdx b/apps/docs/mintlify/documentation/concepts/rewards.mdx similarity index 100% rename from apps/docs/mintlify/documentation/pricing/rewards.mdx rename to apps/docs/mintlify/documentation/concepts/rewards.mdx diff --git a/apps/docs/mintlify/documentation/customers/subscriptions/overview.mdx b/apps/docs/mintlify/documentation/concepts/subscriptions.mdx similarity index 91% rename from apps/docs/mintlify/documentation/customers/subscriptions/overview.mdx rename to apps/docs/mintlify/documentation/concepts/subscriptions.mdx index ed488aa33..3552a2b15 100644 --- a/apps/docs/mintlify/documentation/customers/subscriptions/overview.mdx +++ b/apps/docs/mintlify/documentation/concepts/subscriptions.mdx @@ -14,7 +14,7 @@ flowchart LR When a subscription is created, Autumn provisions [balances](/documentation/customers/balances) for each feature in the plan. Balances determine what the customer can access and track how much they've used. For example, a Pro plan might grant 1,000 API requests per month—this becomes a balance that decrements as the customer uses your product. -Balances can also be created for [credit systems](/documentation/pricing/credits) (eg, $10 credits per month) and boolean toggle features (eg, access to a premium analytics dashboard). +Balances can also be created for [credit systems](/documentation/modelling-pricing/credit-systems) (eg, $10 credits per month) and boolean toggle features (eg, access to a premium analytics dashboard). ## Subscription statuses diff --git a/apps/docs/mintlify/documentation/pricing/versioning.mdx b/apps/docs/mintlify/documentation/concepts/versioning.mdx similarity index 100% rename from apps/docs/mintlify/documentation/pricing/versioning.mdx rename to apps/docs/mintlify/documentation/concepts/versioning.mdx diff --git a/apps/docs/mintlify/documentation/customers/balances/balance-stacking.mdx b/apps/docs/mintlify/documentation/customers/balances/balance-stacking.mdx deleted file mode 100644 index 5c7fda000..000000000 --- a/apps/docs/mintlify/documentation/customers/balances/balance-stacking.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: "Balance Stacking" -description: "How multiple balances combine and how usage is deducted" ---- - -A single feature can have balances from multiple sources - different plans, add-ons, or standalone grants. - -Autumn combines these into a single parent balance while tracking each source separately in a `breakdown` array, grouped by plan and interval. - -> **Example**
-> A customer has a feature, `messages`, with the following balances: -> - Pro plan: 500 messages per month -> - Top-up add-on: 200 lifetime messages -> -> Their total available balance is 700 `messages`. - -## The Breakdown Array - -Each balance source is tracked separately in the `breakdown` array. This lets you see exactly where the balance came from and how much remains from each source. - -```json expandable -{ - "balances": { - "messages": { - "included_usage": 700, - "balance": 700, - "usage": 0, - "breakdown": [ - { - "id": "ent_abc123", - "product_id": "pro", - "included_usage": 500, - "balance": 500, - "usage": 0, - "interval": "month", - "next_reset_at": 1745193600000 - }, - { - "id": "ent_def456", - "product_id": "top-up", - "included_usage": 200, - "balance": 200, - "usage": 0, - "interval": "one_off", - "next_reset_at": null - } - ] - } - } -} -``` - -## Deduction Order - -When usage is tracked, Autumn deducts from balances in a specific order based on their reset interval. **Shorter intervals are deducted first** by default. - -The order is: `hour` (shortest) > `day` > `week` > `month` > `quarter` > `semi_annual` > `year` > `one_off` (lifetime - never resets). - -This ensures that expiring balances are used before permanent ones. - - -If you need the deduction order reversed (longest interval first), please [contact us](https://discord.gg/STqxY92zuS). - - -> **Example**
-> Suppose a customer has two balances for messages: 500 monthly and 200 lifetime. They have a total of 700 messages.
-> - The customer uses 400 messages. The monthly balance (the shorter interval) is used up first, leaving 100 in monthly and 200 in lifetime (300 total).
-> - The customer uses another 200 messages. The remaining 100 monthly is depleted, and the next 100 is deducted from the lifetime balance. Now, monthly is 0, lifetime is 100 (100 total).
-> - On the next cycle, the monthly balance resets to 500, and the lifetime remains at 100, for a new total of 600.
- - diff --git a/apps/docs/mintlify/documentation/customers/balances/managing-balances.mdx b/apps/docs/mintlify/documentation/customers/managing-balances.mdx similarity index 97% rename from apps/docs/mintlify/documentation/customers/balances/managing-balances.mdx rename to apps/docs/mintlify/documentation/customers/managing-balances.mdx index 8043fcea9..dd7272aaa 100644 --- a/apps/docs/mintlify/documentation/customers/balances/managing-balances.mdx +++ b/apps/docs/mintlify/documentation/customers/managing-balances.mdx @@ -18,7 +18,7 @@ You can manage customer balances through the Autumn dashboard or programmaticall To set or add to a feature's balance: 1. Navigate to the customer's detail page -2. Under the balances section, click on the feature you want to modify. If there are [stacked balances](/documentation/customers/balance-stacking), choose the one you want to modify. +2. Under the balances section, click on the feature you want to modify. If there are [stacked balances](/documentation/concepts/balances#balance-stacking), choose the one you want to modify. 3. Choose whether to **set the balance** to a specific value or **add to the balance** 4. Enter the amount and save diff --git a/apps/docs/mintlify/documentation/customers/payment-flow.mdx b/apps/docs/mintlify/documentation/customers/payment-flow.mdx new file mode 100644 index 000000000..28b07e902 --- /dev/null +++ b/apps/docs/mintlify/documentation/customers/payment-flow.mdx @@ -0,0 +1,269 @@ +--- +title: "Payment Flow" +description: "Hosted checkout pages vs building your own payment flow" +--- + +When you call `billing.attach`, Autumn handles payment collection through one of two paths depending on the `redirect_mode` you choose: + +- **Hosted pages** — redirect the customer to a checkout page (Stripe or Autumn) to review and pay +- **Custom checkout** — show pricing in your own UI, then charge the customer's saved payment method directly + +```mermaid +graph TD + A["billing.attach"] --> B{"redirect_mode"} + B -->|"always (default)"| C{"Has payment method?"} + B -->|"if_required"| D{"Has payment method?"} + B -->|"never"| E["Charge directly"] + + C -->|Yes| F["Autumn Checkout URL"] + C -->|No| G["Stripe Checkout URL"] + + D -->|Yes| H["Charge directly — no redirect"] + D -->|No| G + + F -->|"Customer confirms"| I["Plan enabled"] + G -->|"Customer pays"| I + H --> I + E -->|"Success"| I + E -->|"No payment method"| J["Error: payment_method_required"] +``` + +## `redirect_mode` + +The `redirect_mode` parameter controls when `billing.attach` returns a `paymentUrl` instead of charging directly. + +| Mode | Behavior | Best for | +|---|---|---| +| `"always"` **(default)** | Always returns a `paymentUrl`. New customers go to Stripe Checkout, existing customers go to Autumn Checkout for confirmation. | Getting started quickly — just redirect and you're done | +| `"if_required"` | Only returns a `paymentUrl` when the customer has no payment method. Otherwise charges directly. | Building your own checkout UI | +| `"never"` | Never returns a `paymentUrl`. Attempts to charge the saved payment method directly. Fails if none exists. | Backend-only flows where you've already collected payment | + +## Using hosted pages + +The simplest approach: call `billing.attach` and redirect the customer to the returned `paymentUrl`. Autumn handles the rest. + + + +```typescript TypeScript +const response = await autumn.billing.attach({ + customerId: "user_123", + planId: "pro", +}); + +redirect(response.paymentUrl); +``` + +```tsx React +import { useCustomer } from "autumn-js/react"; + +const { attach } = useCustomer(); + +// Automatically redirects the customer +await attach({ planId: "pro" }); +``` + +```python Python +response = await autumn.billing.attach( + customer_id="user_123", + plan_id="pro", +) +# Redirect to response.payment_url +``` + +```bash cURL +curl -X POST 'https://api.useautumn.com/v1/billing.attach' \ + -H 'Authorization: Bearer am_sk_...' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro" + }' +``` + + + +Where the customer lands depends on their situation: + +| Scenario | Checkout type | What happens | +|---|---|---| +| New subscription, no payment method | **Stripe Checkout** | Customer enters card details and pays | +| New subscription, has payment method | **Autumn Checkout** | Customer reviews the plan and confirms | +| Upgrade or downgrade | **Autumn Checkout** | Customer reviews prorated charges and confirms | + +After checkout completes, the customer is redirected to your `success_url` (or the default URL configured in your Autumn dashboard). + + +Pass a `successUrl` to control where the customer returns after checkout: + +```typescript +await autumn.billing.attach({ + customerId: "user_123", + planId: "pro", + successUrl: "https://your-app.com/billing?success=true", +}); +``` + + +## Building your own checkout + +For full control over the UI, use a two-step flow: preview the change, show it in your own interface, then execute it. + +### Step 1: Preview the charge + +Call `billing.previewAttach` to get a breakdown of what the customer will be charged — line items, totals, proration credits, and next billing cycle info. + + + +```typescript TypeScript +const preview = await autumn.billing.previewAttach({ + customerId: "user_123", + planId: "pro", +}); + +// preview.lineItems — array of charges and credits +// preview.total — net amount in cents +// preview.currency — e.g. "usd" +// preview.nextCycle — next billing cycle details +``` + +```tsx React +import { useCustomer } from "autumn-js/react"; + +const { previewAttach } = useCustomer(); + +const preview = await previewAttach({ planId: "pro" }); +``` + +```python Python +preview = await autumn.billing.preview_attach( + customer_id="user_123", + plan_id="pro", +) +# preview.line_items, preview.total, preview.currency +``` + +```bash cURL +curl -X POST 'https://api.useautumn.com/v1/billing.preview_attach' \ + -H 'Authorization: Bearer am_sk_...' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro" + }' +``` + + + + +```json +{ + "customerId": "user_123", + "lineItems": [ + { + "title": "Pro Plan", + "description": "Monthly subscription", + "amount": 20 + }, + { + "title": "Credit for Free Plan", + "description": "Unused time on current plan", + "amount": -5 + } + ], + "total": 15, + "currency": "usd", + "nextCycle": { + "startsAt": 1735689600000, + "total": 20 + } +} +``` + + +### Step 2: Confirm and charge + +Once the customer confirms in your UI, call `billing.attach` with `redirectMode: "if_required"`. This charges the saved payment method directly — no redirect needed unless the customer doesn't have one yet. + + + +```typescript TypeScript +const response = await autumn.billing.attach({ + customerId: "user_123", + planId: "pro", + redirectMode: "if_required", +}); + +if (response.paymentUrl) { + // No payment method on file — redirect to Stripe Checkout + redirect(response.paymentUrl); +} else { + // Charged successfully, plan is active + showSuccess(); +} +``` + +```tsx React +import { useCustomer } from "autumn-js/react"; + +const { attach } = useCustomer(); + +const response = await attach({ + planId: "pro", + redirectMode: "if_required", +}); + +// If no redirect happened, the plan was attached +``` + +```python Python +response = await autumn.billing.attach( + customer_id="user_123", + plan_id="pro", + redirect_mode="if_required", +) + +if response.payment_url: + # Redirect to Stripe Checkout + pass +else: + # Plan attached and charged + pass +``` + +```bash cURL +curl -X POST 'https://api.useautumn.com/v1/billing.attach' \ + -H 'Authorization: Bearer am_sk_...' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "redirect_mode": "if_required" + }' +``` + + + +## Understanding the response + +Every `billing.attach` call returns the same response shape. The key fields to handle: + +### `payment_url` + +A URL the customer should be redirected to, or `null` if no redirect is needed. + +| Value | Meaning | +|---|---| +| Autumn Checkout URL | Customer needs to review and confirm the change | +| Stripe Checkout URL | Customer needs to enter payment details | +| Stripe Invoice URL | Payment requires action (3DS, retry) | +| `null` | Payment succeeded — no redirect needed | + +### `required_action` + +Present when the payment couldn't be processed automatically. See [Edge Cases](/documentation/customers/billing/edge-cases) for handling each code. + +| Code | Meaning | +|---|---| +| `3ds_required` | Customer must complete 3D Secure authentication at the `payment_url` | +| `payment_failed` | Card was declined — `payment_url` links to Stripe's hosted invoice to retry | +| `payment_method_required` | No payment method on file — `payment_url` links to Stripe Checkout | diff --git a/apps/docs/mintlify/documentation/customers/subscription-lifecycle.mdx b/apps/docs/mintlify/documentation/customers/subscription-lifecycle.mdx new file mode 100644 index 000000000..9cc6afeb8 --- /dev/null +++ b/apps/docs/mintlify/documentation/customers/subscription-lifecycle.mdx @@ -0,0 +1,255 @@ +--- +title: "Subscription Lifecycle" +description: "Handle upgrades, downgrades, and cancellations" +--- + +## Upgrades + +Upgrades happen when you attach a plan with a higher price than the customer's current plan. Use `billing.attach` — Autumn handles the rest. + + +If a payment method exists, attaching the plan will immediately charge the customer. If upgrading from a free to a paid plan, a checkout URL is generated instead. + + +**Pricing behavior:** + +- **Fixed prices** are prorated based on time remaining in the billing period +- **Usage-based prices** bill outstanding usage at the old rate immediately, then apply the new rate going forward + +## Downgrades + +Downgrades happen when you attach a plan with a lower price. Unlike upgrades, downgrades are **scheduled** to take effect at the end of the current billing period. + +The new plan will have status `scheduled` until it activates. Customers can cancel a scheduled downgrade by re-attaching their current plan. + + +If you've set a [`group`](/documentation/concepts/plans#plan-properties) when creating plans, upgrades and downgrades only apply between plans in the same group. Attaching a plan from a different group adds it alongside the existing plan. + + +## Cancellations + +Cancel a subscription using `billing.update` with the `cancelAction` parameter. By default, cancellations take effect at the end of the billing period. + +### Cancel at end of billing period + + + +```tsx React +import { useCustomer } from "autumn-js/react"; + +const { updateSubscription } = useCustomer(); + +await updateSubscription({ + planId: "pro", + cancelAction: "cancel_end_of_cycle", +}); +``` + +```typescript TypeScript +const response = await autumn.billing.update({ + customerId: "user_123", + planId: "pro", + cancelAction: "cancel_end_of_cycle", +}); +``` + +```python Python +response = await autumn.billing.update( + customer_id="user_123", + plan_id="pro", + cancel_action="cancel_end_of_cycle", +) +``` + +```bash cURL +curl -X POST 'https://api.useautumn.com/v1/billing.update' \ + -H 'Authorization: Bearer am_sk_...' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "cancel_action": "cancel_end_of_cycle" + }' +``` + + + +The subscription remains active until the end of the current billing period. If you have a default plan (with `is_default` set), it will be activated after the cancellation takes effect. + +### Cancel immediately + +To cancel a subscription right away with a prorated refund: + + + +```tsx React +import { useCustomer } from "autumn-js/react"; + +const { updateSubscription } = useCustomer(); + +await updateSubscription({ + planId: "pro", + cancelAction: "cancel_immediately", +}); +``` + +```typescript TypeScript +const response = await autumn.billing.update({ + customerId: "user_123", + planId: "pro", + cancelAction: "cancel_immediately", +}); +``` + +```python Python +response = await autumn.billing.update( + customer_id="user_123", + plan_id="pro", + cancel_action="cancel_immediately", +) +``` + +```bash cURL +curl -X POST 'https://api.useautumn.com/v1/billing.update' \ + -H 'Authorization: Bearer am_sk_...' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "cancel_action": "cancel_immediately" + }' +``` + + + +This ends the subscription immediately and issues a prorated refund for the remaining time in the billing period. + +## Uncanceling + +If a subscription was scheduled for cancellation (via `cancel_end_of_cycle`), you can reverse it before the period ends using `uncancel`: + + + +```tsx React +import { useCustomer } from "autumn-js/react"; + +const { updateSubscription } = useCustomer(); + +await updateSubscription({ + planId: "pro", + cancelAction: "uncancel", +}); +``` + +```typescript TypeScript +const response = await autumn.billing.update({ + customerId: "user_123", + planId: "pro", + cancelAction: "uncancel", +}); +``` + +```python Python +response = await autumn.billing.update( + customer_id="user_123", + plan_id="pro", + cancel_action="uncancel", +) +``` + +```bash cURL +curl -X POST 'https://api.useautumn.com/v1/billing.update' \ + -H 'Authorization: Bearer am_sk_...' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "cancel_action": "uncancel" + }' +``` + + + +This clears the pending cancellation and the subscription continues as normal. Any default plan that was scheduled to activate after cancellation is also removed. + + +You cannot uncancel a subscription that was already canceled immediately — only pending cancellations (scheduled for end of cycle) can be reversed. + + +## Canceling a scheduled plan change + +When a downgrade or other plan change is **scheduled** for the end of the billing period, canceling it works the same way as uncanceling. Call `billing.update` with `cancelAction: "uncancel"` on the customer's **active** plan: + + + +```tsx React +import { useCustomer } from "autumn-js/react"; + +const { updateSubscription } = useCustomer(); + +// Customer is on Pro with a scheduled downgrade to Basic. +// Cancel the scheduled change and keep Pro. +await updateSubscription({ + planId: "pro", + cancelAction: "uncancel", +}); +``` + +```typescript TypeScript +// Customer is on Pro with a scheduled downgrade to Basic. +// Cancel the scheduled change and keep Pro. +const response = await autumn.billing.update({ + customerId: "user_123", + planId: "pro", + cancelAction: "uncancel", +}); +``` + +```python Python +# Customer is on Pro with a scheduled downgrade to Basic. +# Cancel the scheduled change and keep Pro. +response = await autumn.billing.update( + customer_id="user_123", + plan_id="pro", + cancel_action="uncancel", +) +``` + +```bash cURL +# Customer is on Pro with a scheduled downgrade to Basic. +# Cancel the scheduled change and keep Pro. +curl -X POST 'https://api.useautumn.com/v1/billing.update' \ + -H 'Authorization: Bearer am_sk_...' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "cancel_action": "uncancel" + }' +``` + + + +This removes the scheduled replacement plan and keeps the customer on their current plan. + +## `cancel_action` reference + +| Value | Behavior | +|---|---| +| `cancel_end_of_cycle` | Schedules cancellation at the end of the current billing period. Subscription stays active until then. | +| `cancel_immediately` | Cancels immediately with a prorated refund for remaining time. | +| `uncancel` | Reverses a pending cancellation or removes a scheduled plan change. | + +## Usage reset behavior + +When a new plan is enabled, you can control what happens to existing feature usage with the `reset_usage_when_enabled` property on the plan item: + +- `true`: Usage resets to 0 (typical for consumable features like credits) +- `false`: Usage carries over to the new plan (typical for continuous features like seats) + + +**Example:** A customer on Free has used 20 of their 100 credits. They upgrade to Pro which includes 500 credits. + +- If `reset_usage_when_enabled = true`: They get 500 credits +- If `reset_usage_when_enabled = false`: They get 480 credits (500 - 20 used) + diff --git a/apps/docs/mintlify/documentation/customers/subscriptions/accepting-payments.mdx b/apps/docs/mintlify/documentation/customers/subscriptions/accepting-payments.mdx deleted file mode 100644 index 7f6439f7e..000000000 --- a/apps/docs/mintlify/documentation/customers/subscriptions/accepting-payments.mdx +++ /dev/null @@ -1,168 +0,0 @@ ---- -title: "Accepting Payments" -description: "How to handle the Stripe payment flow with Autumn" ---- - -Accepting payments is a two-step process: - -1. **`checkout`** - Gets checkout information (either a Stripe Checkout URL or purchase confirmation data) -2. **`attach`** - Enables the product and charges a saved payment method - -```mermaid -graph TD - A(("checkout")) -->|"url"| B["Stripe Checkout"] - A -->|"preview"| C["Display preview info"] - B --> D["Payment complete"] - D --> E["Plan enabled"] - C --> F["User confirms"] - F --> G(("attach")) - G --> D -``` - -## Checkout - -Call `checkout` when a customer wants to purchase a product. If no payment method is on file, a Stripe Checkout URL is returned. Otherwise, preview data (prices, proration info) is returned for the customer to confirm. - - - -```tsx React -import { useCustomer, CheckoutDialog } from "autumn-js/react"; - -const { checkout } = useCustomer(); - - + ) : null; +} +``` + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_test_1234" }); + +const customer = await autumn.customers.getOrCreate({ + customerId: "user_123", +}); + +const cancellingSub = customer.subscriptions?.find( + (sub) => sub.status === "active" && sub.canceledAt !== null +); + +if (cancellingSub) { + await autumn.billing.update({ + customerId: "user_123", + planId: cancellingSub.planId, + cancelAction: "uncancel", + }); +} +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_test_1234") + +customer = await autumn.customers.get_or_create( + customer_id="user_123" +) + +cancelling_sub = next( + (s for s in customer.subscriptions + if s.status == "active" and s.canceled_at is not None), + None, +) + +if cancelling_sub: + await autumn.billing.update( + customer_id="user_123", + plan_id=cancelling_sub.plan_id, + cancel_action="uncancel", + ) +``` + +```bash cURL +curl -X POST 'https://api.useautumn.com/v1/billing/update' \ + -H 'Authorization: Bearer am_sk_test_1234' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "cancel_action": "uncancel" + }' +``` + + + ### Stripe billing portal The Stripe billing portal lets users manage their payment method, view past invoices, and cancel their plan. diff --git a/apps/docs/mintlify/documentation/getting-started/gating.mdx b/apps/docs/mintlify/documentation/getting-started/gating.mdx index 05707c72a..482090d39 100644 --- a/apps/docs/mintlify/documentation/getting-started/gating.mdx +++ b/apps/docs/mintlify/documentation/getting-started/gating.mdx @@ -22,26 +22,6 @@ Check if a user has enough remaining balance of messages, before executing the a -```jsx React -import { useCustomer } from "autumn-js/react"; - -export function SendChatMessage() { - const { check, refetch } = useCustomer(); - - const handleSendMessage = async () => { - const { allowed } = check({ featureId: "messages" }); - - if (!allowed) { - alert("You're out of messages"); - } else { - // Send chatbot message - // Then refresh customer usage data - await refetch(); - } - }; -} -``` - ```typescript TypeScript import { Autumn } from "autumn-js"; @@ -95,10 +75,30 @@ curl -X POST 'https://api.useautumn.com/v1/check' \ -When using [React hooks](/react/hooks/useCustomer), you have access to the [`data`](/react/hooks/useCustomer#data) object, which you can use to [display billing data](/documentation/getting-started/display-billing) to your users (subscription status, feature permissions, usage balances, etc). +When using [React hooks](/react/hooks/useCustomer), you have access to the [`customer`](/react/hooks/useCustomer#data) object, which you can use to [display billing data](/documentation/getting-started/display-billing) to your users (subscription status, feature permissions, usage balances, etc). You can use the client-side `check` function to gate features and show paywalls. Permissions are determined by reading the local `data` state, so no call to Autumn's API is made. The "true" state should always be fetched server-side. +```jsx React +import { useCustomer } from "autumn-js/react"; + +export function SendChatMessage() { + const { check, refetch } = useCustomer(); + + const handleSendMessage = async () => { + const { allowed } = check({ featureId: "messages" }); + + if (!allowed) { + alert("You're out of messages"); + } else { + // Send chatbot message + // Then refresh customer usage data + await refetch(); + } + }; +} +``` + diff --git a/apps/docs/mintlify/documentation/getting-started/setup.mdx b/apps/docs/mintlify/documentation/getting-started/setup.mdx index 78295c43e..cc92a788e 100644 --- a/apps/docs/mintlify/documentation/getting-started/setup.mdx +++ b/apps/docs/mintlify/documentation/getting-started/setup.mdx @@ -13,7 +13,7 @@ In this example we'll create the pricing for a premium AI chatbot. We're going t -Autumn's client-side [hooks](/react/hooks/useCustomer) allow you to handle billing directly from your frontend. They are supported for apps with a TypeScript frontend and backend. +Autumn's client-side [hooks](/react/hooks/useCustomer) are supported for fullstack TypeScript apps. Please use the Server SDK for other frameworks. @@ -53,9 +53,6 @@ pnpm add autumn-js yarn add autumn-js ``` -```bash pip -pip install autumn-sdk -``` diff --git a/apps/docs/mintlify/documentation/modelling-pricing/add-ons.mdx b/apps/docs/mintlify/documentation/modelling-pricing/add-ons.mdx new file mode 100644 index 000000000..61de922fa --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/add-ons.mdx @@ -0,0 +1,187 @@ +--- +title: Add-Ons +description: Offer additional plans and features customers can purchase alongside their plan +--- + +Add-ons are plans that can be purchased alongside a customer's existing plan, rather than replacing it. They're used for top-ups, extra feature packs, or supplementary services. + +> **Example**
+> A customer on the Pro plan can purchase a "Storage Add-On" for an extra 100GB/month, or a one-time "Credit Top-Up" of 500 credits. + +## Setting up + + + + +Set `addOn: true` on the plan: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const storage = feature({ + id: 'storage', + name: 'Storage (GB)', + type: 'metered', + consumable: false, +}); + +export const credits = feature({ + id: 'credits', + name: 'Credits', + type: 'metered', + consumable: true, +}); + +export const storageAddOn = plan({ + id: 'storage_add_on', + name: 'Extra Storage', + addOn: true, + price: { amount: 5, interval: 'month' }, + items: [ + item({ + featureId: storage.id, + included: 100, + }), + ], +}); + +export const creditTopUp = plan({ + id: 'credit_top_up', + name: 'Credit Top-Up', + addOn: true, + items: [ + item({ + featureId: credits.id, + price: { + amount: 10, + billingUnits: 500, + billingMethod: 'prepaid', + }, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and click **Create Plan** +2. Set the plan name and ID +3. Toggle the **Add-on** flag +4. Configure the price and features as needed +5. Click **Create** + + + + +## How add-ons work + +Without the add-on flag, attaching a new plan replaces the customer's current plan (within the same [group](/documentation/concepts/plans#plan-properties)). With the add-on flag: + +- The plan is **added alongside** the customer's existing plans +- Multiple add-ons can be active at the same time +- Add-ons don't participate in upgrade/downgrade logic + +## Balance stacking + +When an add-on provides the same feature as the customer's main plan, the balances [stack](/documentation/concepts/balances#balance-stacking). Each source is tracked separately in the `breakdown` array. + +> **Example**
+> A customer's Pro plan grants 1,000 credits/month. They purchase a one-time top-up of 500 credits. Their total balance is 1,500 credits, tracked as two separate sources. + +Autumn uses [deduction order](/documentation/concepts/balances#deduction-order) to consume shorter-interval balances first (monthly before lifetime). + +## Purchasing add-ons + +Add-ons use the same checkout/attach flow as regular plans: + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +const { data } = await autumn.checkout({ + customer_id: "user_123", + plan_id: "storage_add_on", +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +response = await autumn.checkout( + customer_id="user_123", + plan_id="storage_add_on", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/checkout" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "plan_id": "storage_add_on" + }' +``` + + + +For prepaid add-ons (like a credit top-up), pass the quantity: + +```typescript TypeScript +const { data } = await autumn.checkout({ + customer_id: "user_123", + plan_id: "credit_top_up", + options: [{ + feature_id: "credits", + quantity: 1000, + }], +}); +``` + +## Cancelling add-ons + +Cancel an add-on using the same [cancel](/documentation/customers/managing-subscriptions#cancellations) flow: + + + +```typescript TypeScript +await autumn.cancel({ + customer_id: "user_123", + plan_id: "storage_add_on", +}); +``` + +```python Python +await autumn.cancel( + customer_id="user_123", + plan_id="storage_add_on", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/cancel" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "plan_id": "storage_add_on" + }' +``` + + + +## Common add-on patterns + +| Pattern | Configuration | +|---------|---------------| +| Recurring add-on | `addOn: true`, recurring price (e.g., $5/month for extra storage) | +| One-time top-up | `addOn: true`, prepaid price, no base price | +| Feature pack | `addOn: true`, grants boolean or metered features | diff --git a/apps/docs/mintlify/documentation/modelling-pricing/auto-top-ups.mdx b/apps/docs/mintlify/documentation/modelling-pricing/auto-top-ups.mdx new file mode 100644 index 000000000..baa2f51aa --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/auto-top-ups.mdx @@ -0,0 +1,161 @@ +--- +title: Auto Top-Ups +description: Automatically replenish customer balances when they run low +--- + +Auto top-ups automatically purchase additional balance for a customer when their usage drops below a configured threshold. This prevents service interruptions for customers who don't want to manually manage their balance. + +> **Example**
+> A customer has 500 credits. When their balance drops below 100, Autumn automatically purchases 500 more credits using their saved payment method. + +## Prerequisites + +Auto top-ups require: +1. A [one-off prepaid plan](/documentation/modelling-pricing/one-off-purchases) (the top-up plan) that the customer has purchased at least once +2. The customer must have a saved payment method on file + +## Setting up + + + + +Auto top-ups are configured per customer, not in `autumn.config.ts`. First, create a top-up plan: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const credits = feature({ + id: 'credits', + name: 'Credits', + type: 'metered', + consumable: true, +}); + +export const creditTopUp = plan({ + id: 'credit_top_up', + name: 'Credit Top-Up', + addOn: true, + items: [ + item({ + featureId: credits.id, + price: { + amount: 10, + billingUnits: 500, + billingMethod: 'prepaid', + }, + }), + ], +}); +``` + +Then configure auto top-ups per customer via the API (see below). + + + + +1. Navigate to the **Customers** page +2. Click on a customer +3. Under their balance for a feature, configure **Auto Top-Up**: + - **Threshold**: the balance level that triggers a top-up + - **Quantity**: how many units to purchase each time +4. The customer must have a saved payment method and have previously purchased a prepaid top-up plan for that feature + + + + +## Configuring auto top-ups via API + +Set up auto top-ups for a customer by updating their billing controls: + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +await autumn.customers.update("user_123", { + billing_controls: { + auto_topups: [{ + feature_id: "credits", + enabled: true, + threshold: 100, + quantity: 500, + }], + }, +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +await autumn.customers.update( + "user_123", + billing_controls={ + "auto_topups": [{ + "feature_id": "credits", + "enabled": True, + "threshold": 100, + "quantity": 500, + }], + }, +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/customers/user_123" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "billing_controls": { + "auto_topups": [{ + "feature_id": "credits", + "enabled": true, + "threshold": 100, + "quantity": 500 + }] + } + }' +``` + + + +## Auto top-up configuration + +| Field | Type | Description | +|-------|------|-------------| +| `feature_id` | string | The feature to monitor | +| `enabled` | boolean | Whether auto top-up is active | +| `threshold` | number | Balance level that triggers a top-up | +| `quantity` | number | How many units to purchase each time | +| `purchase_limit` | object | Optional limit on how often top-ups can occur | + +### Purchase limits + +To prevent runaway spending, you can set a purchase limit: + +```json +{ + "purchase_limit": { + "interval": "month", + "interval_count": 1, + "limit": 5 + } +} +``` + +This limits the customer to 5 auto top-ups per month. Supported intervals: `hour`, `day`, `week`, `month`. + +## How it works + +1. After every usage event (via `track`), Autumn checks the customer's remaining balance +2. If the balance falls below the configured `threshold`, an auto top-up is triggered +3. Autumn creates an invoice for the configured `quantity` using the existing prepaid top-up plan +4. The invoice is charged to the customer's saved payment method +5. The balance is replenished with the purchased amount + + +Auto top-ups use burst suppression to prevent duplicate purchases when multiple track events happen in quick succession. There's a 30-second cooldown between top-ups for the same feature. + \ No newline at end of file diff --git a/apps/docs/mintlify/documentation/pricing/credits.mdx b/apps/docs/mintlify/documentation/modelling-pricing/credit-systems.mdx similarity index 97% rename from apps/docs/mintlify/documentation/pricing/credits.mdx rename to apps/docs/mintlify/documentation/modelling-pricing/credit-systems.mdx index e9159394a..72f938b02 100644 --- a/apps/docs/mintlify/documentation/pricing/credits.mdx +++ b/apps/docs/mintlify/documentation/modelling-pricing/credit-systems.mdx @@ -5,7 +5,7 @@ description: Learn how to create a credit system in Autumn Credit systems let you track actions with different credit costs from a single balance pool. -A credit system is made up of a list of [features](/documentation/pricing/features) that can draw from it, and a credit cost per unit of usage for each feature. +A credit system is made up of a list of [features](/documentation/concepts/features) that can draw from it, and a credit cost per unit of usage for each feature. > **Example**
@@ -26,7 +26,7 @@ A credit system is made up of a list of [features](/documentation/pricing/featur system. -1. Navigate to the features page, under Products. +1. Navigate to the features page, under Plans. 2. Click "Create Credit System" 4. Add the features that can draw from this credit system. 5. For each feature, define how many credits each unit of usage should cost (eg, 3 credits per "premium request"). diff --git a/apps/docs/mintlify/documentation/modelling-pricing/free-plans.mdx b/apps/docs/mintlify/documentation/modelling-pricing/free-plans.mdx new file mode 100644 index 000000000..b4907ea72 --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/free-plans.mdx @@ -0,0 +1,135 @@ +--- +title: Free Plans +description: Offer free tiers with usage limits to onboard customers +--- + +Free plans let you give every new customer access to a limited set of features at no cost. They're the foundation of freemium models — customers start free and upgrade when they need more. + +> **Example**
+> A developer tool offers a free tier with 100 API requests per month and 1 workspace. When a user exceeds the limit, they're prompted to upgrade. + +## Setting up + + + + +Create a plan with no `price` and set `autoEnable: true`: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const apiRequests = feature({ + id: 'api_requests', + name: 'API Requests', + type: 'metered', + consumable: true, +}); + +export const workspaces = feature({ + id: 'workspaces', + name: 'Workspaces', + type: 'metered', + consumable: false, +}); + +export const free = plan({ + id: 'free', + name: 'Free', + group: 'main', + autoEnable: true, + items: [ + item({ + featureId: apiRequests.id, + included: 100, + reset: { interval: 'month' }, + }), + item({ + featureId: workspaces.id, + included: 1, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and click **Create Plan** +2. Set the plan name and ID (e.g., "Free", `free`) +3. Leave the **Price** empty (no base price) +4. Add features with grant amounts and reset intervals +5. Toggle **Auto-enable** so the plan is automatically assigned to new customers +6. Set a **group** (e.g., `main`) if you have paid plans the customer can upgrade to +7. Click **Create** + + + + +## How it works + +When `autoEnable` is set: + +1. Every new customer created via the API or SDK is automatically assigned this plan +2. Feature balances are provisioned immediately — no checkout or payment needed +3. When the customer [upgrades](/documentation/customers/managing-subscriptions#upgrades) to a paid plan in the same group, the free plan is replaced + + +If a customer cancels their paid plan and you have an auto-enabled free plan in the same group, the free plan will be re-activated automatically. + + +## Gating features + +Use the [check](/documentation/customers/check) endpoint to gate access based on the free plan's limits: + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +const { data } = await autumn.check({ + customer_id: "user_123", + feature_id: "api_requests", +}); + +if (!data.allowed) { + // Prompt user to upgrade +} +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +response = await autumn.check( + customer_id="user_123", + feature_id="api_requests", +) + +if not response.allowed: + # Prompt user to upgrade +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/check" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "feature_id": "api_requests" + }' +``` + + + +When `allowed` is `false`, the customer has exhausted their free tier balance. This is a good moment to prompt them to upgrade. + +## Tips + +- Set a **group** on your free plan that matches your paid plans, so upgrades automatically replace the free tier +- Use `reset` intervals (e.g., monthly) to give customers a recurring allowance +- Combine with [pay-per-use](/documentation/modelling-pricing/pay-per-use) to let free users optionally pay for overages diff --git a/apps/docs/mintlify/documentation/modelling-pricing/graduated-pricing.mdx b/apps/docs/mintlify/documentation/modelling-pricing/graduated-pricing.mdx new file mode 100644 index 000000000..19f35270f --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/graduated-pricing.mdx @@ -0,0 +1,106 @@ +--- +title: Graduated Pricing +description: Set tiered pricing where different usage ranges are charged at different rates +--- + +Graduated pricing splits usage into tiers, and each tier is charged at its own rate. This means the price per unit decreases as usage increases — customers pay less per unit for higher volumes, but each range has its own rate. + +> **Example**
+> An API service charges: +> - First 1,000 requests: $0.01 each +> - Next 9,000 requests (1,001–10,000): $0.008 each +> - Everything above 10,000: $0.005 each +> +> A customer who makes 15,000 requests pays: (1,000 × $0.01) + (9,000 × $0.008) + (5,000 × $0.005) = **$107** + +## Setting up + + + + +Use the `tiers` array on a plan item price. By default, tiers use graduated behavior: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const apiCalls = feature({ + id: 'api_calls', + name: 'API Calls', + type: 'metered', + consumable: true, +}); + +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 20, interval: 'month' }, + items: [ + item({ + featureId: apiCalls.id, + reset: { interval: 'month' }, + price: { + tiers: [ + { to: 1000, amount: 0.01 }, + { to: 10000, amount: 0.008 }, + { to: 'inf', amount: 0.005 }, + ], + billingMethod: 'usage_based', + interval: 'month', + }, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and create or edit a plan +2. Add a **consumable** feature +3. Under **Price**, select **Tiered** +4. The default tier behavior is **Graduated** +5. Add tiers with the **upper limit** (`to`) and **rate** (`amount`) for each range. Use `inf` for the final tier +6. Set the billing method to **Usage-based** and the billing interval +7. Save the plan + + + + +## How graduated pricing works + +At the end of the billing period, Autumn calculates the total charge by applying each tier's rate to the usage that falls within that tier's range: + +| Usage range | Rate | Charge | +|-------------|------|--------| +| 0 – 1,000 | $0.01 | 1,000 × $0.01 = $10 | +| 1,001 – 10,000 | $0.008 | 9,000 × $0.008 = $72 | +| 10,001+ | $0.005 | 5,000 × $0.005 = $25 | +| **Total** | | **$107** | + +Each tier's rate only applies to the usage **within that tier's range**. This is in contrast to [volume-based pricing](/documentation/modelling-pricing/volume-based-tiers), where a single rate is applied to the entire usage. + +## Tier configuration + +Each tier has the following fields: + +| Field | Type | Description | +|-------|------|-------------| +| `to` | number or `"inf"` | The upper boundary of this tier. Use `"inf"` for the final tier. | +| `amount` | number | Price per unit within this tier | +| `flat_amount` | number | Optional flat fee added when this tier is reached | + + +Tiers must be in ascending order by `to`. The final tier should always use `"inf"` to capture all remaining usage. + + +## Graduated vs volume-based + +| | Graduated | Volume-based | +|---|-----------|--------------| +| **Rate applied** | Each tier at its own rate | Entire usage at a single rate | +| **Total charge** | Sum of each tier's charge | Total usage × matching tier rate | +| **Best for** | Rewarding growth with lower marginal rates | Simpler pricing with volume discounts | + +See [Volume-Based Tiers](/documentation/modelling-pricing/volume-based-tiers) for the alternative model. diff --git a/apps/docs/mintlify/documentation/modelling-pricing/one-off-purchases.mdx b/apps/docs/mintlify/documentation/modelling-pricing/one-off-purchases.mdx new file mode 100644 index 000000000..1dfd92b36 --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/one-off-purchases.mdx @@ -0,0 +1,138 @@ +--- +title: One-Off Purchases +description: Configure one-time purchases and lifetime plans +--- + +One-off purchases are single-charge plans that don't recur. They're used for one-time top-ups, lifetime access plans, or any plan where the customer pays once. + +> **Example**
+> An AI platform lets users buy 500 credits for $10 as a one-time purchase. The credits never expire and can be used at any pace. + +## Setting up + + + + +Set the plan's `price.interval` to `one_off`, or omit `interval` on the item price for a one-time charge: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const credits = feature({ + id: 'credits', + name: 'Credits', + type: 'metered', + consumable: true, +}); + +export const creditTopUp = plan({ + id: 'credit_top_up', + name: 'Credit Top-Up', + addOn: true, + items: [ + item({ + featureId: credits.id, + price: { + amount: 10, + billingUnits: 500, + billingMethod: 'prepaid', + }, + }), + ], +}); +``` + + +Set `addOn: true` so the plan can be purchased alongside an existing subscription instead of replacing it. + + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and click **Create Plan** +2. Set the plan name and ID +3. Under **Price**, select **One-off** as the interval — or leave no base price if pricing is purely feature-based +4. Add a feature with a **prepaid** price. The customer will select a quantity at checkout +5. Toggle **Add-on** if this should be purchasable alongside other plans +6. Click **Create** + + + + +## How it works + +When a customer purchases a one-off plan: + +1. Autumn creates a Stripe invoice (not a subscription) and charges it immediately +2. The feature balance is provisioned with the purchased quantity +3. The balance has a `one_off` interval — it never resets or expires + + +One-off purchases don't create Stripe subscriptions. They generate a one-time invoice instead. + + +## Purchasing a one-off plan + +For prepaid one-off plans, pass the desired `quantity` via the `options` array: + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +const { data } = await autumn.checkout({ + customer_id: "user_123", + plan_id: "credit_top_up", + options: [{ + feature_id: "credits", + quantity: 1000, + }], +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +response = await autumn.checkout( + customer_id="user_123", + plan_id="credit_top_up", + options=[{ + "feature_id": "credits", + "quantity": 1000, + }], +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/checkout" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "plan_id": "credit_top_up", + "options": [{ + "feature_id": "credits", + "quantity": 1000 + }] + }' +``` + + + +## Balance stacking + +One-off balances stack with existing balances from subscriptions. Autumn uses [deduction order](/documentation/concepts/balances#deduction-order) to ensure shorter-interval balances (e.g., monthly) are used before one-off (lifetime) balances. + +## Use cases + +| Use case | Configuration | +|----------|---------------| +| Credit top-up | Prepaid price, add-on, no base price | +| Lifetime plan | One-off base price, features with no reset | +| One-time fee | One-off base price, no features | diff --git a/apps/docs/mintlify/documentation/modelling-pricing/pay-per-use.mdx b/apps/docs/mintlify/documentation/modelling-pricing/pay-per-use.mdx new file mode 100644 index 000000000..44ec13d25 --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/pay-per-use.mdx @@ -0,0 +1,183 @@ +--- +title: Pay-Per-Use +description: Bill customers based on actual usage at the end of each billing period +--- + +Pay-per-use (usage-based) pricing charges customers based on how much of a feature they actually consume, billed at the end of each billing period. This is ideal for products where usage varies significantly between customers. + +> **Example**
+> A notification service charges $1 per 1,000 notifications sent. A customer who sends 5,000 notifications in a month pays $5 at the end of that month. + +## Setting up + + + + +Create a consumable feature with a `usage_based` price: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const notifications = feature({ + id: 'notifications', + name: 'Notifications', + type: 'metered', + consumable: true, +}); + +export const payAsYouGo = plan({ + id: 'pay_as_you_go', + name: 'Pay As You Go', + group: 'main', + items: [ + item({ + featureId: notifications.id, + included: 1000, + reset: { interval: 'month' }, + price: { + amount: 1, + interval: 'month', + billingUnits: 1000, + billingMethod: 'usage_based', + }, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and create a plan +2. Add a **consumable** feature (e.g., notifications) +3. Set an optional **included** amount (free usage before charges begin) +4. Add a **price** with: + - **Billing method**: Usage-based + - **Amount**: price per billing unit (e.g., $1) + - **Billing units**: the package size (e.g., 1,000 notifications) + - **Interval**: billing frequency (e.g., monthly) +5. Save the plan + + + + +## How it works + +1. A customer's usage is tracked via the [track](/documentation/customers/tracking-usage) endpoint throughout the billing period +2. Usage first draws down from the **included** amount (if any) at no charge +3. Usage beyond the included amount is **overage** — billed at the configured rate +4. At the end of the billing period, Autumn generates a Stripe invoice for the total overage + + +Usage-based features allow overage by default. The `check` endpoint will return `allowed: true` even if the customer has exceeded their included balance, as long as a usage-based price is configured. + + +## Tracking usage + +Track usage as it occurs — Autumn accumulates it over the billing period: + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +await autumn.track({ + customer_id: "user_123", + feature_id: "notifications", + value: 500, +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +await autumn.track( + customer_id="user_123", + feature_id="notifications", + value=500, +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/track" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "feature_id": "notifications", + "value": 500 + }' +``` + + + +## Checking access + +Check if the customer can use the feature. For usage-based features with overage, `allowed` is `true` as long as the feature exists on the customer's plan: + + + +```typescript TypeScript +const { data } = await autumn.check({ + customer_id: "user_123", + feature_id: "notifications", +}); + +console.log(data.allowed); // true (overage allowed) +console.log(data.balance); +``` + +```python Python +response = await autumn.check( + customer_id="user_123", + feature_id="notifications", +) + +print(response.allowed) # True (overage allowed) +print(response.balance) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/check" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "feature_id": "notifications" + }' +``` + + + + +```json +{ + "allowed": true, + "customerId": "user_123", + "balance": { + "featureId": "notifications", + "granted": 1000, + "remaining": -500, + "usage": 1500, + "unlimited": false, + "overageAllowed": true, + "nextResetAt": 1757192635393 + } +} +``` + + +## Combining with free tiers + +A common pattern is pairing usage-based pricing with a [free plan](/documentation/modelling-pricing/free-plans). Free users are blocked when they exceed their limit, while paying users are billed for overages. + +| Plan | Over limit | Result | +|------|------------|--------| +| Free | Yes | Blocked (`allowed: false`) | +| Pay-as-you-go | Yes | Allowed, billed at end of period | diff --git a/apps/docs/mintlify/documentation/modelling-pricing/per-unit-pricing.mdx b/apps/docs/mintlify/documentation/modelling-pricing/per-unit-pricing.mdx new file mode 100644 index 000000000..3b2219186 --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/per-unit-pricing.mdx @@ -0,0 +1,212 @@ +--- +title: Per-Unit Pricing +description: Charge customers based on the number of units they use, such as seats or workspaces +--- + +Per-unit pricing charges customers based on the quantity of a resource they use — seats, workspaces, environments, or any other non-consumable feature. Customers either commit to a quantity upfront (prepaid) or are billed based on actual usage at the end of each billing cycle (usage-based). + +> **Example**
+> A collaboration tool charges $10/seat/month. The plan includes 5 seats for free, and each additional seat costs $10. + +## Setting up + + + + +Create a `non-consumable` metered feature and add it to a plan with a per-unit price: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const seats = feature({ + id: 'seats', + name: 'Seats', + type: 'metered', + consumable: false, +}); + +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 20, interval: 'month' }, + items: [ + item({ + featureId: seats.id, + included: 5, + price: { + amount: 10, + interval: 'month', + billingUnits: 1, + billingMethod: 'usage_based', + }, + proration: { + onIncrease: 'prorate', + onDecrease: 'prorate', + }, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and create or edit a plan +2. Add a `metered`, `non-consumable` feature (e.g., "Seats") +3. Set an **included** amount (e.g., 5 seats for free) +4. Add a **price** per unit (e.g., $10 per seat per month) +5. Choose the **billing method**: + - **Prepaid** — customer selects quantity at checkout, charged upfront + - **Usage-based** — billed for actual usage at end of billing cycle +6. Under **Advanced**, configure [proration](/documentation/modelling-pricing/proration) behavior for mid-cycle changes +7. Save the plan + + + + +## Billing methods + +| Method | Behavior | +|--------|----------| +| **Prepaid** | Customer commits to a quantity at checkout and pays immediately. To change quantity, they update their subscription. | +| **Usage-based** | Customer is billed for the actual number of units at the end of each billing cycle. | + +### Prepaid per-unit + +With prepaid, the customer selects a quantity when purchasing. Pass the quantity via `options`: + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +const { data } = await autumn.checkout({ + customer_id: "user_123", + plan_id: "pro", + options: [{ + feature_id: "seats", + quantity: 10, + }], +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +response = await autumn.checkout( + customer_id="user_123", + plan_id="pro", + options=[{ + "feature_id": "seats", + "quantity": 10, + }], +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/checkout" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "options": [{ + "feature_id": "seats", + "quantity": 10 + }] + }' +``` + + + +### Usage-based per-unit + +With usage-based billing, track seat additions and removals as they happen. Autumn bills the total at the end of the billing cycle. + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +await autumn.track({ + customer_id: "user_123", + feature_id: "seats", + value: 1, +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +await autumn.track( + customer_id="user_123", + feature_id="seats", + value=1, +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/track" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "feature_id": "seats", + "value": 1 + }' +``` + + + +## Checking access + +Before allowing a user to add a new seat, check if they have capacity: + + + +```typescript TypeScript +const { data } = await autumn.check({ + customer_id: "user_123", + feature_id: "seats", +}); + +if (!data.allowed) { + // Prompt user to purchase more seats +} +``` + +```python Python +response = await autumn.check( + customer_id="user_123", + feature_id="seats", +) + +if not response.allowed: + # Prompt user to purchase more seats +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/check" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "feature_id": "seats" + }' +``` + + + +## Proration on quantity changes + +When a customer increases or decreases their seat count mid-billing-cycle, you can configure how the price adjustment is handled. See [Proration](/documentation/modelling-pricing/proration) for details. diff --git a/apps/docs/mintlify/documentation/modelling-pricing/proration.mdx b/apps/docs/mintlify/documentation/modelling-pricing/proration.mdx new file mode 100644 index 000000000..1c6cb9790 --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/proration.mdx @@ -0,0 +1,112 @@ +--- +title: Proration +description: Handle mid-cycle plan changes with prorated billing +--- + +Proration adjusts billing when a customer changes their subscription mid-cycle — whether upgrading to a higher plan, downgrading, or changing the quantity of a non-consumable feature like seats. Autumn calculates the prorated amount and either charges or credits the customer. + +> **Example**
+> A customer on a $20/month plan upgrades to a $50/month plan halfway through the billing cycle. They're charged $15 (the prorated difference for the remaining half of the month). + +## Setting up + + + + +Add a `proration` config to a priced plan item: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const seats = feature({ + id: 'seats', + name: 'Seats', + type: 'metered', + consumable: false, +}); + +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 20, interval: 'month' }, + items: [ + item({ + featureId: seats.id, + included: 5, + price: { + amount: 10, + interval: 'month', + billingUnits: 1, + billingMethod: 'usage_based', + }, + proration: { + onIncrease: 'prorate', + onDecrease: 'prorate', + }, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and edit a plan +2. Select a **non-consumable** priced feature (e.g., seats) +3. Under **Advanced**, configure **Proration Behavior**: + - **On Increase**: what happens when the customer adds more units + - **On Decrease**: what happens when the customer removes units +4. Save the plan + + + + +## Proration options + +### On Increase + +| Option | Behavior | +|--------|----------| +| `prorate` | Charge the prorated difference immediately | +| `charge_immediately` | Charge the full unit price immediately (no proration) | + +### On Decrease + +| Option | Behavior | +|--------|----------| +| `prorate` | Credit the prorated difference immediately | +| `no_action` | No credit or refund — change takes effect at next billing cycle | + + +Proration is only relevant for `non-consumable` features (like seats, workspaces). Consumable features (like API requests) are billed based on usage, not quantity changes. + + +## Plan-level proration + +When a customer switches between plans (upgrade or downgrade), Autumn prorates automatically: + +- **Upgrades**: the customer is charged the prorated difference between the old and new plan prices for the remainder of the billing cycle. This happens immediately. +- **Downgrades**: the plan change is **scheduled** to take effect at the end of the current billing period. The customer continues on their current plan until then. + + +**Example** + +A customer is on a $20/month plan and upgrades to a $50/month plan on day 15 of a 30-day cycle. + +- Old plan credit: $20 × (15/30) = $10 credit +- New plan charge: $50 × (15/30) = $25 charge +- Net charge: $25 - $10 = **$15** + + +## Usage-based proration + +For usage-based prices, when a plan change occurs mid-cycle: + +1. Outstanding usage at the **old rate** is billed immediately +2. The new rate applies going forward for the rest of the billing period + +## Stripe integration + +Autumn uses Stripe's proration system under the hood. Prorated amounts appear as line items on the customer's next invoice (or are charged immediately, depending on configuration). diff --git a/apps/docs/mintlify/documentation/modelling-pricing/recurring.mdx b/apps/docs/mintlify/documentation/modelling-pricing/recurring.mdx new file mode 100644 index 000000000..7d36cb338 --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/recurring.mdx @@ -0,0 +1,253 @@ +--- +title: Recurring Plans +description: Set up recurring subscription plans for your customers +--- + +Subscriptions are the most common way to charge customers on a recurring basis. A subscription plan has a fixed base price that customers pay at a regular interval (monthly, quarterly, annually), and can include features with usage limits or additional usage-based charges. + +> **Example**
+> A project management tool offers a Pro plan at $20/month that includes 10 seats, 50GB storage, and SSO access. + +## Setting up + + + + +Define a recurring plan in your `autumn.config.ts`: + +```ts autumn.config.ts expandable +import { feature, item, plan } from 'atmn'; + +export const messages = feature({ + id: 'messages', + name: 'Messages', + type: 'metered', + consumable: true, +}); + +export const sso = feature({ + id: 'sso', + name: 'SSO', + type: 'boolean', +}); + +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 20, interval: 'month' }, + items: [ + item({ + featureId: messages.id, + included: 1000, + reset: { interval: 'month' }, + }), + item({ + featureId: sso.id, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** in the Autumn dashboard +2. Click **Create Plan** +3. Set a **name** and **ID** for the plan (e.g., "Pro", `pro`) +4. Under **Price**, set the amount and select a billing interval (`month`, `quarter`, `semi_annual`, or `year`) +5. Add features to the plan — set grant amounts, reset intervals, and prices as needed. These will be granted to the customer once they purchase the plan. +6. Save your changes + + + + +## Attaching a subscription + +Use [billing.attach](/documentation/customers/attaching-plans) to attach a subscription to a customer. With `redirectMode: "always"`, a checkout URL is always returned for the customer to complete payment or confirm the plan change. + + + +```tsx React +import { useCustomer } from "autumn-js/react"; + +const { attach } = useCustomer(); + +await attach({ planId: "pro", redirectMode: "always" }); +``` + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +const response = await autumn.billing.attach({ + customerId: "user_123", + planId: "pro", + redirectMode: "always", +}); + +// Redirect customer to complete payment or confirm plan change +redirect(response.paymentUrl); +``` + +```python Python +import asyncio +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +async def main(): + response = await autumn.billing.attach( + customer_id="user_123", + plan_id="pro", + redirect_mode="always", + ) + + # Redirect customer to response.payment_url + +asyncio.run(main()) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/attach" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "redirect_mode": "always" + }' +``` + + + + +```json +{ + "id": "user_123", + "name": "Jane Smith", + "email": "jane@example.com", + "createdAt": 1771409161016, + "fingerprint": null, + "stripeId": "cus_U0BKxpq1mFhuJO", + "env": "sandbox", + "metadata": {}, + "sendEmailReceipts": false, + "billingControls": { + "autoTopups": [] + }, + "subscriptions": [ + { + "planId": "pro", + "autoEnable": false, + "addOn": false, + "status": "active", + "pastDue": false, + "canceledAt": null, + "expiresAt": null, + "trialEndsAt": null, + "startedAt": 1771431921437, + "currentPeriodStart": 1771431921437, + "currentPeriodEnd": 1773851121437, + "quantity": 1 + } + ], + "purchases": [], + "balances": { + "messages": { + "featureId": "messages", + "granted": 1000, + "remaining": 1000, + "usage": 0, + "unlimited": false, + "overageAllowed": false, + "maxPurchase": null, + "nextResetAt": 1773851121437, + "breakdown": [ + { + "id": "cus_ent_abc123", + "planId": "pro", + "includedGrant": 1000, + "prepaidGrant": 0, + "remaining": 1000, + "usage": 0, + "unlimited": false, + "reset": { + "interval": "month", + "resetsAt": 1773851121437 + }, + "price": null, + "expiresAt": null + } + ] + }, + "sso": { + "featureId": "sso", + "granted": 1, + "remaining": 1, + "usage": 0, + "unlimited": false, + "overageAllowed": false, + "maxPurchase": null, + "nextResetAt": null, + "breakdown": [ + { + "id": "cus_ent_def456", + "planId": "pro", + "includedGrant": 1, + "prepaidGrant": 0, + "remaining": 1, + "usage": 0, + "unlimited": false, + "reset": null, + "price": null, + "expiresAt": null + } + ] + } + } +} +``` + + +When a subscription is created, Autumn: + +1. Creates a Stripe subscription with the plan's prices +2. Provisions [balances](/documentation/concepts/balances) for each feature in the plan +3. Starts the billing cycle based on the plan's interval + +## Billing intervals + +Plans support the following billing intervals: + +| Interval | Description | +|----------|-------------| +| `week` | Billed every week | +| `month` | Billed every month | +| `quarter` | Billed every 3 months | +| `semi_annual` | Billed every 6 months | +| `year` | Billed annually | + +You can create a separate plan for each interval you want to support. For example, if you want to support monthly and annual plans, you can create a `pro_monthly` plan and a `pro_annual` plan. + +You can also configure a custom `interval_count` to charge at non-standard intervals (e.g., every 2 months). + +## Managing subscriptions + +Once a customer has an active subscription, you can manage upgrades, downgrades, and cancellations. See [Managing Subscriptions](/documentation/customers/s) for details on: + +- **Upgrades** — prorated charges for switching to a higher-priced plan +- **Downgrades** — scheduled at end of billing period +- **Cancellations** — immediate or end-of-period + +## Subscription statuses + +| Status | Description | +|--------|-------------| +| `active` | Subscription is in good standing | +| `trialing` | Customer is in a [free trial](/documentation/modelling-pricing/trials) period | +| `past_due` | Payment failed, needs attention | +| `scheduled` | Will activate at end of current billing period (e.g., downgrade) | +| `expired` | Subscription has ended | \ No newline at end of file diff --git a/apps/docs/mintlify/documentation/modelling-pricing/rollovers.mdx b/apps/docs/mintlify/documentation/modelling-pricing/rollovers.mdx new file mode 100644 index 000000000..b2eca17aa --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/rollovers.mdx @@ -0,0 +1,128 @@ +--- +title: Rollovers +description: Allow unused balances to carry over to the next billing period +--- + +Rollovers let unused feature balances carry forward to the next billing cycle instead of being lost at reset. This gives customers more flexibility and prevents wasted allocation. + +> **Example**
+> A customer on a plan with 1,000 credits/month only uses 600 in January. With rollovers enabled, the remaining 400 credits carry over — giving them 1,400 credits available in February. + +## Setting up + + + + +Add a `rollover` config to a plan item: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const credits = feature({ + id: 'credits', + name: 'Credits', + type: 'metered', + consumable: true, +}); + +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 20, interval: 'month' }, + items: [ + item({ + featureId: credits.id, + included: 1000, + reset: { interval: 'month' }, + rollover: { + max: 2000, + expiryDurationType: 'forever', + expiryDurationLength: 1, + }, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and edit a plan +2. Select a **consumable** feature on the plan +3. Under **Advanced**, toggle on **Rollovers** +4. Set the **maximum rollover cap** — the most unused balance that can be carried over (leave empty for no cap) +5. Set the **expiry**: + - **Forever** — rollover balances never expire + - **Month** — rollover balances expire after a set number of months +6. Save the plan + + + + +## Rollover configuration + +| Field | Description | +|-------|-------------| +| `max` | Maximum amount that can roll over. Set to `null` for no cap. | +| `expiryDurationType` | `"forever"` (never expires) or `"month"` (expires after N months) | +| `expiryDurationLength` | Number of months until rollover balances expire. Ignored if type is `"forever"`. | + +## How rollovers work + +At the end of each billing cycle, when a feature's balance resets: + +1. Autumn checks how much unused balance remains +2. If rollovers are configured, the unused balance is saved as a **rollover balance** +3. The feature resets to its granted amount, and the rollover is added on top +4. If a `max` cap is set, the oldest rollover balances are trimmed first (FIFO) +5. Expired rollover balances are removed automatically + +## Viewing rollover balances + +Rollover balances appear in the `breakdown` array when you retrieve a customer's balances. Each rollover entry has its own expiry date: + + +```json +{ + "balances": { + "credits": { + "included_usage": 1400, + "balance": 1400, + "usage": 0, + "breakdown": [ + { + "plan_id": "pro", + "included_usage": 1000, + "balance": 1000, + "usage": 0, + "interval": "month", + "next_reset_at": 1745193600000 + }, + { + "id": "roll_abc123", + "included_usage": 400, + "balance": 400, + "usage": 0, + "interval": "one_off", + "expires_at": null + } + ] + } + } +} +``` + + +## Deduction order + +Rollover balances are treated as `one_off` (lifetime) balances. Because Autumn's [deduction order](/documentation/concepts/balances#deduction-order) uses shorter intervals first, monthly balances are consumed before rollover balances — ensuring that new allocation is used before carried-over amounts. + + +Rollovers are only available on `consumable` features with a reset interval. Non-consumable features (like seats) don't reset and therefore don't support rollovers. + + +## Entity rollovers + +If you're using [sub-entity balances](/documentation/modelling-pricing/sub-entity-balances), rollovers are tracked per entity. Each entity's unused balance rolls over independently. diff --git a/apps/docs/mintlify/documentation/modelling-pricing/sub-entity-balances.mdx b/apps/docs/mintlify/documentation/modelling-pricing/sub-entity-balances.mdx new file mode 100644 index 000000000..eafceafc6 --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/sub-entity-balances.mdx @@ -0,0 +1,240 @@ +--- +title: Sub-Entity Balances +description: Grant individual usage limits to entities like users or workspaces under a customer +--- + +Sub-entity balances let you set usage limits that apply to each entity (user, workspace, project, etc.) individually under a parent customer. Instead of a single shared pool, each entity gets its own independent balance. + +> **Example**
+> A team plan costs $30/seat/month. Each seat gets 50 AI meeting summaries per month. If a team has 5 users, each user has their own balance of 50 summaries — they can't use each other's allocation. + +## Setting up + + + + +Create features for both the entity count (e.g., seats) and the per-entity feature (e.g., summaries). Then link them via `entityFeatureId` on the plan item: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const seats = feature({ + id: 'seats', + name: 'Seats', + type: 'metered', + consumable: false, +}); + +export const summaries = feature({ + id: 'summaries', + name: 'Meeting Summaries', + type: 'metered', + consumable: true, +}); + +export const team = plan({ + id: 'team', + name: 'Team', + price: { amount: 30, interval: 'month' }, + items: [ + item({ + featureId: seats.id, + included: 1, + price: { + amount: 30, + interval: 'month', + billingUnits: 1, + billingMethod: 'usage_based', + }, + }), + item({ + featureId: summaries.id, + included: 50, + reset: { interval: 'month' }, + entityFeatureId: seats.id, + }), + ], +}); +``` + +The `entityFeatureId` links the summaries feature to the seats feature — each seat (entity) will receive its own balance of 50 summaries. + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and create or edit a plan +2. Add the entity feature (e.g., "Seats") — this is the feature that counts entities +3. Add the per-entity feature (e.g., "Meeting Summaries") +4. Under **Advanced** for the per-entity feature, set **Entity Feature** to point to the entity feature (e.g., "Seats") +5. Set the grant amount and reset interval for the per-entity feature +6. Save the plan + + + + +## Creating entities + +When a new team member joins, create an entity. This automatically increments the entity feature count (e.g., seats) and provisions the per-entity balance: + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +await autumn.entities.create({ + customerId: "org_123", + entityId: "user_alice", + featureId: "seats", + name: "Alice", +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +await autumn.entities.create( + customer_id="org_123", + entity_id="user_alice", + feature_id="seats", + name="Alice", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/entities" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "org_123", + "entity_id": "user_alice", + "feature_id": "seats", + "name": "Alice" + }' +``` + + + +## Checking and tracking per entity + +Pass the `entity_id` to `check` and `track` to operate on a specific entity's balance: + +#### Check access + + + +```typescript TypeScript +const { data } = await autumn.check({ + customer_id: "org_123", + feature_id: "summaries", + entity_id: "user_alice", +}); + +console.log(data.allowed); +console.log(data.balance); +``` + +```python Python +response = await autumn.check( + customer_id="org_123", + feature_id="summaries", + entity_id="user_alice", +) + +print(response.allowed) +print(response.balance) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/check" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "org_123", + "feature_id": "summaries", + "entity_id": "user_alice" + }' +``` + + + +#### Track usage + + + +```typescript TypeScript +await autumn.track({ + customer_id: "org_123", + feature_id: "summaries", + entity_id: "user_alice", + value: 1, +}); +``` + +```python Python +await autumn.track( + customer_id="org_123", + feature_id="summaries", + entity_id="user_alice", + value=1, +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/track" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "org_123", + "feature_id": "summaries", + "entity_id": "user_alice", + "value": 1 + }' +``` + + + +## Customer-level vs entity-level + +| Level | How to use | Behavior | +|-------|-----------|----------| +| **Entity-level** | Pass `entity_id` in check/track | Checks/deducts from that entity's individual balance | +| **Customer-level** | Omit `entity_id` | Returns the total balance across all entities | + + +When tracking at the customer level (without `entity_id`), usage is deducted from the first-created entity to keep entity-level totals in sync with the customer-level total. + + +## Deleting entities + +When a team member leaves, delete the entity. This decrements the entity feature count and removes their balance: + + + +```typescript TypeScript +await autumn.entities.delete({ + customerId: "org_123", + entityId: "user_alice", +}); +``` + +```python Python +await autumn.entities.delete( + customer_id="org_123", + entity_id="user_alice", +) +``` + +```bash cURL +curl -X DELETE "https://api.useautumn.com/v1/entities/user_alice" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ "customer_id": "org_123" }' +``` + + diff --git a/apps/docs/mintlify/documentation/modelling-pricing/sub-entity-plans.mdx b/apps/docs/mintlify/documentation/modelling-pricing/sub-entity-plans.mdx new file mode 100644 index 000000000..4f3bced93 --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/sub-entity-plans.mdx @@ -0,0 +1,270 @@ +--- +title: Sub-Entity Plans +description: Assign separate plans to entities under a parent customer +--- + +Sub-entity plans let you attach different subscription tiers to individual entities (users, workspaces, projects) under a parent customer. Each entity can be on a different plan with its own features and billing, while the parent customer handles payment. + +> **Example**
+> A company manages multiple workspaces. Each workspace can be on a different tier — Workspace A is on the Free tier (100 requests/month), while Workspace B is on the Pro tier (10,000 requests/month). Both are billed to the parent organization. + +## When to use sub-entity plans + +Use sub-entity plans when entities under a customer need **different plan tiers**. If all entities get the same features and limits, use [sub-entity balances](/documentation/modelling-pricing/sub-entity-balances) instead. + +| Scenario | Approach | +|----------|----------| +| All seats get the same 50 credits/month | Sub-entity balances | +| Each seat can be Free or Pro tier | Sub-entity plans | + +## Setting up + + + + +Create your plans as normal — no special entity configuration needed on the plan itself. The entity-level attachment happens at runtime via the API. + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const requests = feature({ + id: 'requests', + name: 'API Requests', + type: 'metered', + consumable: true, +}); + +export const workspaceFree = plan({ + id: 'workspace_free', + name: 'Workspace Free', + group: 'workspace', + items: [ + item({ + featureId: requests.id, + included: 100, + reset: { interval: 'month' }, + }), + ], +}); + +export const workspacePro = plan({ + id: 'workspace_pro', + name: 'Workspace Pro', + group: 'workspace', + price: { amount: 20, interval: 'month' }, + items: [ + item({ + featureId: requests.id, + included: 10000, + reset: { interval: 'month' }, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Create your plan tiers as normal (e.g., "Workspace Free", "Workspace Pro") +2. Set the same **group** on plans that should replace each other on upgrade/downgrade +3. Entity-level attachment is handled via the API — no special dashboard configuration needed + + + + +## Attaching plans to entities + +First, create the entity. Then attach a plan to it by passing the `entity_id` in the attach call: + +#### Create the entity + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +await autumn.entities.create({ + customerId: "org_123", + entityId: "workspace_a", + featureId: "workspaces", + name: "Workspace A", +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +await autumn.entities.create( + customer_id="org_123", + entity_id="workspace_a", + feature_id="workspaces", + name="Workspace A", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/entities" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "org_123", + "entity_id": "workspace_a", + "feature_id": "workspaces", + "name": "Workspace A" + }' +``` + + + +#### Attach a plan to the entity + + + +```typescript TypeScript +const response = await autumn.billing.attach({ + customerId: "org_123", + planId: "workspace_pro", + entityId: "workspace_a", +}); +``` + +```python Python +response = await autumn.billing.attach( + customer_id="org_123", + plan_id="workspace_pro", + entity_id="workspace_a", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/billing.attach" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "org_123", + "plan_id": "workspace_pro", + "entity_id": "workspace_a" + }' +``` + + + +Each entity's subscription is created separately in Stripe, with billing cycles synced to the parent customer. + +## Checking and tracking per entity + +Pass the `entity_id` to scope `check` and `track` calls to a specific entity: + + + +```typescript TypeScript +const { data } = await autumn.check({ + customer_id: "org_123", + feature_id: "requests", + entity_id: "workspace_a", +}); + +console.log(data.allowed); +``` + +```python Python +response = await autumn.check( + customer_id="org_123", + feature_id="requests", + entity_id="workspace_a", +) + +print(response.allowed) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/check" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "org_123", + "feature_id": "requests", + "entity_id": "workspace_a" + }' +``` + + + +## Upgrading an entity's plan + +To upgrade or downgrade an entity, attach the new plan with the same `entity_id`. The same [upgrade/downgrade](/documentation/customers/billing/subscription-lifecycle) logic applies: + + + +```typescript TypeScript +await autumn.billing.attach({ + customerId: "org_123", + planId: "workspace_pro", + entityId: "workspace_a", +}); +``` + +```python Python +await autumn.billing.attach( + customer_id="org_123", + plan_id="workspace_pro", + entity_id="workspace_a", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/billing.attach" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "org_123", + "plan_id": "workspace_pro", + "entity_id": "workspace_a" + }' +``` + + + +## Cancelling an entity's plan + +Use `billing.update` with `cancelAction` to cancel an entity's plan. The same [cancel/uncancel](/documentation/customers/billing/subscription-lifecycle#cancellations) behavior applies. + + + +```typescript TypeScript +await autumn.billing.update({ + customerId: "org_123", + planId: "workspace_pro", + entityId: "workspace_a", + cancelAction: "cancel_end_of_cycle", +}); +``` + +```python Python +await autumn.billing.update( + customer_id="org_123", + plan_id="workspace_pro", + entity_id="workspace_a", + cancel_action="cancel_end_of_cycle", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/billing.update" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "org_123", + "plan_id": "workspace_pro", + "entity_id": "workspace_a", + "cancel_action": "cancel_end_of_cycle" + }' +``` + + \ No newline at end of file diff --git a/apps/docs/mintlify/documentation/modelling-pricing/trials.mdx b/apps/docs/mintlify/documentation/modelling-pricing/trials.mdx new file mode 100644 index 000000000..5ba2f152f --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/trials.mdx @@ -0,0 +1,224 @@ +--- +title: Trials +description: Let customers try paid plans before committing +--- + +Free trials give customers temporary access to a paid plan before they're charged. Autumn supports two trial modes: **card required** (collect payment info upfront, bill when trial ends) and **card not required** (no payment info needed, access expires automatically). + +> **Example**
+> A SaaS tool offers a 14-day free trial of their Pro plan. If the customer doesn't cancel, billing begins on day 15. + +## Setting up + + + + +Add a `freeTrial` object to your plan: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const messages = feature({ + id: 'messages', + name: 'Messages', + type: 'metered', + consumable: true, +}); + +export const pro = plan({ + id: 'pro', + name: 'Pro', + group: 'main', + price: { amount: 20, interval: 'month' }, + freeTrial: { + durationLength: 14, + durationType: 'day', + cardRequired: true, + }, + items: [ + item({ + featureId: messages.id, + included: 1000, + reset: { interval: 'month' }, + }), + ], +}); +``` + +Trial duration types: `day`, `month`, `year`. + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and open your plan (or create a new one) +2. Under **Plan Settings**, toggle on **Free Trial** +3. Set the **duration** (e.g., 14 days) +4. Choose whether a **card is required**: + - **Card required**: customer goes through Stripe Checkout, but isn't charged until the trial ends + - **Card not required**: no checkout needed — the plan can be attached directly +5. Save your changes + + + + +## Card required trials + +When `cardRequired` is `true`, the customer must provide payment information to start the trial. Stripe creates a subscription with a trial period — no charge occurs until the trial ends. + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +const { data } = await autumn.checkout({ + customer_id: "user_123", + plan_id: "pro", +}); + +// Returns Stripe Checkout URL — customer adds card and starts trial +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +response = await autumn.checkout( + customer_id="user_123", + plan_id="pro", +) +# Returns Stripe Checkout URL +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/checkout" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro" + }' +``` + + + +If the customer doesn't cancel before the trial ends, their card is automatically charged. + +## Card not required trials + +When `cardRequired` is `false`, no checkout is needed. You can attach the plan directly: + + + +```typescript TypeScript +const { data } = await autumn.attach({ + customer_id: "user_123", + plan_id: "pro", +}); +``` + +```python Python +response = await autumn.attach( + customer_id="user_123", + plan_id="pro", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/attach" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro" + }' +``` + + + +When the trial expires, the customer loses access unless they add a payment method. If a [free plan](/documentation/modelling-pricing/free-plans) with `autoEnable` exists in the same group, it's activated as a fallback. + + +You can combine `autoEnable` with `cardRequired: false` to create an **auto-trial** plan. The trial starts automatically when a customer is created, and expires after the trial period. + + +## Trial deduplication + +Each customer can only use a plan's trial **once**. If they try to attach the same plan again, the trial is skipped and they're billed immediately. + +### Fingerprint-based deduplication + +To prevent trial abuse across multiple accounts, set a `fingerprint` when creating a customer (e.g., device ID, browser fingerprint). Autumn checks whether any customer with the same fingerprint has already used the trial. + + + +```typescript TypeScript +await autumn.customers.create({ + id: "user_456", + name: "Jane Doe", + email: "jane@example.com", + fingerprint: "device_abc123", +}); +``` + +```python Python +await autumn.customers.create( + id="user_456", + name="Jane Doe", + email="jane@example.com", + fingerprint="device_abc123", +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/customers" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "id": "user_456", + "name": "Jane Doe", + "email": "jane@example.com", + "fingerprint": "device_abc123" + }' +``` + + + +## Cancelling a trial + +Cancel a trial using the same [cancel](/documentation/customers/managing-subscriptions#cancellations) flow as any subscription: + + + +```typescript TypeScript +await autumn.cancel({ + customer_id: "user_123", + plan_id: "pro", + cancel_immediately: true, +}); +``` + +```python Python +await autumn.cancel( + customer_id="user_123", + plan_id="pro", + cancel_immediately=True, +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/cancel" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "plan_id": "pro", + "cancel_immediately": true + }' +``` + + \ No newline at end of file diff --git a/apps/docs/mintlify/documentation/modelling-pricing/volume-based-tiers.mdx b/apps/docs/mintlify/documentation/modelling-pricing/volume-based-tiers.mdx new file mode 100644 index 000000000..ea0dcbc6f --- /dev/null +++ b/apps/docs/mintlify/documentation/modelling-pricing/volume-based-tiers.mdx @@ -0,0 +1,129 @@ +--- +title: Volume-Based Tiers +description: Charge a single rate based on the total volume of usage +--- + +Volume-based pricing uses tiers to determine a single rate, then applies that rate to the **entire** usage volume. Unlike [graduated pricing](/documentation/modelling-pricing/graduated-pricing), where each tier has its own rate, volume-based pricing gives the customer a single per-unit price based on the total amount consumed. + +> **Example**
+> A data platform charges: +> - 0–1,000 records: $0.10 each +> - 1,001–10,000 records: $0.08 each +> - 10,001+: $0.05 each +> +> A customer who processes 15,000 records falls into the 10,001+ tier and pays: 15,000 × $0.05 = **$750** +> +> Compare this to graduated pricing, where the same usage would cost: (1,000 × $0.10) + (9,000 × $0.08) + (5,000 × $0.05) = $1,070 + +## Setting up + + + + +Use the `tiers` array with `tierBehavior: 'volume'` on a plan item price: + +```ts autumn.config.ts +import { feature, item, plan } from 'atmn'; + +export const records = feature({ + id: 'records', + name: 'Records Processed', + type: 'metered', + consumable: true, +}); + +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 50, interval: 'month' }, + items: [ + item({ + featureId: records.id, + reset: { interval: 'month' }, + price: { + tiers: [ + { to: 1000, amount: 0.10 }, + { to: 10000, amount: 0.08 }, + { to: 'inf', amount: 0.05 }, + ], + tierBehavior: 'volume', + billingMethod: 'usage_based', + interval: 'month', + }, + }), + ], +}); +``` + +Push changes with `atmn push`. + + + + +1. Navigate to **Plans** and create or edit a plan +2. Add a **consumable** feature +3. Under **Price**, select **Tiered** +4. Switch the tier behavior to **Volume** +5. Add tiers with the upper limit (`to`) and rate (`amount`) for each range +6. Set the billing method to **Usage-based** and the billing interval +7. Save the plan + + + + +## How volume-based pricing works + +At the end of the billing period, Autumn: + +1. Looks at the total usage for the feature +2. Finds the tier the total falls into +3. Applies that single tier's rate to the **entire** usage + +| Total usage | Matching tier | Rate | Charge | +|-------------|---------------|------|--------| +| 500 | 0–1,000 | $0.10 | 500 × $0.10 = **$50** | +| 5,000 | 1,001–10,000 | $0.08 | 5,000 × $0.08 = **$400** | +| 15,000 | 10,001+ | $0.05 | 15,000 × $0.05 = **$750** | + +## Tier configuration + +Each tier has the following fields: + +| Field | Type | Description | +|-------|------|-------------| +| `to` | number or `"inf"` | The upper boundary of this tier | +| `amount` | number | Price per unit when total usage falls in this tier | +| `flat_amount` | number | Optional flat fee added when this tier is the matching tier | + + +Tiers must be in ascending order by `to`. The final tier should use `"inf"`. + + +## Flat amounts + +Each tier can include an optional `flat_amount` — a fixed fee added on top of the per-unit charge when that tier is the matching tier. This is useful for combining a base fee with volume pricing. + +```ts +price: { + tiers: [ + { to: 1000, amount: 0.10, flat_amount: 0 }, + { to: 10000, amount: 0.08, flat_amount: 50 }, + { to: 'inf', amount: 0.05, flat_amount: 100 }, + ], + tierBehavior: 'volume', + billingMethod: 'usage_based', + interval: 'month', +} +``` + +A customer with 5,000 records would pay: (5,000 × $0.08) + $50 = **$450** + +## Graduated vs volume-based + +| | Graduated | Volume-based | +|---|-----------|--------------| +| **Rate applied** | Each tier at its own rate | Entire usage at a single rate | +| **Total charge** | Sum of each tier's charge | Total usage × matching tier rate | +| **Best for** | Rewarding growth with lower marginal rates | Simpler pricing with volume discounts | + +See [Graduated Pricing](/documentation/modelling-pricing/graduated-pricing) for the alternative model. diff --git a/apps/docs/mintlify/welcome.mdx b/apps/docs/mintlify/welcome.mdx index 0ba055a6f..5bdeae714 100644 --- a/apps/docs/mintlify/welcome.mdx +++ b/apps/docs/mintlify/welcome.mdx @@ -72,7 +72,7 @@ Stripe has its own metered billing product, and there are others out there too l 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: +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 very different product in 2 ways: **Functionally**
Because check runs before the action, Autumn becomes your system of record for pricing and entitlements.