Ask the buyer
Add an ask step. The buyer answers one question through a link that needs no account, and the job's clock pauses until they do.
Some work needs something only a person on the buyer's side can give: an answer, a file, a signature, consent or a decision. An ask step puts it to them through a hosted link that needs no account, and the job's deadline pauses until they act. Here the buyer says which terms stay in English. This is Grow 2.
check counts 9 lines:
import { Service } from 'services.dev'
// Grow 2, ask the buyer: before the AI starts, the buyer says which terms stay in English, through a
// link that needs no account. The job waits on them, and its clock pauses until they answer.
export default Service({
name: 'Translation',
input: { document: 'file', to: 'language: a BCP 47 tag, such as fr-CA' },
output: { document: 'file' },
deliver: {
glossary: { ask: 'buyer', to: 'answer', items: () => [{ id: 'terms', label: 'Which terms stay in English?' }] },
translate: 'ai',
harden: { review: 'adversarial', lenses: ['meaning', 'terms'], rounds: 2 },
},
price: 49,
})glossary is an ask. It's written first, so it runs first, and translate waits for it.
| Key | What it does | Default |
|---|---|---|
ask | Who acts: 'buyer', or a party input such as a co-signer (party? when optional, party[] for several) | required |
to | What they do: answer, upload, sign, consent, decide, direct, or grant, a scoped and expiring permission a later step needs (Grants) | required |
items | A request list, a function of the job, so the questions can follow the input. Each item is answered, waived with a reason, or substituted; a step can start on one with after: 'glossary.terms'. Request lists derives one from the job and its subject deliver.items | none |
expires | When the link stops working. The ask then opens once more, with a new link; a second expiry lapses the job, which charges only the acts already committed | 7d |
remind | Reminders, each this long before the link expires | ['1d'] |
choices | For decide: the options the party picks from. A decision is theirs alone | none |
how | How the act is proven: 'click', 'esign', 'id-verified' or 'notarized', kept on the record | 'click' |
Asks lists the rest: what a signature binds, all for several parties, capacity and void.
npx services.dev checkservices/translation.ts · 9 lines · $49 per job, within 48 hours, charged on delivery.
who AI delivers every job. The buyer answers at glossary.
steps glossary · buyer · answer → translate · ai → harden · ai · adversarial · 2 lenses
agent order_translation · 29 of 30 words · job_7Hq2 waits on party
ledger +$49.00 · AI $1.26 (example) · you $47.74
margin at least $39.20 a job (80%) · a job that stops costs you at most $9.80
apps customer portal · owner console
mcp 13 buyer tools · 4,494 of 8,000 tokens · 9 more with ?toolset=sandbox
✓ ready to sell · 1 Service · it keeps at least 80% of every job
next npx services.dev dev
AI and money are simulated until you wire them. $0.42 a call is an example.The who line names the step where the buyer answers, and the agent line says an order now waits on a party. The margin doesn't move: asking costs nothing.
No magic value is needed. In this capture the buyer answers two hours after the order; on localhost, dev serves the link, so open the one the CLI prints and answer it yourself.
npx services.dev translation document=https://example.com/lease.pdf to=fr-CA --max 49 --key growjob_7Hq2 · working · $49.00 held
job_7Hq2 · waiting · on party · $49.00 held
record
next hand http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ to the person it asks
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
$ npx services.dev jobs wait job_7Hq2
job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 chargedcurl -X POST http://localhost:8787/jobs -H 'content-type: application/json' -d '{"service":"translation","document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":49}' next get_job {"id":"job_7Hq2"}
$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · waiting · on party · $49.00 held
next hand_to_person {"ask":"ask_7Hq2"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged→ tools/call order_translation {"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":49}← job_7Hq2 · working · $49.00 held
next get_job {"id":"job_7Hq2"}
→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · waiting · on party · $49.00 held
next hand_to_person {"ask":"ask_7Hq2"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 chargedawait lingua.translation.order({"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":49})← job_7Hq2 · working · $49.00 held
next get_job {"id":"job_7Hq2"}
await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · waiting · on party · $49.00 held
next hand_to_person {"ask":"ask_7Hq2"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 chargedEvery surface answers the same way: the job waits on: party, and its next step is hand_to_person, with the link. An answer or upload may also come from an agent holding the party's delegated key; sign, consent and decide need the party's own link, and any other caller is refused needs_the_party. The record:
→ 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"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
← job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged
record glossary.terms · answered | glossary · buyer · answer · clock paused 2h | translate · ai | harden · ai · adversarial · 2 lenses · pass
ledger +$49.00 · AI $1.26 · you $47.74waiting reads "clock":"paused", and the record keeps the two hours as clock paused 2h, so the wait doesn't count against the 48-hour deadline. Deadlines lists everything that pauses it.
npx services.dev diff grow/review.ts grow/ask.tsgrow/review.ts (8 lines) → grow/ask.ts (9 lines)
Translation · changed deliver, human · adds an ask step: a party acts through a hosted link
API ~ operation service.order.translation POST /translation item.description
~ operation service.quote.translation GET /translation.json answers.deliver, answers.human.sentence, answers.surfaces.description
+ operation ask.get GET /asks/{id}
+ operation ask.complete POST /asks/{id}
+ operation ask.reissue POST /asks/{id}/reissue
~ operation brand.manifest GET /manifest.json answers.services
+ object ask_ ask
+ 10 events job.paused, job.ended, ask.opened, item.done, ask.reminded, ask.done, ask.expired, ask.voided, ask.relinked, consent.given
+ 4 errors needs_the_party, ask_expired, ask_voided, signature_void
CLI + operation ask.get services.dev asks get <ask>
+ operation ask.reissue services.dev asks reissue <ask>
+ 10 events job.paused, job.ended, ask.opened, item.done, ask.reminded, ask.done, ask.expired, ask.voided, ask.relinked, consent.given
+ 4 errors needs_the_party, ask_expired, ask_voided, signature_void
MCP ~ operation order_translation order_translation description
+ operation reissue_ask reissue_ask
SDK + operation ask.reissue asks.reissue()
~ object job_ (translation) Job<"translation"> + stage: "glossary" | "translate" | "harden"
An ask is the first key that adds calls: ask.get, ask.complete and ask.reissue, the hosted page at /a/:id, ten events and four errors, each on every surface that reaches it.
In a brand, the legal example asks the client for consent before any work starts, and for a decision when a reply comes back.
Add a signer: a sworn translator signs every job.