---
title: "deliver"
url: https://services.dev/docs/service/deliver
captured: [ci.txt, gate-instructions-too-long.txt, grow-submit-tabs.txt, legal-walk.txt, sales-build.txt, sales-check.txt, tax-check.txt]
codes: [needs_a_person, not_waiting, reserved]
---
# deliver

URL: https://services.dev/docs/service/deliver

> Who does the work on each job. Leave it out and AI does it, in one step. Name steps and each can be AI, your code, a person, a party or an outside system.

`deliver` answers one question: who does the work on each job.

## Smallest form

Leave it out. The four-line Translation has one AI step, briefed by `services/translation.md`, and the review every AI job gets:

```bash
npx services.dev check
```

```txt title="magic values: none"
  steps   draft · ai → review · ai · adversarial
```

Name the steps, and each one says who does it:

```ts title="grow/review.ts"
  deliver: {
    translate: 'ai',
    harden: { review: 'adversarial', lenses: ['meaning', 'terms'], rounds: 2 },
  },
```

## Every form

**On the Service**

| Key | Written | Left out | Does |
|---|---|---|---|
| `deliver` | `Deliver` | `'ai'`, briefed by services/&lt;id>.md | Who does the work: `'ai'`, a person or a role word, a URL, your function, or named steps in order. |

**The step kinds**

| Written | Does |
|---|---|
| `'ai'`, `'person'`, a role word, a URL, your function, another Service | `'ai'`, a person, a URL, your function, or another of your Services. A person is `'person'`, or a role word such as `'interpreter'`: the role names their app, `/interpreters`. Once you write `roles`, a role word is one of them. |
| `{ by, … }` | Work: AI, a person, your code, a URL or another Service proposes output. |
| `{ review, … }` | Review: a step that hardens the step before it (adversarial) or extends it, "yes, and" (affirmative). |
| `{ ask, to, … }` | Ask: a party acts, through a hosted single-use link or the customer portal, with no account. The job waits `on: 'party'`, and the step's `within` pauses. Every ask is an object, `ask_…`, with its own link. |
| `{ submit, to, receipt, … }` | Submit: send to a system you don't control, and keep its receipt. The send goes through the gate, so a guard can stop it, and the job waits `on: 'outside'` for the answer. A rejection is a transition: the job moves to the repair step `rejected` names, never an error answer. |

## Four kinds of step

A step is **work**, a **review**, an **ask** or a **submit**. `check` reads each one aloud, and the record prints who did it.

**Work** proposes output: AI from its [brief](https://services.dev/docs/service/ai.md), [your code](https://services.dev/docs/service/code.md), [another Service](https://services.dev/docs/service/nested.md), or a person. A role word makes a step a person's, and the job waits in the delivery portal for that role:

```bash
npx services.dev check
```

```txt title="magic values: none"
  steps   prep · ai → call · interpreter → recap · ai → review · ai · adversarial
```

**A review**, **an ask** and **a submit** each have their page: [review steps](https://services.dev/docs/service/review.md), [asks](https://services.dev/docs/service/ask.md) and [sending to outside systems](https://services.dev/docs/service/submit.md).

One run of [Grow 4](https://services.dev/docs/grow/submit.md)'s Translation, with all four:

```bash
npx services.dev jobs wait job_7Hq2
```

```txt title="magic values: metadata.sandbox.outside = { reject: [missing_certificate] }"
  record  glossary.terms · answered | glossary · buyer · answer · clock paused 2h | translate · ai | harden · ai · adversarial · 2 lenses · pass | sign · person · sworn translator · * · mbr_7Hq2 | file · the registry · rejected · missing_certificate · repairing at sign
```

## Escalate to a person

A step stays AI's until the job says a person should take it. `escalate: { to, when }` names the role and the condition: a function of the job, read as the job reaches the step, or `'at_risk'` or `'late'`, read on [the deadline](https://services.dev/docs/service/within.md#escalate-before-its-late)'s tick while the step is open. When it holds, the step is the role's: its Task posts to the role's Pool, and the job waits on a claim, with the [fill ladder](https://services.dev/docs/people/pool/fill.md) running if no one takes it. A role with a [`span`](https://services.dev/docs/people/roles.md#one-person-across-several-steps) keeps the steps after it too.

[The Deal Service](https://services.dev/docs/examples/sales/deal.md#the-steps) hands the booking to a setter on a warm reply, and the meeting, with every step after it, to a closer from $50,000 or a buying committee:

```bash
cat services/deal.ts
```

```txt title="magic values: none"
const setter = job => job.policy.setter === 'person' || (job.policy.setter === 'on_warm' && job.signals.warm)
const person = job => job.input.value >= job.policy.humans_from || job.signals.committee || job.signals.person
    book: { by: 'ai', escalate: { to: 'setter', when: setter } },
    meet: { by: 'ai', escalate: { to: 'closer', when: person }, when: job => job.input.motion !== 'B2A',
```

`check` prints each escalation on the step it moves, and the ledger counts the role's pay only on jobs that escalate.

## The order they run in

Steps run in the order you write them, unless a step says otherwise. Branches, loops, joins and parallel steps are on [the step graph](https://services.dev/docs/service/graph.md). The step that last committed is the job's stage, so the steps are the state machine and you never declare one.

## Every doer proposes

No step writes to the job directly. AI, your code, a person and an outside system each propose, and [the gate](https://services.dev/docs/service/gate.md) commits or refuses. A person's work meets the same checks as the AI's.

## Seven surfaces

| Op | API | CLI | MCP | SDK | Site | Docs | App |
|---|---|---|---|---|---|---|---|
| `job.approve` | `POST /:portal/jobs/:id/approve`, `POST /jobs/:id/approve`, `POST /jobs/:id/sign`, `POST /owner/jobs/:id/approve` | `jobs approve <job>` | never: a person acts | never: a person acts |  | 8 error pages | owner console, delivery 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_`, `rcp_`.
Events: `job.paused`, `step.refused`, `ask.opened`, `item.done`, `ask.reminded`, `ask.done`, `ask.expired`, `ask.voided`, `ask.relinked`, `consent.given`, `job.ended`, `submission.sent`, `receipt.accepted`, `receipt.rejected`, `receipt.timed_out`, `review.done`.

[Operations](https://services.dev/docs/reference/operations.md) has every column of every row.

## Refusals

| Code | Category | HTTP | Why |
|---|---|---|---|
| [`needs_a_person`](https://services.dev/docs/reference/errors.md#needs_a_person) | blocked | 403 | A person approves and signs, in the app. A key or a tool never can. |
| [`not_waiting`](https://services.dev/docs/reference/errors.md#not_waiting) | conflict | 409 | The job isn't waiting on a person. |
| [`reserved`](https://services.dev/docs/reference/errors.md#reserved) | blocked | 403 | The rules reserve this act to a credential the person who attested doesn't hold. Nothing was charged. |

| `check` gate | Why |
|---|---|
| [`step_unreachable`](https://services.dev/docs/reference/check/step_unreachable.md) | A step no edge reaches never runs. |

| Written | What TypeScript says |
|---|---|
| `input: { document: 'file' }, output: { document: 'file' }, deliver: { translate: translation }, price: 99` | `Property 'to' is missing in type '{ readonly document: FileRef; }' but required in type '{ readonly document: FileRef; readonly to: string; }'.` |

## In the example brands

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

```ts title="legal · services/contract-review.ts · 16 lines"
  deliver: {
    consent: { ask: 'client', to: 'consent', expires: '7d' },
    flag: 'ai',
    attack: { review: 'adversarial' },
    advise: { by: 'attorney', stamp: true, est: '45min', checklist: ['every flag is addressed', 'the memo answers the goals'] },
    send: { by: 'ai' },
  },
```

```txt title="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](https://services.dev/docs/examples/legal.md) runs every one of its files, captured.

```ts title="tax · services/notice-response.ts · 19 lines"
  deliver: {
    read: { by: 'ai', cite: 'source' },
    poa: { ask: 'owner', to: 'grant', after: 'respond.claim', what: 'Form 2848', names: { claimant: 'caf' }, scope: job => ({ acts: ['represent', 'send'], matters: [{ tax: 'income', form: '1120', periods: job.input.tax_years }], for: '365d' }), how: 'esign', capacity: 'officer', withdrawn: ['accepted', 'for'] },
    respond: { by: 'representative', act: 'represent', needs: 'poa', stamp: true, est: '1h', drafts: { misapplied: 'a transfer request citing the receipt', penalty: 'a request to abate', extend: 'a Form 872 limited to the issues and a date' } },
    send: { submit: 'https://correspondence.example.test/replies', to: 'the agency', receipt: 'receipt', needs: 'poa' },
  },
```

```txt title="npx services.dev check · captured/tax-check.txt"
services/notice-response.ts · 19 lines · $299 per job, or $99 if you answer it yourself, or 25% of a penalty abated, $99 at least, within 48 hours, charged on send's receipt.
  steps   read · ai · cites source → match · code · cites each payment's receipt, and the period it was for → classify · code · agree to pay, the rest to respond → review · ai · adversarial → respond · representative · * · stamped → poa · owner · grant · Form 2848, after respond.claim → send · the agency → pay · Treasury · routed
```

[The tax example](https://services.dev/docs/examples/tax.md) runs every one of its files, captured.

```ts title="sales · services/deal.ts · 42 lines"
  deliver: {
    source: 'ai',
    qualify: { by: 'ai', next: job => (job.output.fit ? 'book' : 'lost') },
    book: { by: 'ai', escalate: { to: 'setter', when: setter } },
    meet: { by: 'ai', escalate: { to: 'closer', when: person }, when: job => job.input.motion !== 'B2A',
      guard: { held: (m, job) => m.attended(job.input.account) >= 10 || 'no one from the account stayed 10 minutes' } },
    propose: { by: 'ai' }, negotiate: { by: 'ai' },
    close: { by: 'ai', next: job => (job.output.closed_value ? 'won' : 'lost'),
      guard: { closed_value: (v, job) => v >= job.input.value * job.policy.floor || 'below the floor you set' } },
    nurture: { by: 'nurturer', routed: true, next: job => (job.output.revived ? job.record.interrupted : 'lost') },
  },
```

```txt title="npx services.dev check · captured/sales-check.txt"
services/deal.ts · 42 lines · nothing on effort; 5% of closed_value at won, proven by the account's payment, held 60 days by bank, 120 by card.
  steps   source · ai → qualify · ai → book · ai · setter when warm → meet · ai · closer from humans_from, committee or person · skipped for B2A · guard held: the account attends 10 minutes → propose · ai · the closer's span → negotiate · ai · the closer's span → close · ai · the closer's span · guard closed_value at least value × floor → nurture · nurturer · routed
```

[The sales example](https://services.dev/docs/examples/sales.md) runs every one of its files.

---

- Up: [Service()](https://services.dev/docs/service.md)
- Previous: [Fields and type words](https://services.dev/docs/service/fields.md)
- Next: [The step graph](https://services.dev/docs/service/graph.md)
