# Charge monthly

URL: /systems/finance/charge-monthly · Id: `guide:charge-monthly`

> Say "charge $29 a month" and get a monthly plan, checkout on your domain, a billing portal, retries for failed cards, sales tax and up-to-date books.

Charge a monthly [Price](/business/nouns/price) for an [Offer](/business/nouns/offer): each [Customer](/business/nouns/customer) holds a [Subscription](/business/nouns/subscription) that Stripe renews and collects.

Needs: [Startup](/business/nouns/startup)

**Before real money moves**

- Who says yes: You. Your agent shows you the run and waits for your [Approval](/business/nouns/approval).
- You leave the chat: Once, at Stripe: to confirm who you are and add the bank account payouts land in.
- First question at Idea: “Take the first month as a refundable deposit until it ships?”
- First question at Live: “A new plan beside the current one, or a new price for it?”
- First question at Repeatable: “Move current customers to the new price at renewal?”

## What you get
- 1 Offer with a $29 monthly Price (Offer)
- 1 monthly price set up on Stripe (Price)
- 1 checkout on your domain (Offer)
- 1 Subscription per Customer who checks out, renewed each month (Subscription)
- 1 Invoice each month, paid by the saved card (Invoice)
- 1 billing portal to change card, plan or cancel (Portal)
- 1 retry plan for failed payments, before access pauses (Process)
- 1 sales tax on every charge (Invoice)
- 2 lines in your books per charge: cash in, and revenue counted as each month is earned (Entry)

## Your agent does
**For agents**

- Guide: `guide:charge-monthly`, status `live`. Page: [/systems/finance/charge-monthly.md](/systems/finance/charge-monthly.md).
- Approval: ask the founder first. Show the run, then wait for their Approval; a refusal ends it.
- Leaves the chat: Once, at Stripe: to confirm who you are and add the bank account payouts land in.
- Needs first: [Startup](/business/nouns/startup).
- Run: call the steps in order, each through the API, SDK, CLI or MCP tab below; a later step passes the `$id` an earlier one returns. Each value marked example is the sample business’s: pass the founder’s own.

| Step | Verb | Inputs (type, required, this guide’s value) | Returns |
| --- | --- | --- | --- |
| 1 | [`Offer.create`](/business/nouns/offer/create) (`verb:Offer.create`) | `price` money, required: example `{"amount":29,"currency":"USD"}`; `terms` text: example `"Monthly, billed in advance. Cancel any time from the billing portal."`; `interval` string: example `"month"` | `offer` Offer |
| 2 | [`Offer.publish`](/business/nouns/offer/publish) (`verb:Offer.publish`) | `offer` Offer (an id), required: the `$id` step 1 returns | `offer` Offer |

Returns: the last call answers with `offer` and its `action`, whose `status` is `done`, `pending` (waiting on an Approval or a Worker), `failed`, `canceled` or `refused`:

```json
{
  "api": "https://api.sb/leaseline.app",
  "offer": {
    "$id": "https://api.sb/leaseline.app/offers/offer_1",
    "$type": "Offer",
    "price": {
      "amount": 29,
      "currency": "USD"
    },
    "interval": "month",
    "terms": "Monthly, billed in advance. Cancel any time from the billing portal."
  },
  "action": {
    "$id": "https://api.sb/leaseline.app/actions/action_1",
    "operation": "Offer.publish",
    "status": "done"
  },
  "links": {
    "self": "https://api.sb/leaseline.app/actions/action_1"
  }
}
```

Next: offer [Retry the payments that failed](/systems/finance/retry-failed-payments) (`guide:retry-failed-payments`) when `Invoice.overdue` fires, or “Add a $290 annual plan”, or “Give a 14-day free trial”.

Calls: `Offer.create` → `Offer.publish`

Ask:

```text
Charge $29 a month.
Make Leaseline Pro a $29 monthly subscription.
Bill customers every month and let them cancel on their own.
```

API:

```bash
# 1. Offer.create
OFFER=$(curl -X POST https://api.sb/leaseline.app/offers \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"price":{"amount":29,"currency":"USD"},"interval":"month","terms":"Monthly, billed in advance. Cancel any time from the billing portal."}' | jq -r '.offer["$id"] | split("/") | last')

# 2. Offer.publish
curl -X POST https://api.sb/leaseline.app/offers/$OFFER/publish \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
```

SDK:

```ts
import { $ } from 'startup-builder'

const offer = await $.Offer.create({ price: { amount: 29, currency: 'USD' }, interval: 'month', terms: 'Monthly, billed in advance. Cancel any time from the billing portal.' })
await $.Offer.publish({ offer: offer.$id })
```

CLI:

```bash
OFFER=$(npx startup-builder do Offer.create --json '{"price":{"amount":29,"currency":"USD"},"interval":"month","terms":"Monthly, billed in advance. Cancel any time from the billing portal."}' | jq -r '.offer["$id"] | split("/") | last')
npx startup-builder do Offer.publish --json '{"offer":"'"$OFFER"'"}'
```

MCP:

```json
{
  "name": "do",
  "arguments": {
    "code": "const offer = await $.Offer.create({ price: { amount: 29, currency: 'USD' }, interval: 'month', terms: 'Monthly, billed in advance. Cancel any time from the billing portal.' })\nreturn await $.Offer.publish({ offer: offer.$id })"
  }
}
```

## Change it
- Add a $290 annual plan.
- Give a 14-day free trial.
- Raise the price for new customers only.

## Under the hood
- Systems: [Billing](/systems/billing), [Ledger](/systems/ledger), [Tax](/systems/tax)
- Nouns: [Offer](/business/nouns/offer), [Price](/business/nouns/price), [Subscription](/business/nouns/subscription), [Invoice](/business/nouns/invoice), [Entry](/business/nouns/entry)
- Events: `Offer.created`, [`Offer.published`](/business/events/Offer.published), [`Offer.subscribed`](/business/events/Offer.subscribed), [`Subscription.renewed`](/business/events/Subscription.renewed)
- Stripe objects: `Product`, `Price`, `Checkout Session`, `Subscription`, `Invoice`, `Customer Portal`, `Tax Calculation`

Each Customer then subscribes at checkout, which runs [`Offer.subscribe`](/business/nouns/offer/subscribe). Revenue posts as deferred and moves to earned as each month passes. A failed renewal retries on the dunning schedule, then pauses the Customer's [Entitlements](/business/nouns/entitlement).