Intake and request lists
Intake is steps at the top of the graph. Consent comes first, then the terms, then a request list your code derives from the job and its subject. Each item is answered, waived with a reason or substituted, and a step can start on one item while the rest are still open.
Intake answers one question: what must the client give before the work can start, and what may start before they have given it all?
There is no intake key. Preconditions are ask steps at the top of the graph, and the request list is a function of the job:
consent: { ask: 'client', to: 'consent' },
terms: { ask: 'client', to: 'sign', how: 'esign' },→ POST /annual-accounts {"client":{"name":"Ada Lovelace","email":"ada@example.com"},"company":"acme","kind":"company","max_usd":90,"year":"2025","opened":"payroll","key":"books-2025"}
← job_7Hq2 · waiting · on party · $90.00 held · consent · consentOn an ask step
| Key | Written | Left out | Does |
|---|---|---|---|
ask | 'buyer' | <party input> | required | Who acts: the buyer, or a party, party? or party[] input. |
to | AskTo | required | What the party does: 'sign', 'consent', 'answer', 'upload', 'decide', 'direct' or 'grant'. |
items | (job) => AskItem[] | none | For answer and upload: a request list derived from the job. A step can start on one item: after: '<ask>.<item>'. |
expires | Within | '7d' | When the link stops working; a second expiry lapses the job. |
remind | '${number}d'[] | ['1d'] | Reminders, each this long before the link expires. |
Each item of a request list
| Key | Written | Left out | Does |
|---|---|---|---|
id | string | required | Its name: a step can start on it with after: '<ask>.<item>'. |
label | string | required | The question or the document asked for, as the party reads it. |
optional | true | none | The ask is done without it. |
remind | '${number}d'[] | none | Reminders for this item alone, each this long before the link expires, while the item is still open. |
from | string | none | Where it comes from, as the party reads it: on the 2025 return. The customer portal shows it beside the item. |
What the party sends for a list: POST /asks/:id, through their link
| Key | Written | Left out | Does |
|---|---|---|---|
items | Record | none | A request list's items: each answered (answer or file), waived with a reason, or substituted. |
decline | unknown | none | The party says no. A decline ends the job at declined. |
reason | unknown | none | Why, with a decline. |
The job waits on the client from the first step. Each ask is their own link, and neither needs an account. Their time on it never counts against your deadline.
→ POST /asks/ask_7Hq3?t=rE5VINsEvPPB93bDQLooDc {} # terms · sign
ask ask_7Hq4 · organizer · upload · expires 2026-11-01T14:02:11.000Z
item bank · Statements for the bank account, 2025 · open · remind 2d
item payroll · Statements for the payroll account, 2025 · open · remind 2d · opened this yearitems is your code, run on the job. Here it reads last year's accounts off the subject and adds the one the client opened since, so this year's list writes itself. Each item can carry its own reminders and a from the client reads beside it:
→ POST /asks/ask_7Hq6?t=j-Rd4ngI-U2rfgStAvuMN_ {} # terms · sign
ask ask_7Hq7 · organizer · upload · expires 2026-11-01T14:02:11.000Z · from 2025, job_7Hq2
item bank · Statements for the bank account, 2026 · open · remind 2d
item payroll · Statements for the payroll account, 2026 · open · remind 2d
item card · Statements for the card account, 2026 · open · remind 2d · opened this yearA repair can re-ask only some items: a rejection routed { to: '<ask>', items } asks again for just what its code names (One submission per element).
The open list, on every surface:
$ npx services.dev jobs get job_7Hq2 --json # its wait and its open request list
"id": "job_7Hq2",
"status": "waiting",
"on": "party",
"id": "ask_7Hq4",
"step": "organizer",
"to": "upload",
"status": "open",
"id": "bank",
"status": "open",
"id": "payroll",
"status": "open",
"from": "opened this year"$ curl http://localhost:8787/jobs/job_7Hq2 # its wait and its open request list
"id": "job_7Hq2",
"status": "waiting",
"on": "party",
"id": "ask_7Hq4",
"step": "organizer",
"to": "upload",
"status": "open",
"id": "bank",
"status": "open",
"id": "payroll",
"status": "open",
"from": "opened this year"→ tools/call get_job {"id":"job_7Hq2"} # its wait and its open request list
"id": "job_7Hq2",
"status": "waiting",
"on": "party",
"id": "ask_7Hq4",
"step": "organizer",
"to": "upload",
"status": "open",
"id": "bank",
"status": "open",
"id": "payroll",
"status": "open",
"from": "opened this year"await lingua.jobs.get("job_7Hq2") // its wait and its open request list
"id": "job_7Hq2",
"status": "waiting",
"on": "party",
"id": "ask_7Hq4",
"step": "organizer",
"to": "upload",
"status": "open",
"id": "bank",
"status": "open",
"id": "payroll",
"status": "open",
"from": "opened this year"An agent reads the list and carries the link to the client, who answers through it.
after: '<ask>.<item>' starts a step as soon as that item is in, while the rest are still open:
reconcile: { by: async (_, job) => ({ accounts: accounts(job) }), after: 'organizer.bank' },→ POST /asks/ask_7Hq7?t=Veld6gxJn8YTVqzW-IuUdK {"items":{"bank":{"file":"bank-2026.pdf"}}} # organizer · upload
← job_7Hq3 · waiting · on party · $90.00 held
record consent · client · consent | terms · client · sign · esign | screen · code | organizer.bank · answered | reconcile · codeAn item the client can't send is waived with a reason, or substituted with what they have. Either way the list is done, and the record says which:
→ POST /asks/ask_7Hq7?t=Veld6gxJn8YTVqzW-IuUdK {"items":{"payroll":{"waive":"The payroll account closed in March 2025."},"card":{"substitute":"A ledger export of the card account"}}} # organizer · upload
← job_7Hq3 · delivered · $90.00 charged
record consent · client · consent | terms · client · sign · esign | screen · code | organizer.bank · answered | reconcile · code | organizer.payroll · waived | organizer.card · substituted | organizer · client · upload | summary · ai | review · ai · passA step at the top can route the job before any work: here a screen sends a kind of client the brand doesn't take to a declared end that charges $0.
→ POST /annual-accounts {"client":{"name":"Ada Lovelace","email":"ada@example.com"},"company":"helping-hands","kind":"charity","max_usd":90,"year":"2026","key":"books-charity"}
← job_7Hq4 · waiting · on party · $90.00 held · consent · consent
→ POST /asks/ask_7Hq8?t=bpNrN7KRd_tVwQ97uaCGlt {} # consent · consent
→ POST /asks/ask_7Hq9?t=TS_n-HMFWeuUfAgAcvC1gx {} # terms · sign
← job_7Hq4 · ended · referred_out · $0
record consent · client · consent | terms · client · sign · esign | screen · code | referred_out · endedA client who declines consent ends the job at declined, and owes nothing.
| Op | API | CLI | MCP | SDK | Site | Docs | App |
|---|---|---|---|---|---|---|---|
job.get | GET /jobs/:id | jobs get <job> | get_job | jobs.get() | 1 error page | customer portal | |
ask.get | GET /asks/:id | asks get <ask> | never: a person acts | never: a person acts | 1 error page | hosted link, owner console | |
ask.complete | POST /asks/:id | never: a person acts | never: a person acts | 6 error pages | hosted link | ||
ask.reissue | POST /asks/:id/reissue, POST /asks/:id/link | asks reissue <ask> | reissue_ask | asks.reissue() | 3 error pages | customer portal, owner console |
Objects: ask_.
Events: ask.opened, item.done, ask.reminded, ask.done, ask.expired, ask.voided, ask.relinked, consent.given, job.paused, job.ended.
Operations has every column of every row.
The customer portal lists every open ask and item across jobs, with each item's source, under Outstanding items (The customer portal).
| Code | Category | HTTP | Why |
|---|---|---|---|
ask_expired | conflict | 410 | This ask expired. |
needs_the_party | blocked | 403 | Only the party can do this, through their own link. |
input_invalid | invalid_request | 422 | The order doesn't match the Service's input. |
The lines that use it in the example brands, as their files write them, and what check read aloud for each file.
evidence: { ask: 'client', to: 'upload', items: () => [{ id: 'specimen', label: 'A specimen showing the mark in use' }] },services/trademark-response.ts · 21 lines · $349 per job, within 48 hours, charged on file's receipt.
steps consent · client · consent → read · ai · cites source → evidence · client · upload → draft · ai → attack · ai · adversarial · 3 lenses → respond · attorney · * · stamped · 2-item checklist → file · USPTO → fee · USPTO · routedThe legal example runs every one of its files, captured.
organizer: { ask: 'owner', to: 'upload', items: requestList, expires: '180d' },
file: {
submit: efile, to: 'IRS and states', receipt: 'receipts', each: { of: 'jurisdictions', lead: j => j === 'US' },
rejected: { ein_mismatch: { to: 'organizer', items: reask.ein_mismatch }, schedule_missing: { to: 'organizer', items: reask.schedule_missing }, math: 'draft', '*': 'prepare' }, perfect: '10d',
},services/annual-return.ts · 45 lines · $1,800 per job, filed 5 days before its deadline, 20% off if late, or we pay the penalty and its interest, up to $5,000, after we ask the IRS to take it off, charged when file is accepted.
steps consent · owner · consent · §7216, may disclose to a preparer outside the US and a lender you name → organizer · owner · upload → compute · code · cites depreciation and carryforward schedules → draft · ai · cites the workpapers and the records the organizer took → check · ai · adversarial · 3 lenses → sign · signer · * · stamped in the paid-preparer block · 3-item checklist → authorize · owner · sign · Form 8879-CORP or 8879-PE → file · IRS and states → prepare · preparer · * · 1-item checklist · routed → tentative · code · cites the year as the ledger stands, less the estimates paid · routed → pay · Treasury and states · routed → extend · IRS and states · routedThe tax example runs every one of its files, captured.
The tax example's organizer builds its list the same way: what last year's return carried, from job.subject.prior('compute'); the year's estimates to confirm, from job.subject.fact('paid', { period, all: true }); and an account number for each state. Annual return runs consent, a year's request list and the re-asks a rejection sends back to it.