---
title: "Tax Plan"
url: https://services.dev/docs/examples/tax/tax-plan
captured: [tax-plan.txt]
---
# Tax Plan

URL: https://services.dev/docs/examples/tax/tax-plan

> One subscription a business buys once a year, routed to six Services, five inside allowances and the amendment at its own price. Schedules order the 1099s, the return and each quarter's estimate on dates the Services derive, and events order a question, a notice response or an amendment.

*tax's words: return is a job, taxpayer is a subject, review is a task, preparer is a member (examples/tax/brand.ts).*

The Tax Plan is a Service whose work is other Services. Its `on` key routes the year: five days after each calendar year, its 1099s; a month after each tax year ends, that year's return; ten days before each quarter's payment is due, a C corp's estimate; and a question, a notice or a restated figure whenever one arrives. Its price is a year, and `includes` sets how much of each that year covers.

```bash
cat services/tax-plan.ts
```

```txt title="captured/tax-plan.txt · magic values: none"
import { Service } from 'services.dev'
import amendedReturn from './amended-return.ts'
import annualReturn from './annual-return.ts'
import estimate from './estimate.ts'
import informationReturns from './information-returns.ts'
import noticeResponse from './notice-response.ts'
import question from './question.ts'

// The plan a business buys. Its year routes to small Services with their allowances: five days after each calendar
// year, its 1099s; a month after each tax year ends, that year's return, and for a client who joins mid-year any year
// still open; ten days before each quarterly payment, a C corp's estimate; a question or a notice when one comes; and an
// amendment, at its own price, when a figure a later year read is restated. Past an allowance, the next is offered.
export default Service({
  name: 'Tax Plan',
  description: 'A tax year.',
  input: {
    business: 'party', owner: 'party: the officer who signs', ein: 'text: such as 12-3456789',
    entity: ['C corp', 'S corp', 'partnership'], jurisdictions: 'jurisdiction[]: the federal return first, then each state, such as US, US-CA',
    books: 'url: a read-only link to the ledger, such as https://books.example/acme',
  },
  output: { letter: 'file: the year in one page' },
  subject: { kind: 'taxpayer', key: 'ein' },
  on: {
    schedule: {
      information: { order: informationReturns, each: 'year', at: '5d after end', catch_up: 'open' },
      annual: { order: annualReturn, each: 'tax_year', at: '1mo after end', catch_up: 'open' },
      estimate: { order: estimate, each: 'quarter', at: '10d before pay', when: sub => sub.input.entity === 'C corp' },
    },
    event: { 'question.asked': question, 'notice.received': noticeResponse, 'fact.restated': amendedReturn },
  },
  price: { usd: 2_400, every: 'year', includes: { 'annual-return': 1, 'information-returns': 25, estimate: 4, question: 12, 'notice-response': 2 } },
})
```

| Key | Here | What it does |
|---|---|---|
| `on.schedule.information` | `each: 'year', at: '5d after end'` | Mints the 1099s for each calendar year, whatever the tax year, five days after it ends |
| `on.schedule.annual` | `each: 'tax_year', at: '1mo after end'` | Mints the return for each tax year a month after it ends, on the client's own year end |
| `catch_up` | `'open'` | A client who joins mid-year gets every period whose date is still open, minted at once, and none whose date has passed |
| `on.schedule.estimate` | `each: 'quarter', at: '10d before pay'` | Mints each quarter's estimate ten days before its own pay clock is due, so the dates come from `lib/tax.ts`, never from the plan |
| `when` | `sub => sub.input.entity === 'C corp'` | Reads the subscription as it stands, so a client who changes entity changes what the next mint does |
| `on.event` | `question.asked`, `notice.received`, `fact.restated` | An event sent to `POST /events` mints the Service it names. It carries your own id, so one sent twice mints once. A restated figure mints the amendment, outside the allowances, at its own price |
| `price.every` | `'year'` | The order is a subscription, `sbn_…`, charged at the start of each cycle |
| `price.includes` | 1 return, 25 payees' 1099s, 4 estimates, 12 questions, 2 notices | Allowances per cycle. Inside one a job charges $0 and is counted; past it, the next is offered at its own price. Renewal resets them |

Every minted job shares the Tax Plan's taxpayer, and names the subscription it came from.

## Bought once

The business orders the Tax Plan like any Service. The answer is a subscription: its cycle, its allowances, and what each schedule and event will order next. Bought on 20 January, the plan catches up the 1099s still open:

```txt
→ POST /returns {"service":"tax-plan","business":{"name":"Acme Robotics","email":"books@acme.example"},"owner":{"name":"Ada Lovelace","email":"ada@example.com"},"ein":"12-3456789","entity":"C corp","jurisdictions":["US","US-CA","US-NY","US-NJ"],"books":"https://books.example/acme","max_usd":2400,"key":"plan-2027"}
```

```txt title="magic values: none"
← sbn_7Hq2 · tax-plan · active · cycle 1 · 2027-01-20 → 2028-01-20 · $2,400.00 per year
  allowances  annual-return 0 of 1 · information-returns 0 of 25 · estimate 0 of 4 · question 0 of 12 · notice-response 0 of 2
  minted      2027-01-20  catch_up open → job_4Tp0 information-returns · period 2026 · due 2027-02-01, rolled from 2027-01-31
  schedule    information → information-returns · next 2028-01-05 (5d after end 2027-12-31)
  schedule    annual → annual-return · next 2027-02-01 (1mo after end 2026-12-31)
  schedule    estimate → estimate · next 2027-04-05 (10d before pay 2027-04-15) · C corp
  event       question.asked → question
  event       notice.received → notice-response
  event       fact.restated → amended-return · outside the allowances, at $900.00
```

## The schedule orders the year

A month after 2026 ends, the schedule mints the 2026 return and draws on the allowance. The return costs the business nothing more:

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

```txt title="magic values: none"
  2027-01-20  job_4Tp0  information-returns · 5 payees filed · allowance 5 of 25
  2027-02-01  sbn_7Hq2  schedule annual → job_4Tp1 annual-return · period 2026 · $0.00 · allowance 1 of 1
```

## Events order the rest

The owner's bookkeeping system sends a question and then a notice. Each event mints its Service inside the allowance:

```txt
→ POST /events {"type":"question.asked","id":"Q-0001","data":{"ein":"12-3456789","question":"Can we deduct the van?"}}
```

```txt title="magic values: none"
← subscription.minted · event question.asked → job_4Tp2 question · $0.00
→ POST /events {"type":"notice.received","id":"CP161-0001","data":{"ein":"12-3456789","notice":"cp161.pdf","reply_by":"2027-03-01","tax_years":["2025"]}}
← subscription.minted · event notice.received → job_4Tp3 notice-response · $0.00
→ POST /events {"type":"notice.received","id":"CP161-0002","data":{"ein":"12-3456789","notice":"cp161.pdf","reply_by":"2027-04-01","tax_years":["2025"]}}
← subscription.minted · event notice.received → job_4Tp4 notice-response · $0.00
```

A third notice finds the allowance spent. It isn't refused: the next answer is offered at the Service's own price, and the Tax Plan keeps the list of what it minted:

```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="magic values: none"
← subscription.minted · event notice.received → notice-response offered at $299.00

← sbn_7Hq2 · tax-plan · active · cycle 1 · 2027-01-20 → 2028-01-20 · $2,400.00 per year
  allowances  annual-return 1 of 1 · information-returns 5 of 25 · estimate 0 of 4 · question 1 of 12 · notice-response 2 of 2
  schedule    information → information-returns · next 2028-01-05 (5d after end 2027-12-31)
  schedule    annual → annual-return · next 2028-02-01 (1mo after end 2027-12-31)
  schedule    estimate → estimate · next 2027-04-05 (10d before pay 2027-04-15) · C corp
  event       question.asked → question
  event       notice.received → notice-response
  event       fact.restated → amended-return · outside the allowances, at $900.00
  minted      2027-01-20  catch_up open → job_4Tp0 information-returns · period 2026 · $0.00
  minted      2027-02-01  schedule annual → job_4Tp1 annual-return · period 2026 · $0.00
  minted      2027-02-02  event question.asked → job_4Tp2 question · $0.00
  minted      2027-02-02  event notice.received → job_4Tp3 notice-response · $0.00
  minted      2027-02-02  event notice.received → job_4Tp4 notice-response · $0.00
  minted      2027-02-02  event notice.received → notice-response offered at $299.00
```

[A year, and the next](https://services.dev/docs/examples/tax/years.md) runs the Tax Plan through a whole year and its renewal, and [two clients](https://services.dev/docs/examples/tax/clients.md) a change, a cancel and a late start. [Schedules and triggers](https://services.dev/docs/service/subscriptions.md) covers `on`, and [Subscriptions and allowances](https://services.dev/docs/money/subscriptions.md) what a plan charges.

---

- Up: [The tax example](https://services.dev/docs/examples/tax.md)
- Previous: [The dates to file and pay](https://services.dev/docs/examples/tax/deadline.md)
- Next: [Annual return](https://services.dev/docs/examples/tax/annual-return.md)
