One job per period
With subject.period, a subject holds one live or ended job of a Service per period. A second order for that period answers 409 period_taken, and its next is the follow-on the job's end offers, such as a revision.
period answers one question: can this work be ordered twice for the same stretch of time?
Two lines: the input that names the period, and the Service an accepted year offers instead of a second one.
subject: { kind: 'account', key: 'account', period: 'year', keeps: { glossary: 'glossary' } },
ends: { accepted: { follow: [revision] } },→ POST /jobs/job_7Hq2/accept
next [{"do":"again","arguments":{"id":"job_7Hq2","service":"glossary-revision"},"tool":"reorder_job","http":"POST http://localhost:8787/jobs/job_7Hq2/again"}]On the subject
| Key | Written | Left out | Does |
|---|---|---|---|
period | (keyof I & string) | ((job) => string) | { of; ends?; many? } | none | One live or ended job of this Service per subject per period: the input that names the period, or a function of the order. A second order for a period answers 409 period_taken, its next the follow-on the end offers, such as an amendment. A canceled job frees its period. ends gives the period's last day as MM-DD, for a fiscal year; many: true holds any number of jobs per period, as amendments do. |
On an end
| Key | Written | Left out | Does |
|---|---|---|---|
follow | AnyService[] | none | The follow-on Services this end offers, through POST /jobs/:id/again { service }. |
The subject already holds 2025, so the order is refused before anything runs or is charged. The refusal names the job that holds the period, and its next is the follow-on, ordered from that job:
$ npx services.dev glossary account=acme year=2025 terms=Premises --max 19 --key glossary-2025-again --json # a second order for 2025
"code": "period_taken",
"reason": "account acme has Glossary for 2025 already: job_7Hq2, accepted. A second one for 2025 is a follow-on from it: glossary-revision.",
"period": "2025",
"do": "again",
"service": "glossary-revision"
"tool": "reorder_job",$ curl -X POST http://localhost:8787/glossary -H 'content-type: application/json' -d '{"account":"acme","year":"2025","terms":["Premises"],"max_usd":19,"key":"glossary-2025-again"}' # a second order for 2025
"code": "period_taken",
"reason": "account acme has Glossary for 2025 already: job_7Hq2, accepted. A second one for 2025 is a follow-on from it: glossary-revision.",
"period": "2025",
"do": "again",
"service": "glossary-revision"
"tool": "reorder_job",→ tools/call order_glossary {"account":"acme","year":"2025","terms":["Premises"],"max_usd":19,"key":"glossary-2025-again"} # a second order for 2025
← isError: true, structuredContent:
"code": "period_taken",
"reason": "account acme has Glossary for 2025 already: job_7Hq2, accepted. A second one for 2025 is a follow-on from it: glossary-revision.",
"period": "2025",
"do": "again",
"service": "glossary-revision"
"tool": "reorder_job",await lingua.glossary.order({"account":"acme","year":"2025","terms":["Premises"],"max_usd":19,"key":"glossary-2025-again"}) // a second order for 2025
× throws ConflictError<'service.order'> · 409
"code": "period_taken",
"reason": "account acme has Glossary for 2025 already: job_7Hq2, accepted. A second one for 2025 is a follow-on from it: glossary-revision.",
"period": "2025",
"do": "again",
"service": "glossary-revision"
"tool": "reorder_job",An agent sends next as it comes. The follow-on is its own job, at its own price, with from on its record:
→ POST /jobs/job_7Hq2/again {"service":"glossary-revision","max_usd":9}
from job_7Hq2The follow-on joins the same subject. A canceled job frees its period; a job that ended any other way holds it.
period names an input, such as a year or a quarter, or a function of the order. A job with a period reads its subject's facts from the period before its own, so a late job for an earlier period never changes what a later one reads: Subjects.
A subscription that mints the work on a schedule sets the period through each. A mint for a period the subject already holds is recorded on the subscription as refused, never dropped: Schedules and triggers.
Each Service holds its own periods on a subject, and a period's name nests: 2027 holds 2027-Q1 to 2027-Q4, so a yearly job reads every value its quarterly ones kept with { period: '2027', all: true } (Subjects).
A period follows the subject's own year. apis.tax writes period: { of: 'tax_year', ends: job => job.input.year_end ?? '12-31' }, so Beta Labs' period 2027, for a year that ends 30 June, runs 2026-07-01 to 2027-06-30, and its quarters nest as fiscal quarters: 2028-Q1 is July to September 2027. Your code reads job.period.name, .start and .end, so the return's dates come from the period, never from a calendar year:
const statutory = (job: AnyView, j: string) => {
const due = JURISDICTIONS[j].due
if (typeof due === 'string') return `${Number(job.period.end.slice(0, 4)) + 1}-${due}`
return fifteenth(job.period.end, j === 'US' && june(job) ? 3 : due[String(job.input.entity)] ?? due['*'])
}
export const estimateDue = (job: AnyView) => fifteenth(job.period.start, [3, 5, 8, 11][Number(job.period.name.slice(-1)) - 1])A schedule with each mints one job per period in the same terms: Schedules and triggers.
A period sets no timer of its own. What touches it:
| When | The runtime | Event |
|---|---|---|
| a schedule mints for a period the subject already holds | records the mint as refused, and charges nothing | subscription.minted |
| a job holding a period is canceled | frees the period for the next order | job.canceled |
| App | Shows |
|---|---|
| Customer portal | one row per period under each subject, its job and how it ended, and the follow-on a held period offers |
| Owner console | each subject's periods beside its jobs, and every refused order with its next |
A refused second order charges nothing and holds nothing. The follow-on is its own job, at its own price. A mint a subscription makes for a held period is recorded as refused and charges nothing either. Periods run live on Launch, with the subject. Every kind of time, by plan: Keeping time.
| Op | API | CLI | MCP | SDK | Site | Docs | App |
|---|---|---|---|---|---|---|---|
service.order | POST /jobs, POST /:service | <service> k=v … | order_<service> | <service>.order() | /<service>: the order form | 10 error pages | customer portal |
job.again | POST /jobs/:id/again | jobs again <job> | reorder_job | jobs.again() | 7 error pages | customer portal |
Operations has every column of every row.
| Code | Category | HTTP | Why |
|---|---|---|---|
not_terminal | conflict | 409 | Only an ended job is ordered again. |
period_taken | conflict | 409 | A job of this Service already holds the subject's period: one live or ended job per period. |
| When the file won't load | Thrown by | Why |
|---|---|---|
period_unknown | Service() | Service Glossary: subject.period is edition, which isn't an input. Name the input that says the period, such as year: 'text', or write period as a function of the order. |
The lines that use it in the example brands, as their files write them, and what check read aloud for each file.
subject: { kind: 'taxpayer', key: 'ein', period: 'quarter', keeps: { paid: 'paid' } },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 · codeA second client runs a June year end in apis.tax.
After a year of its plan, the tax example's taxpayer holds annual returns by year and estimates by quarter:
→ GET /taxpayers/sbj_7Hq2
← sbj_7Hq2 · taxpayer 12-3456789 · of record mbr_8Ea1, Katherine Johnson, until 2030-07-11
period 2025 annual-return job_4Tx2 · accepted
period 2026 information-returns job_4Tx1 · accepted
period 2026 annual-return job_4Tx3 · accepted
period 2027-Q1 estimate job_4Tx4 · accepted
period 2027-Q2 estimate job_4Tx5 · accepted
period 2027-Q3 estimate job_4Tx6 · accepted
period 2027-Q4 estimate job_4Tx8 · accepted
period 2027 information-returns job_4TxA · delivered
period 2027 annual-return job_4Tx9 · waitingAmended return runs the refusal and its follow-on end to end.