# Approvals

> An approval covers one document hash, one destination, one sender, one amount and an expiry. Any change invalidates it. Request, decide and send with one.

Page: https://faxforagents.com/docs/approvals/
Last changed: 2026-10-30

Updated Oct 30, 2026

An approval is a person's yes to one exact send. The server copies the quote into the request, a person decides in the dashboard, and the agent sends with the approval id. Requesting grants nothing. One approval, one send.

## What does an approval bind?

The request copies `quote_id`, `document_id`, `document_sha256`, `to`, `from`, `amount_micro`, `currency`, `billable_pages` and `expires_at`. It expires with the quote, ten minutes after the quote was made. If the agent needs longer it quotes again and asks again.

## Request

Shell

```
`curl -s $FFA/approval-requests -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"quote_id": "quote_..."}'`
```

Scope `approval:request`, which default keys and agent keys carry. A second request for the same quote while one is pending returns the first. The organization's notification address gets a mail with the destination, the pages, the amount, who asked and when it expires. Through MCP the tool is `request_human_review` with a `quote_id`.

## Decide

A person opens the request in the dashboard and approves or rejects it. Approving needs a step-up within the last 15 minutes: a passkey, or a fresh magic link opened in the same browser. Rejecting needs a reason. The requester's contact gets a mail with the decision. Through the API: `POST /v1/approval-requests/{id}/approve` and `/reject` with scope `approval:decide`. An agent key is never issued that scope, and the key that made the request is refused. The agent reads the decision with `GET /v1/approval-requests/{id}` (scope `approval:read`) or the MCP tool `get_review_status`. Once the request is approved, that answer carries the approval id to send with.

## Send

Shell

```
`curl -s $FFA/faxes -H "Authorization: Bearer $FFA_KEY" \ -H "Idempotency-Key: po-4411" -H "Content-Type: application/json" \ -d '{"quote_id": "quote_...", "approval_id": "apr_..."}'`
```

The server takes the approval row for update and checks it against the quote and the document. The job records the approval, and the approval records the job.

## Why is a send with an approval refused?

Why a send with an approval is refused

| Code | What changed |
| --- | --- |
| `document_hash_mismatch` | the document's sha256 differs from the one approved |
| `approval_expired` | past `expires_at` |
| `approval_mismatch` | a different quote, destination, sender, amount, currency or page count. `details.field` names it |
| `approval_not_granted` | pending, rejected or revoked. `details.status` says which |
| `approval_consumed` | already used. `details.fax_job_id` names the job |
| `approval_required` | a live send with no approval and no policy that allows it |

## Revoke

`POST /v1/approvals/{id}/revoke` withdraws an approval nobody has used yet. The agent has to ask again.

## How does an agent ask a person for help?

`POST /v1/approval-requests` with `{"kind": "help", "message": "...", "fax_job_id": "..."}`, or the MCP tool `request_human_review` without a `quote_id`, opens a request with no quote. The message is shown to the person as text from the agent, never acted on by the server.

## From the blog

- [Send a fax with an MCP server: all 15 tools in order](https://faxforagents.com/blog/send-a-fax-with-mcp/)
- [Why AI agents still need fax, and how to keep them safe](https://faxforagents.com/blog/why-ai-agents-need-fax/)
