Asks: a ceremony without an account
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?
glossary: { ask: 'buyer', to: 'answer', items: () => [{ id: 'terms', label: 'Which terms stay in English?' }] },→ 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
| 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'. |
names | RoleWord | none | For direct: the role whose next Task the client locks to the one member they name. |
what | <step> | the step before | For sign: the step whose output is signed. The signature binds that output's hash. |
all | true | any one | For 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. |
capacity | string | string[] | none | The capacity the party acts in, printed on the record: 'officer', 'partner', 'trustee'. |
void | { tolerance: { <output>: number } } | none, so any change voids it | For sign: the change each output field may take and keep the signature. A number is the most it may move. |
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>'. |
choices | string[] | none | For decide: the choices. A decision is merits, the client's alone. |
scope | { acts: string[]; for: Within } | live until revoked | For 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. |
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: POST /asks/:id, through their link
| Key | Written | Left out | Does |
|---|---|---|---|
answer | unknown | none | An answer ask's answer. |
file | unknown | none | An upload ask's file. |
items | Record | none | A request list's items: each answered (answer or file), waived with a reason, or substituted. |
choice | unknown | none | A decide ask's choice. |
member | unknown | none | A direct ask's member: the one member of its role the client names. |
end | unknown | none | A direct ask on a declined job: the end the client chooses instead of another member. |
decline | unknown | none | The party says no. A decline ends the job at declined. |
reason | unknown | none | Why, 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:
$ 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:
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
← 200 · ask_7Hq2 · doneWhile the job waits on them, its deadline pauses, and the record says for how long:
$ 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 · passEach 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:
→ 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:
→ 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:
sign: { ask: 'client', to: 'sign', what: 'translate', how: 'esign', void: { tolerance: { pages: 1 } } },→ 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:254af4bffb58225dda08346ed8b56de950d74f7013a2258a1cce44dc260a0587Each 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.
→ 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.00A 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:
$ 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_7Hq3A 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:
$ 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.
| Op | API | CLI | MCP | SDK | Site | Docs | App |
|---|---|---|---|---|---|---|---|
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.
| Code | Category | HTTP | Why |
|---|---|---|---|
needs_the_party | blocked | 403 | Only the party can do this, through their own link. |
ask_expired | conflict | 410 | This ask expired. |
ask_voided | conflict | 410 | This link no longer works: used, reissued or voided. |
signature_void | conflict | 409 | What was signed changed past its tolerance. |
| Written | What TypeScript says |
|---|---|
input: { applicant: 'party', document: 'file' }, output: { document: 'file' }, deliver: { sign: { ask: 'document', to: 'sign' } }, price: 49 | Type '"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: 49 | Type '"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.
consent: { ask: 'client', to: 'consent', expires: '7d' },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 · aiThe legal example runs every one of its files, captured.
authorize: { ask: 'owner', to: 'sign', what: authorization, how: 'id-verified', capacity: 'officer', void: { tolerance: { balance_due: 1 } } },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.adjustedThe tax example runs every one of its files, captured.