---
title: "Subscriptions"
url: https://services.dev/docs/money/subscriptions
captured: [grow-subscription.txt, legal-docket-watch.txt, money-subscriptions-tabs.txt, money-subscriptions.txt, tax-cases.txt, tax-money.txt, tax-plan.txt]
---
# Subscriptions

URL: https://services.dev/docs/money/subscriptions

> price.every makes an order a subscription, sbn_, charged once per cycle, and price.includes says how many jobs of each Service the cycle covers. Past an allowance, the next job is offered at its own price. Renewal charges again and resets every allowance, an update takes effect at the next mint, and a cancel runs to the end of the cycle paid for.

A subscription answers one money question: what does a buyer pay for work that keeps coming? One charge a cycle, a count of jobs it covers, and each job past the count at its own price. What orders the jobs, `on`, is [Schedules and triggers](https://services.dev/docs/service/subscriptions.md).

## The smallest form

Lingua's Translation Plan keeps a buyer's site translated for $149 a month, with four translations included:

```bash
grep price services/plan.ts
```

```txt title="magic values: none"
  price: { usd: 149, every: 'month', includes: { translation: 4 } },
```

`check` reads the cycle back, with what it leaves you:

```bash
npx services.dev check
```

```txt title="magic values: none"
services/plan.ts · 7 lines · $149 per month, charged at the start of each month, 4 translation included each month.
  ledger  +$149.00 · AI $0.84 (example) · you $148.16
  margin  at least $119.20 a job (80%) · a job that stops costs you at most $29.80
```

## One charge each cycle

The order opens `sbn_7Hq2` and charges the first cycle at once:

```txt
→ POST /plan {"site":"acme.example","document":"lease.pdf","to":"fr-CA","max_usd":149,"key":"plan"}
```

```txt title="magic values: none"
← sbn_7Hq2 · plan · active · cycle 1 · 2026-10-25 → 2026-11-25 · $149.00 per month
  allowances  translation 0 of 4
  schedule    monthly → translation · next 2026-11-01
  event       page.changed → translation
```

```txt
→ GET /events?type=subscription.created
```

```txt title="magic values: none"
  2026-10-25  sbn_7Hq2  created · cycle 1 · $149.00 charged · ends 2026-11-25
```

## Inside the allowance

Each job inside the four charges $0 and counts against the cycle, so its ledger carries only what it cost you: the AI here, and any person's pay:

```txt
→ POST /events {"type":"page.changed","id":"page-1","data":{"site":"acme.example","document":"page-1.html"}}
```

```txt title="200 · subscription.minted · event page.changed → job_7Hq2 translation · $0.00 · magic values: none"
→ POST /events {"type":"page.changed","id":"page-2","data":{"site":"acme.example","document":"page-2.html"}}
← 200 · subscription.minted · event page.changed → job_7Hq3 translation · $0.00
→ POST /events {"type":"page.changed","id":"page-3","data":{"site":"acme.example","document":"page-3.html"}}
← 200 · subscription.minted · event page.changed → job_7Hq4 translation · $0.00
→ POST /events {"type":"page.changed","id":"page-4","data":{"site":"acme.example","document":"page-4.html"}}
← 200 · subscription.minted · event page.changed → job_7Hv5 translation · $0.00

→ GET /owner/jobs/job_7Hq2
← job_7Hq2 · delivered · page-1.fr-CA.html · $0.00 held
  ledger  +$0.00 · AI $0.84 · you −$0.84
```

## Past the allowance

A fifth order answers 402 OFFER at Translation's own $49 and charges nothing until the buyer sends its `next`. The extra job then holds $49, `from` the plan, with its own ledger:

```txt
→ POST /translation {"document":"page-5.html","to":"fr-CA","subscription":"sbn_7Hq2"}
```

```txt title="magic values: none"
← 402 OFFER · $49 per job, within 48 hours, charged on delivery.
  next    order translation {"document":"page-5.html","to":"fr-CA","max_usd":49,"key":"order-f402daf0","subscription":"sbn_7Hq2"}
→ POST /translation {"document":"page-5.html","to":"fr-CA","max_usd":49,"key":"order-f402daf0","subscription":"sbn_7Hq2"}
← job_7Hq6 · working · $49.00 held · from sbn_7Hq2
→ GET /owner/jobs/job_7Hq6
← job_7Hq6 · delivered · page-5.fr-CA.html · $49.00 charged
  ledger  +$49.00 · AI $0.84 · you $48.16
```

A month earns you $149, less its included jobs' costs, plus each extra job's [ledger](https://services.dev/docs/money.md#the-ledger). `check`'s margin covers the plan's charge, not the jobs it includes, so price each allowance against its Service's ledger: four Translations spend at most four AI caps, $39.20.

## Renewal

At the cycle's end the plan charges $149 again and resets every allowance. Each cycle counts from the first, so it never drifts: a monthly plan ordered 31 January renews 28 February, then 31 March. A date that falls past the count before then is offered too, never charged on its own:

```txt
→ POST /test_clocks/default/advance {"to":"2026-11-26T00:00:00Z"}
```

```txt title="magic values: none"
  2026-11-01  sbn_7Hq2  allowance translation past 4 of 4 · the extra job offered at $49.00
  2026-11-01  sbn_7Hq2  schedule monthly → translation offered at $49.00
  2026-11-25  sbn_7Hq2  renews · cycle 2 · $149.00 charged · allowances reset · ends 2026-12-25
```

## A renewal that fails

```txt
→ GET /subscriptions/sbn_7Hr3   # another buyer's plan: the renewal's card was declined
```

```txt title="magic values: none"
← sbn_7Hr3 · plan · past_due · cycle 2 · 2026-11-25 → 2026-12-25 · $149.00 per month
  renewal     25 Nov charge.failed, card expired · ask_7Hr3 to update the card · Stripe retries 27 Nov, 1 Dec and 5 Dec
  allowances  translation 1 of 4 · still usable while past_due
  then        9 Dec, if still unpaid: unpaid · allowances frozen · new orders answer 402 with the balance
```

| Status | Allowances | New orders |
|---|---|---|
| `past_due`, while Stripe retries and an `ask` asks for a new card | usable | priced as usual |
| `unpaid`, once [`Brand.dunning`](https://services.dev/docs/money/release.md#when-the-charge-fails) runs out | frozen | 402, with the balance and the exact next call |
| `active`, once a retry collects | as they were | priced as usual |
| a schedule's date, while `past_due` or `unpaid` | its job mints, held | runs once a retry collects: no date skipped, none worked unpaid |

## Update

`PATCH /subscriptions/:id`, `subscriptions update`, `update_subscription`, `subscriptions.update()`:

| An update | |
|---|---|
| changes | the subscription's input: a new state, a new officer, a new books link |
| takes effect | at the next mint, recording `subscription.updated` |
| leaves | jobs already minted on their input, and the cycle's charge unchanged |


```bash
npx services.dev subscriptions update sbn_7Hq2 jurisdictions=US,US-CA,US-NY,US-NJ,US-TX --test-clock tc_7Hq4
```

```txt title="magic values: owner.email = signs@example.test, metadata.sandbox.outside = {"reject":{"file.US-NY":["ein_mismatch"]}}"
sbn_7Hq2 · tax-plan · active · updated 2027-03-08 · jurisdictions + US-TX
  next mint   annual 2028-02-01 · period 2027 · US, US-CA, US-NY, US-NJ, US-TX · Texas due 05-15, its own extension on 05-164
  keeps       job_4Tx3 · period 2026 · US, US-CA, US-NY, US-NJ, as minted 2027-02-01
```

## Cancel

A cancel stops the renewal, not the cycle the buyer paid for. The subscription reads `canceling` until the cycle ends, then `canceled`, and nothing after that mints or charges:

**CLI**

```bash title="CLI"
npx services.dev subscriptions cancel sbn_7Hq2
```

```txt title="magic values: none"
sbn_7Hq2 · plan · canceling · cycle 1 · 2026-10-25 → 2026-11-25 · $149.00 per month · ends 2026-11-25
  allowances  translation 0 of 4
  schedule    monthly → translation · next 2026-11-01
  event       page.changed → translation
```

**API**

```bash title="API"
curl -X POST http://localhost:8787/subscriptions/sbn_7Hq2/cancel
```

```json title="200 · magic values: none"
{
  "id": "sbn_7Hq2",
  "object": "subscription",
  "livemode": false,
  "service": "plan",
  "status": "canceling",
  "subject": "sbj_7Hq2",
  "policy": {},
  "price": {
    "usd": 149,
    "every": "month"
  },
  "cycle": {
    "number": 1,
    "start": "2026-10-25T14:02:11.000Z",
    "end": "2026-11-25T14:02:11.000Z"
  },
  "allowances": {
    "translation": {
      "used": 0,
      "included": 4
    }
  },
  "schedule": [
    {
      "name": "monthly",
      "service": "translation",
      "next": "2026-11-01T00:00:00.000Z"
    }
  ],
  "events": [
    {
      "event": "page.changed",
      "service": "translation"
    }
  ],
  "charges": [
    {
      "cycle": 1,
      "usd": 149,
      "at": "2026-10-25T14:02:11.000Z"
    }
  ],
  "minted": [],
  "jobs": [],
  "ends_at": "2026-11-25T14:02:11.000Z",
  "metadata": {},
  "created": "2026-10-25T14:02:11.000Z",
  "simulated": true
}
```

**MCP**

```txt title="MCP"
→ tools/call cancel_subscription {"id":"sbn_7Hq2"}
```

```json title="magic values: none"
{
  "id": "sbn_7Hq2",
  "object": "subscription",
  "livemode": false,
  "service": "plan",
  "status": "canceling",
  "subject": "sbj_7Hq2",
  "policy": {},
  "price": {
    "usd": 149,
    "every": "month"
  },
  "cycle": {
    "number": 1,
    "start": "2026-10-25T14:02:11.000Z",
    "end": "2026-11-25T14:02:11.000Z"
  },
  "allowances": {
    "translation": {
      "used": 0,
      "included": 4
    }
  },
  "schedule": [
    {
      "name": "monthly",
      "service": "translation",
      "next": "2026-11-01T00:00:00.000Z"
    }
  ],
  "events": [
    {
      "event": "page.changed",
      "service": "translation"
    }
  ],
  "charges": [
    {
      "cycle": 1,
      "usd": 149,
      "at": "2026-10-25T14:02:11.000Z"
    }
  ],
  "minted": [],
  "jobs": [],
  "ends_at": "2026-11-25T14:02:11.000Z",
  "metadata": {},
  "created": "2026-10-25T14:02:11.000Z",
  "simulated": true
}
```

**SDK**

```ts title="SDK"
await lingua.subscriptions.cancel("sbn_7Hq2")
```

```json title="magic values: none"
{
  "id": "sbn_7Hq2",
  "object": "subscription",
  "livemode": false,
  "service": "plan",
  "status": "canceling",
  "subject": "sbj_7Hq2",
  "policy": {},
  "price": {
    "usd": 149,
    "every": "month"
  },
  "cycle": {
    "number": 1,
    "start": "2026-10-25T14:02:11.000Z",
    "end": "2026-11-25T14:02:11.000Z"
  },
  "allowances": {
    "translation": {
      "used": 0,
      "included": 4
    }
  },
  "schedule": [
    {
      "name": "monthly",
      "service": "translation",
      "next": "2026-11-01T00:00:00.000Z"
    }
  ],
  "events": [
    {
      "event": "page.changed",
      "service": "translation"
    }
  ],
  "charges": [
    {
      "cycle": 1,
      "usd": 149,
      "at": "2026-10-25T14:02:11.000Z"
    }
  ],
  "minted": [],
  "jobs": [],
  "ends_at": "2026-11-25T14:02:11.000Z",
  "metadata": {},
  "created": "2026-10-25T14:02:11.000Z",
  "simulated": true
}
```

On the clock, the date inside the paid cycle still mints at $0, and the next one never comes:

```txt
→ POST /test_clocks/default/advance {"to":"2027-01-02T00:00:00Z"}
```

```txt title="magic values: none"
  2026-12-01  sbn_7Hq2  schedule monthly → job_7Hq7 translation · $0.00 · allowance 1 of 4

→ GET /subscriptions/sbn_7Hq2
← sbn_7Hq2 · plan · canceled · cycle 2 · 2026-11-25 → 2026-12-25 · $149.00 per month · ends 2026-12-25
  allowances  translation 1 of 4
  schedule    monthly → translation · next none

→ POST /events {"type":"page.changed","id":"page-9","data":{"site":"acme.example","document":"page-9.html"}}
← 422 input_invalid: page.changed isn't an event of the catalog. No subscription listens for it, and to raise a job's inbound signal, name the job.
```

## The money on the feed

Each cycle's money arrives on [the feed](https://services.dev/docs/api/events.md), for the buyer and for you:

| Event | The buyer's card |
|---|---|
| `subscription.created` | The first cycle charged, with when it ends |
| `allowance.used` | $0 on the job, counted against the cycle |
| `allowance.exceeded` | Nothing yet: the extra job is offered at its price, 402 OFFER, with the exact next call |
| `subscription.minted` | A date or an event: the job minted, the job offered, or the refusal recorded |
| `subscription.renewed` | Charged again; every allowance reset |
| `charge.failed` | The renewal declined: update the card; the plan reads `past_due` |
| `subscription.unpaid` | Still declined when dunning ran out: allowances frozen until it's paid |
| `subscription.updated` | The new input, and the next mint that takes it |
| `subscription.canceled` | Nothing more: it runs to the end of the cycle paid for |

## Every form

| Key | Written | Left out | Does |
|---|---|---|---|
| `price.every` | `'day'`, `'week'`, `'month'`, `'quarter'` or `'year'` | one job | An order is a subscription, `sbn_…`, charged `usd` at the start of each cycle |
| `price.includes` | `{ <service id>: <jobs> }` | none | Allowances per cycle: inside one a job is $0 and counted; past it, the order answers 402 OFFER at the job's price. Renewal resets them |
| `subscription` on an order | `sbn_…` | none | Orders the job on the plan: inside an allowance at $0, past it at its own price |

## Seven surfaces

| Surface | What `every` and `includes` add |
|---|---|
| API | The order answers an `sbn_` object; `GET /subscriptions/:id`, `PATCH /subscriptions/:id` and `POST /subscriptions/:id/cancel` |
| CLI | `subscriptions list`, `get`, `update` and `cancel` |
| MCP | The order tool; `get_subscription`, `update_subscription` and `cancel_subscription` in the sandbox toolset |
| SDK | `subscriptions.list()`, `get()`, `update()` and `cancel()` |
| Site | The price sentence, with what each cycle includes |
| Docs | The plan's order and its operations in `/llms.txt` |
| App | Subscriptions and allowances used in the customer portal; the cycle's price in the owner console |

## Refusals

| Code | When |
|---|---|
| [`subscription_unpriced`](https://services.dev/docs/reference/definition-errors.md#subscription_unpriced) | `Service()`: `includes` or `on` without `price.every` |
| [`every_unknown`](https://services.dev/docs/reference/definition-errors.md#every_unknown) | `Service()`: `price.every` isn't a cycle |
| [`allowance_uncounted`](https://services.dev/docs/reference/definition-errors.md#allowance_uncounted) | `Service()`: an allowance isn't a whole number of jobs |

## Three brands

**Tax.** A year for $2,400, routed to six Services, five of them inside an allowance. The third notice is past two and is offered at $299. [The Tax Plan](https://services.dev/docs/examples/tax/tax-plan.md).

```txt
→ POST /events {"type":"notice.received","id":"CP161-0003","data":{"ein":"12-3456789","notice":"cp161.pdf","reply_by":"2027-05-01","tax_years":["2025"]}}
```

```txt title="captured/tax-plan.txt · magic values: none"
← subscription.minted · event notice.received → notice-response offered at $299.00
```

Two years of the Tax Plan: each cycle's charge, then each job it drew on at $0 with its own costs. A return inside the allowance still paid its signer:

```txt
→ GET /subscriptions/sbn_7Hq2
```

```txt title="captured/tax-money.txt · magic values: owner.email = signs@example.test, metadata.sandbox.asks = {"organizer.books":"after 160d","organizer.assets":"waive","organizer #2":"after 2d","w9":"after 19d"}, metadata.sandbox.outside = {"reject":{"file.US-NY":["ein_mismatch"]}}, metadata.sandbox.events = { notice.received: { at: 2027-11-14T15:00:00Z, data: { notice, reply_by, tax_years } } }"
← sbn_7Hq2 · tax-plan · active · cycle 2 · 2028-01-20 → 2029-01-20 · $2,400.00 per year
  charge      cycle 1 · $2,400.00 · 2027-01-20
  charge      cycle 2 · $2,400.00 · 2028-01-20
# each job the plan drew on, as the owner reads its ledger
  job_4Tx1  information-returns  2026  +$0.00 · AI $0.84 · you −$0.84 · 5 payees
  job_4Tx3  annual-return        2026  +$0.00 · AI $3.36 · signer $210.00 · you −$213.36
  job_4Tx4  estimate                   +$0.00 · AI $0.42 · you −$0.42
  job_4Tx5  estimate                   +$0.00 · AI $0.42 · you −$0.42
  job_4Tx6  estimate                   +$0.00 · AI $0.42 · you −$0.42
  job_4Tx7  notice-response            +$0.00 · AI $0.84 · representative $90.00 · you −$90.84
  job_4Tx8  estimate                   +$0.00 · AI $0.42 · you −$0.42
  job_4TxA  information-returns  2027  +$0.00 · AI $0.84 · you −$0.84 · 6 payees
# the brand
tax · 10 jobs · +$6,600.00 · AI $9.24 · people $405.00 · you $6,185.76
```

**Legal.** A docket watched for $12 a month, with no allowance. Each weekly check is $0 out of the month, and an office action is offered at its own response's price. [Docket watch](https://services.dev/docs/examples/legal/docket-watch.md).

```txt
→ POST /events {"type":"office_action.issued","id":"OA-97123456-1","data":{"matter":"63/000,001","serial":"97123456","action":"office-action.pdf","issued":"2026-10-30","client":{"name":"Ada Lovelace","email":"ada@example.com"}}}
```

```txt title="magic values: none"
← subscription.minted · event office_action.issued → trademark-response offered at $349.00
  next    order trademark-response {"serial":"97123456","action":"office-action.pdf","issued":"2026-10-30","client":{"name":"Ada Lovelace","email":"ada@example.com"},"max_usd":349,"key":"sbn_7Hq2:event office_action.issued:OA-97123456-1","subscription":"sbn_7Hq2"}
```

**Sales.** Paid per outcome, on the close, with no cycle.

Subscriptions run live on [Launch](https://services.dev/docs/money/plans.md#launch).

---

- Up: [Money](https://services.dev/docs/money.md)
- Previous: [Prices](https://services.dev/docs/money/prices.md)
- Next: [Outcomes](https://services.dev/docs/money/outcomes.md)
