Asks: a ceremony without an account

The running jobjob_7Hq2 · working · $49.00 held

An ask step waits on a party to sign, consent, answer, upload, decide or direct, through a single-use link that needs no account. Only that party can complete it.

An ask answers one question: what does this job need from someone outside the brand, and who alone can give it?

grow/ask.ts
    glossary: { ask: 'buyer', to: 'answer', items: () => [{ id: 'terms', label: 'Which terms stay in English?' }] },
captured/grow-ask.txt · magic values: none
→ POST /translation {"document":"lease.pdf","to":"fr-CA","key":"grow","max_usd":49}
← job_7Hq2 · waiting · on party · $49.00 held
  waiting {"on":"party","since_at":"2026-10-25T14:02:11.000Z","step":"glossary","clock":"paused"}

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'.
namesRoleWordnoneFor direct: the role whose next Task the client locks to the one member they name.
what<step>the step beforeFor sign: the step whose output is signed. The signature binds that output's hash.
alltrueany oneFor a party[] input: every party acts, and the ask is done when the last one has.
how'click' | 'esign' | 'id-verified' | 'notarized''click'How the act is proven, recorded with it.
capacitystring | string[]noneThe capacity the party acts in, printed on the record: 'officer', 'partner', 'trustee'.
void{ tolerance: { <output>: number } }none, so any change voids itFor sign: the change each output field may take and keep the signature. A number is the most it may move.
items(job) => AskItem[]noneFor answer and upload: a request list derived from the job. A step can start on one item: after: '<ask>.<item>'.
choicesstring[]noneFor decide: the choices. A decision is merits, the client's alone.
scope{ acts: string[]; for: Within }live until revokedFor grant: the acts it permits, and how long it stays live once given. Its state (live, expired, revoked) comes from the record; the party revokes it through their own link.
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: POST /asks/:id, through their link

KeyWrittenLeft outDoes
answerunknownnoneAn answer ask's answer.
fileunknownnoneAn upload ask's file.
itemsRecordnoneA request list's items: each answered (answer or file), waived with a reason, or substituted.
choiceunknownnoneA decide ask's choice.
memberunknownnoneA direct ask's member: the one member of its role the client names.
endunknownnoneA direct ask on a declined job: the end the client chooses instead of another member.
declineunknownnoneThe party says no. A decline ends the job at declined.
reasonunknownnoneWhy, with a decline.

Each ask has an id, a status (open, done, expired or voided), an expiry and its own link on the brand's domain:

captured/ask.txt · magic values: none
$ curl http://localhost:8787/asks/ask_7Hq2
{
  "id": "ask_7Hq2",
  "object": "ask",
  "job": "job_7Hq2",
  "step": "glossary",
  "to": "answer",
  "party": "buyer",
  "what_hash": null,

The party opens the link and acts. They need no account, and the link works once. Behind the page is one call, POST /asks/:id with the link's token, and its body is the last table in Every form:

captured/ask.txt · 200 · ask_7Hq2 · done · magic values: none
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
← 200 · ask_7Hq2 · done

While the job waits on them, its deadline pauses, and the record says for how long:

captured/ask.txt · magic values: none
$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged
  record  glossary.terms · answered | glossary · buyer · answer · clock paused 2h 5min | translate · ai | review · ai · pass

Each item can be answered, waived with a reason or substituted, and a step that needs only one item can start on it with after: 'glossary.terms'.

An agent can carry the link to its person. It can't act for them, and neither can the brand. Over MCP the job's next step is hand_to_person, with the url. An agent that tries to finish the job itself is refused, and the refusal hands it the link again:

captured/hand-to-person.txt · magic values: none
→ tools/call accept_job {"id":"job_7Hq2"}
← {
  "code": "needs_the_party",
  "reason": "glossary waits on the buyer to answer. Only they can, through their own link. Hand them the link in next.",
    "do": "hand_to_person",
    "url": "http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_",
  "doc_url": "http://localhost:8787/docs/errors/needs_the_party",

The brand's own console gets the same no:

captured/service-lapse.txt · 403 · needs_the_party · sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next. · magic values: client.email = silent@example.test
→ POST /owner/jobs/job_7Hq2/approve
← 403 · needs_the_party · sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.

An answer or upload ask may also be completed by an agent holding the party's delegated key, once delegated keys ship. A sign, a consent or a decision is always the party's own.

A signature binds the hash of the step it signs. void.tolerance says how far each field may move and keep it. Typesetting adds a page and the signature holds; it adds three, and the client signs again before delivery:

services/signed-translation.ts
    sign: { ask: 'client', to: 'sign', what: 'translate', how: 'esign', void: { tolerance: { pages: 1 } } },
captured/service-sign.txt · magic values: client.email = signs@example.test
→ POST /signed-translation {"document":"lease.pdf","to":"fr","client":{"name":"Ada Lovelace","email":"signs@example.test"},"added":1,"max_usd":120,"key":"added-1"}
← job_7Hq2 · delivered · lease.fr.pdf · $120.00 charged
  record  translate · ai | harden · ai · adversarial · 2 lenses · pass | sign · client · sign · esign | typeset · code
  pages   1: typesetting added 1, and the signature holds a change of 1
  asks    ask_7Hq2 · done · sha256:0f613db007970da2596d27f5be43688dec330d5118ee7d1cbc1482befc98ec46
← job_7Hq3 · delivered · lease.fr.pdf · $120.00 charged
  record  translate · ai | harden · ai · adversarial · 2 lenses · pass | sign · client · sign · esign | typeset · code | sign · signature void | sign · client · sign · esign
  pages   3: typesetting added 3, and the signature holds a change of 1
  asks    ask_7Hq3 · voided · sha256:0f613db007970da2596d27f5be43688dec330d5118ee7d1cbc1482befc98ec46
          ask_7Hq4 · done · sha256:254af4bffb58225dda08346ed8b56de950d74f7013a2258a1cce44dc260a0587

Each ask keeps what_hash, the hash it binds. The voided ask bound the work before typesetting; the new one binds it as delivered.

A consent comes before the work it covers. A decision is the client's choice among choices, and it is merits: once committed, the gate never undoes it on mechanics.

captured/service-consent.txt · magic values: client.email = signs@example.test, metadata.sandbox.asks = { register: informal }, client.email = declines@example.test
→ POST /private-translation {"document":"memo.pdf","to":"de","client":{"name":"Ada Lovelace","email":"signs@example.test"},"max_usd":59,"key":"consents","metadata":{"sandbox":{"asks":{"register":"informal"}}}}
← job_7Hq2 · delivered · memo.de.pdf · $59.00 charged
  record  consent · client · consent | register · client · decide · decided informal | translate · ai | review · ai · pass
  ledger  +$59.00 · AI $0.84 · you $58.16
← job_7Hq3 · ended · declined · $0
  record  declined · ended · Ada Lovelace declined consent.
  ledger  you $0.00

A party who declines ends the job at declined, and only paid acts already done are owed.

An upload ask takes a file, and a repair can be one. When the outside system rejects a filing, the buyer uploads the corrected file through the link:

captured/rejected.txt · magic values: metadata.sandbox.outside = reject_once
$ curl http://localhost:8787/jobs/job_7Hq2
  record  translate · ai | review · ai · pass | file · the registry · rejected · reject_once · repairing at correct | correct · buyer · upload · clock paused 1d | file · the registry · accepted · rcp_7Hq3

A link expires after 7 days, with a reminder a day before. An expired ask opens once more, and a second expiry ends the job at lapsed:

captured/service-lapse.txt · magic values: client.email = silent@example.test
$ curl http://localhost:8787/owner/jobs/job_7Hq2   # 2026-11-08: the second ask expires
← job_7Hq2 · ended · lapsed · lease.fr.pdf · $0
  record  translate · ai | harden · ai · adversarial · 2 lenses · pass | sign · reminded 1d before expiry | sign · expired | sign · reminded 1d before expiry | sign · expired | lapsed · ended · clock · sign expired twice.

POST /asks/:id/reissue sends a fresh link and voids the old one.

to: 'direct' lets the client name the one member who takes a role's next Task: { ask: 'client', to: 'direct', names: '<role>' }. A member's decline opens one on its own, and the job waits on: 'direction' until the client answers through their link: declines and direction walks it, from the decline to the member the client names. to: 'grant' is a scoped, expiring permission a step needs, and only the party revokes it: Grants and decisions. A request list for answer and upload asks has its own page: Intake and request lists.

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

CodeCategoryHTTPWhy
needs_the_partyblocked403Only the party can do this, through their own link.
ask_expiredconflict410This ask expired.
ask_voidedconflict410This link no longer works: used, reissued or voided.
signature_voidconflict409What was signed changed past its tolerance.
WrittenWhat TypeScript says
input: { applicant: 'party', document: 'file' }, output: { document: 'file' }, deliver: { sign: { ask: 'document', to: 'sign' } }, price: 49Type '"document"' is not assignable to type '"applicant" | "buyer" | undefined'.
input: { client: 'party' }, output: { document: 'file' }, deliver: { authorize: { ask: 'client', to: 'grant', scope: { acts: ['file'], for: '30d' } }, file: { by: 'ai', needs: 'authorise' } }, price: 49Type '"authorise"' is not assignable to type '"authorize" | "file" | undefined'. Did you mean '"authorize"'?

The lines that use it in the example brands, as their files write them, and what check read aloud for each file.

legal · services/contract-review.ts · 16 lines
    consent: { ask: 'client', to: 'consent', expires: '7d' },
npx services.dev check · captured/legal-walk.txt
services/contract-review.ts · 16 lines · $99 per job, within 48 hours, charged on acceptance.
  steps   consent · client · consent → flag · ai → attack · ai · adversarial → advise · attorney · client · stamped · 2-item checklist → send · ai

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

tax · services/amended-return.ts · 22 lines
    authorize: { ask: 'owner', to: 'sign', what: authorization, how: 'id-verified', capacity: 'officer', void: { tolerance: { balance_due: 1 } } },
npx services.dev check · captured/tax-check.txt
services/amended-return.ts · 22 lines · $900 per job, within 30 days, charged when file or aar is accepted.
  steps   compute · code · cites depreciation and carryforward schedules → draft · ai · cites the workpapers and the records the organizer took → check · ai · adversarial · 2 lenses → prepare · preparer · * · 1-item checklist → sign · signer · * · stamped in the paid-preparer block → authorize · owner · sign → file · IRS and states, unless a partnership → aar · IRS · Form 8082, for a partnership · emits k1.adjusted

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