---
title: "Guards"
url: https://services.dev/docs/service/guards
captured: [legal-walk.txt, sales-check.txt, service-guard.txt, tax-check.txt]
codes: [guard_refused]
---
# Guards

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

> A guard checks a step's output before it commits, such as a floor, a cap or a required field. The AI redoes a refused proposal, and a person's step stays theirs until it holds.

A guard answers one question: is this output acceptable, whoever proposed it?

## Smallest form

```ts title="services/checked-translation.ts"
    translate: { by: 'ai', guard: { match: (v, job) => v >= job.policy.match || `below the match the client set (${v} < ${job.policy.match})` } },
```

## Every form

**On a step**

| Key | Written | Left out | Does |
|---|---|---|---|
| `guard` | `Guard` | none | Checks on the output before it commits; a failure is `guard_refused`, for AI and people alike. |
| `checklist` | `string[]` | none | What a person checks, item by item. |

## Return true, or say why

A guard is a function per output field. It reads the proposed value and the job, and returns `true` or the reason it can't stand. The reason is what everyone sees: the record, the person, the agent.

## The AI's proposal

Refused, the AI redoes it inside its budget, and the record keeps both:

```txt
→ POST /checked-translation {"document":"manual.pdf","to":"de","policy":{"match":0.9},"max_usd":79,"key":"guarded","metadata":{"sandbox":{"propose":{"match":[0.7,0.95]}}}}
```

```txt title="magic values: metadata.sandbox.propose = { match: [0.7, 0.95] }"
← job_7Hq2 · waiting · on person · $79.00 held
  record  translate · ai · refused · guard_refused · match: below the match the client set (0.7 < 0.9) | translate · ai · redo
```

## A person's proposal

A person gets the same refusal as a 422, inline, and the step stays theirs:

```txt
→ POST /proofreaders/jobs/job_7Hq2/approve {"output":{"match":0.8}}
```

```txt title="422 · magic values: metadata.sandbox.propose = { match: [0.7, 0.95] }"
  "code": "guard_refused",
  "reason": "match: below the match the client set (0.8 < 0.9)",
    "do": "approve",
    "tool": null,
    "http": "POST http://localhost:8787/jobs/job_7Hq2/approve"
```

`next` is `approve` with no MCP tool: the step is still the proofreader's, in their app, and no key can take it.

```txt
→ POST /proofreaders/jobs/job_7Hq2/approve {"output":{"match":0.96}}
```

```txt title="200 · job_7Hq2 · working · $79.00 held · magic values: metadata.sandbox.propose = { match: [0.7, 0.95] }"
← job_7Hq2 · delivered · manual.de.pdf · $79.00 charged
```

## Guards and policy

A guard reads `job.policy`, so the bound can be the client's. Here `match` is a merits value only the client sets, and the guard holds the AI and the proofreader to it. See [policy](https://services.dev/docs/service/policy.md).

## A checklist

`checklist` lists what a person affirms, item by item. An attestation missing an item is refused, the same way.

## Signals don't block

A [signal](https://services.dev/docs/service/signals.md) names a fact and can move the job. It never refuses an output. Blocking is a guard's job.

## 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 |

Events: `step.refused`.

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

## Refusals

| Code | Category | HTTP | Why |
|---|---|---|---|
| [`guard_refused`](https://services.dev/docs/reference/errors.md#guard_refused) | guard | 422 | A guard refused the output, and says why. |

## 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"
    advise: { by: 'attorney', stamp: true, est: '45min', checklist: ['every flag is addressed', 'the memo answers the goals'] },
```

```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/amended-return.ts · 22 lines"
    prepare: { by: 'preparer', est: '1h', checklist: ['every changed line traces to a source'] },
```

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

```ts title="sales · services/deal.ts · 42 lines"
      guard: { held: (m, job) => m.attended(job.input.account) >= 10 || 'no one from the account stayed 10 minutes' } },
      guard: { closed_value: (v, job) => v >= job.input.value * job.policy.floor || 'below the floor you set' } },
```

```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: [Propose, then the gate commits](https://services.dev/docs/service/gate.md)
- Next: [What the buyer sets, and what only the client sets](https://services.dev/docs/service/policy.md)
