Intake and request lists

The running jobjob_7Hq2 · working · $49.00 held

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:

services/annual-accounts.ts
    consent: { ask: 'client', to: 'consent' },
    terms: { ask: 'client', to: 'sign', how: 'esign' },
captured/service-intake.txt · magic values: none
→ 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 · consent

On an ask step

KeyWrittenLeft outDoes
ask'buyer' | <party input>requiredWho acts: the buyer, or a party, party? or party[] input.
toAskTorequiredWhat the party does: 'sign', 'consent', 'answer', 'upload', 'decide', 'direct' or 'grant'.
items(job) => AskItem[]noneFor answer and upload: a request list derived from the job. A step can start on one item: after: '<ask>.<item>'.
expiresWithin'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

KeyWrittenLeft outDoes
idstringrequiredIts name: a step can start on it with after: '<ask>.<item>'.
labelstringrequiredThe question or the document asked for, as the party reads it.
optionaltruenoneThe ask is done without it.
remind'${number}d'[]noneReminders for this item alone, each this long before the link expires, while the item is still open.
fromstringnoneWhere 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

KeyWrittenLeft outDoes
itemsRecordnoneA request list's items: each answered (answer or file), waived with a reason, or substituted.
declineunknownnoneThe party says no. A decline ends the job at declined.
reasonunknownnoneWhy, 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.

captured/service-intake.txt · magic values: none
→ 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 year

items 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:

captured/service-intake.txt · magic values: none
→ 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 year

A 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:

captured/intake-tabs.txt · magic values: none
$ 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"
captured/intake-tabs.txt · 200 · magic values: none
$ 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"
captured/intake-tabs.txt · magic values: none
→ 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"
captured/intake-tabs.txt · magic values: none
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:

services/annual-accounts.ts
    reconcile: { by: async (_, job) => ({ accounts: accounts(job) }), after: 'organizer.bank' },
captured/service-intake.txt · magic values: none
→ 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 · code

An 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:

captured/service-intake.txt · magic values: none
→ 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 · pass

A 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.

captured/service-intake.txt · magic values: none
→ 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 · ended

A client who declines consent ends the job at declined, and owes nothing.

OpAPICLIMCPSDKSiteDocsApp
job.getGET /jobs/:idjobs get <job>get_jobjobs.get()1 error pagecustomer portal
ask.getGET /asks/:idasks get <ask>never: a person actsnever: a person acts1 error pagehosted link, owner console
ask.completePOST /asks/:idnever: a person actsnever: a person acts6 error pageshosted link
ask.reissuePOST /asks/:id/reissue, POST /asks/:id/linkasks reissue <ask>reissue_askasks.reissue()3 error pagescustomer 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).

CodeCategoryHTTPWhy
ask_expiredconflict410This ask expired.
needs_the_partyblocked403Only the party can do this, through their own link.
input_invalidinvalid_request422The 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.

legal · services/trademark-response.ts · 21 lines
    evidence: { ask: 'client', to: 'upload', items: () => [{ id: 'specimen', label: 'A specimen showing the mark in use' }] },
npx services.dev check · captured/legal-walk.txt
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 · routed

The legal example runs every one of its files, captured.

tax · services/annual-return.ts · 45 lines
    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',
    },
npx services.dev check · captured/tax-check.txt
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 · routed

The 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.