---
title: "Estimated payment"
url: https://services.dev/docs/examples/tax/estimate
captured: [tax-branches.txt, tax-build.txt, tax-check.txt, tax-years.txt]
---
# Estimated payment

URL: https://services.dev/docs/examples/tax/estimate

> A quarter's estimated tax in each jurisdiction, figured, decided and paid. The prior-year safe harbor, cumulative by quarter so a quarter figured short catches up, California's 30, 40, 0 and 30, New York's first installment on its extension, and what an unpaid installment costs.

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

A C corp pays its tax in four installments, and each state keeps its own. The Estimated Payment figures each one, the officer decides, and the brand pays it. The quarter is its period, and what was paid is kept by quarter for the year's return to credit.

```bash
cat services/estimate.ts
```

```txt title="magic values: none"
import { Service } from 'services.dev'
import { estimateDue, estimateFor, underpaymentCost } from '../lib/tax.ts'

// A quarter's estimated payments, the owner deciding. The quarter is its period on the taxpayer, so a second estimate for
// the same quarter answers period_taken. Your code figures each jurisdiction's installment: what the year requires by
// this quarter on last year's tax, less what is paid, so a quarter figured short catches up. The owner pays it, pays a
// different amount themselves, or skips it in writing. Paid, each payment goes under a cap, its receipt satisfies the
// clock the buyer answers for, and what was paid is kept on the taxpayer, in its quarter, for the year's return to credit.
// The estimate is earned once it is figured and decided, so it is charged on delivery, whatever the owner chose.
const decided = (job: { record: readonly { step?: string; decided?: string }[] }) => job.record.findLast(e => e.step === 'approve' && e.decided)?.decided

export default Service({
  name: 'Estimated Payment',
  description: "A quarter's estimated tax, figured, decided and paid.",
  input: { owner: 'party: the officer who decides', ein: 'text: such as 12-3456789', quarter: 'text: such as 2027-Q1', jurisdictions: 'jurisdiction[]: such as US, US-CA' },
  output: { amount: 'money{}', basis: 'text', voucher: 'file', receipt: 'text[]?', paid: 'money{}?' },
  subject: { kind: 'taxpayer', key: 'ein', period: 'quarter', keeps: { paid: 'paid' } },
  deliver: {
    compute: { by: estimateFor, cite: 'prior-year safe harbor, cumulative by quarter' },
    approve: { ask: 'owner', to: 'decide', choices: ['pay it', 'pay a different amount', 'skip, in writing'] },
    pay: {
      submit: 'https://treasury.example.test/payments', to: 'Treasury and states', receipt: 'receipt', each: { of: job => Object.keys(job.output.amount) }, when: job => decided(job) === 'pay it',
      guard: { amount: v => v <= 50_000 || 'over the single-payment cap: the owner pays directly' },
    },
    record: { by: async (_, job) => ({ paid: job.output.amount }), when: job => decided(job) === 'pay it' },
  },
  clocks: { pay: { due: estimateDue, owner: 'buyer', rungs: ['notice 10d', 'escalate 2d'], exposure: underpaymentCost } },
  price: { usd: 79, release: 'delivery' },
})
```

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

```txt title="magic values: none"
services/estimate.ts · 17 lines · $79 per job, within 48 hours, charged on delivery.
  steps   compute · code · cites prior-year safe harbor, cumulative by quarter → approve · owner · decide → review · ai · adversarial → pay · Treasury and states · one for each jurisdiction with an installment → record · code
  clocks  pay · a date your code decides · the buyer · about 7% a year on an installment left unpaid
```

## Each state, its own installments

`estimateFor` in `lib/tax.ts` figures what the year requires by this quarter, less what is already paid, in each jurisdiction:

| Jurisdiction | Share due by each quarter | The first installment |
|---|---|---|
| US | 25%, 50%, 75%, 100% | with the first estimate |
| California | 30%, 70%, 70%, 100%: nothing in the third | with the first estimate |
| New York | 25%, 50%, 75%, 100% | with last year's CT-5 extension, a quarter of that year's tax |
| New Jersey | 25%, 50%, 75%, 100% | with the first estimate |

The basis is last year's tax, the safe harbor; before that return is filed, the latest year on file. A large corporation, $1 million of taxable income in any of the three years before, may use last year's tax for its first installment only.

## A quarter that catches up

Acme's first two quarters were figured on 2025. By September 2026's return was in, and the third quarter catches up the $4,410 the first two were short:

```bash
npx services.dev tick 1y --test-clock tc_7Hq4 --market normal
```

```txt title="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 } } }"
  2027-04-05 13:00:00  job_4Tx4  compute · 2026 isn't filed yet: 2025's tax (job_4Tx2) · US $16,223 · US-CA $1,639 · US-NJ $1,391 · US-NY's first installment goes with its CT-5
  2027-06-05 13:00:00  job_4Tx5  compute · 2026 isn't filed yet: 2025's tax (job_4Tx2) · US $16,222 · US-CA $2,185 · US-NY $1,447 · US-NJ $1,390
  2027-09-05 13:00:00  job_4Tx6  compute · 2026's tax (job_4Tx3), the safe harbor · US $22,838, catching up the $4,410 Q1 and Q2 were short of it · US-NY $2,356 · US-NJ $1,958 · US-CA has no third installment
  2027-12-05 14:00:00  job_4Tx8  compute · 2026's tax (job_4Tx3), the safe harbor · US $18,427 · US-CA $2,381 · US-NY $1,901 · US-NJ $1,579
```

Each clock carries what an unpaid installment costs at the Form 2220 rate, 7% a year here.

## The officer decides

She pays it, pays a different amount herself, or skips it in writing. It's charged on delivery, whatever she chose:

```txt
→ POST /returns {"service":"estimate","owner":{"name":"Ada","email":"signs@example.test"},"ein":"12-3456789","quarter":"2027-Q1","jurisdictions":["US","US-CA"],"max_usd":79,"key":"pay it","metadata":{"sandbox":{"asks":{"approve":"pay it"}}}}   # metadata.sandbox.asks = { approve: pay it }
```

```txt title="captured/tax-branches.txt · magic values: owner.email = signs@example.test, owner.email = declines@example.test, owner.email = silent@example.test, metadata.sandbox.asks = { approve: pay it }, metadata.sandbox.asks = { approve: pay a different amount }, metadata.sandbox.asks = { approve: skip, in writing }, metadata.sandbox.outside = never, metadata.sandbox.brand = stale, metadata.sandbox.brand = verified, member = mbr_eligible, member = mbr_lapsed_credential"
← job_4Tx2 · delivered · voucher.txt · $79.00 charged
  record  compute · code | approve · owner · decide · decided pay it | review · ai · pass | pay · Treasury and states · 2 accepted · rcp_7Hq2 | record · code
```

```txt
→ POST /returns {"service":"estimate","owner":{"name":"Ada","email":"signs@example.test"},"ein":"12-3456789","quarter":"2027-Q1","jurisdictions":["US","US-CA"],"max_usd":79,"key":"skip, in writing","metadata":{"sandbox":{"asks":{"approve":"skip, in writing"}}}}   # metadata.sandbox.asks = { approve: skip, in writing }
```

```txt title="captured/tax-branches.txt · magic values: owner.email = signs@example.test, owner.email = declines@example.test, owner.email = silent@example.test, metadata.sandbox.asks = { approve: pay it }, metadata.sandbox.asks = { approve: pay a different amount }, metadata.sandbox.asks = { approve: skip, in writing }, metadata.sandbox.outside = never, metadata.sandbox.brand = stale, metadata.sandbox.brand = verified, member = mbr_eligible, member = mbr_lapsed_credential"
← job_4Tx2 · delivered · voucher.txt · $79.00 charged
  record  compute · code | approve · owner · decide · decided skip, in writing | review · ai · pass | pay · skipped | record · skipped
```

[A year, and the next](https://services.dev/docs/examples/tax/years.md) runs all four quarters, and [the Tax Plan](https://services.dev/docs/examples/tax/tax-plan.md) orders each ten days before it's due.

---

- Up: [The tax example](https://services.dev/docs/examples/tax.md)
- Previous: [Federal first, then the states](https://services.dev/docs/examples/tax/efile.md)
- Next: [Tax question](https://services.dev/docs/examples/tax/question.md)
