# Send a fax

> The send contract. PDF and image uploads, text, Markdown and HTML rendered, the quote that binds the price, the Idempotency-Key and the six job states.

Page: https://faxforagents.com/docs/send-fax/
Last changed: 2026-10-28

Updated Oct 28, 2026

A send is a document, a quote and a job. The document is immutable once created, the quote fixes the price, and the job records what the provider did. This page is the contract for all three.

## Documents

A document is created from one of five sources and ends as one immutable PDF with a sha256 and a page count. The limit is 200 pages and 25 MiB.

Document sources

| Source | Body of POST /v1/documents | Notes |
| --- | --- | --- |
| PDF upload | `{"upload_id": "up_..."}` | validated with a PDF parser first. Encrypted or unreadable files are refused with `encrypted_pdf` or `unsupported_format` |
| Image upload | `{"upload_id": "up_..."}` | PNG, JPG or TIFF, declared as `content_type` on the upload session. Converted to grayscale letter pages, see Images below |
| Text | `{"text": "..."}` | rendered to pages by the service |
| Markdown | `{"markdown": "..."}` | headings, lists, emphasis and tables |
| HTML | `{"html": "..."}` | rendered in a headless browser with scripts off and no network, see HTML below |

Office formats are not accepted. Convert a Word or Excel file to PDF first.

### Uploading a PDF

Shell

```
`# 1. open a session (25 MiB cap, one hour) curl -s $FFA/uploads -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"filename": "contract.pdf", "size_bytes": 184233}' # answer: id (up_...) and a signed upload_url # 2. send the bytes to the signed upload_url (no key) curl -s -X PUT "$UPLOAD_URL" --data-binary @contract.pdf # 3. finish; the size and the optional sha256 are checked curl -s -X POST $FFA/uploads/up_.../complete -H "Authorization: Bearer $FFA_KEY" # 4. make the document curl -s $FFA/documents -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"upload_id": "up_..."}'`
```

`GET /v1/documents/{id}/pages` returns page previews behind signed URLs that live ten minutes. `DELETE` removes the bytes and the previews. The hash and the page count stay on any job that used it.

### HTML

Shell

```
`# inline CSS and data: images print; an external stylesheet, image or font is refused curl -s $FFA/documents -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"html": "<style>@page { margin: 0.5in } body { font-family: Liberation Serif }</style><h1>Purchase order 4411</h1><p>Please confirm by return fax.</p>", "filename": "po-4411"}' # answer: id (doc_...), source "html", page_count, sha256, and validation with resources_blocked when anything was refused`
```

The HTML is rendered in a headless browser with script execution off and no network. Inline CSS and `data:` images print. An external stylesheet, image or font is refused and counted, and the count comes back on the document's `validation` as the warning `resources_blocked`. Pages are US letter with a 0.4 inch margin unless the document's own `@page` rule says otherwise.

The HTML is at most 2 MiB. A document that takes longer than 45 seconds to render is refused with 422 `conversion_failed`. A renderer that is down answers 503 `render_unavailable`, and that call is retryable.

Fonts inside the renderer: Liberation, DejaVu, Noto Sans, Noto Serif and Noto CJK. Liberation is the metric twin of Arial, Times New Roman and Courier New. DejaVu, Noto Sans and Noto Serif cover Latin, Greek, Cyrillic, Hebrew, Arabic and most other scripts, and Noto CJK covers Chinese, Japanese and Korean. Name one of these in your CSS, or the font it stands in for. The MCP tool `prepare_fax` takes `html` next to `text` and `markdown`, and the CLI takes `ffa documents upload letter.html`.

### Images

Shell

```
`# 1. the same upload session as a PDF, with the content type of the image curl -s $FFA/uploads -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"filename": "scan.tiff", "size_bytes": 912377, "content_type": "image/tiff"}' # 2. send the bytes, then complete the session curl -s -X PUT "$UPLOAD_URL" --data-binary @scan.tiff curl -s -X POST $FFA/uploads/up_.../complete -H "Authorization: Bearer $FFA_KEY" # 3. make the document: one grayscale letter page per TIFF frame curl -s $FFA/documents -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" -d '{"upload_id": "up_..."}' # answer: source "image", page_count, and validation with image_rotated or image_downsampled when either applied`
```

The upload session takes `content_type` `image/png`, `image/jpeg` or `image/tiff` next to `application/pdf`, and the same `upload_id` makes the document. Every page becomes 8-bit grayscale, because fax is monochrome, on a US letter portrait page with a quarter inch margin, scaled to fit. An image wider than it is tall is rotated to fill the page, with the warning `image_rotated`. A frame larger than 2550 by 3300 pixels is scaled down to 300 dpi, with the warning `image_downsampled`.

Each frame of a multi-page TIFF is one page. PNG and JPEG use the first frame only. A frame over 50 million pixels is refused with `image_too_large`, and bytes that do not match the declared type with `unsupported_format`. The document's `source` is `image`, and the limits stay 200 pages and 25 MiB. The dashboard's upload button takes these files too, and the CLI takes `.png`, `.jpg`, `.jpeg`, `.tif` and `.tiff`.

The byte, time and pixel limits above are in [facts.json](https://faxforagents.com/facts.json) with the constant each one is read from, as of 2026-10-28. The page size, the margins and the 300 dpi ceiling are the converter's defaults in the same release, v1.1.0.

## Quotes

Shell

```
`curl -s $FFA/fax-quotes -H "Authorization: Bearer $FFA_KEY" \ -H "Content-Type: application/json" \ -d '{"document_id": "doc_...", "to": "+14155550142", "from": "+16285550199", "cover": {"to_name": "Accounts", "subject": "PO 4411"}}'`
```

The quote carries the route, the document pages, the cover page when one is asked for and the billable pages. Also the amount in micro-USD, the rate table version and `expires_at`, 10 minutes out. It also says `authorization_required`: true for a live key with no policy that allows this send. A quote outside the published routes is refused with `unsupported_destination`.

## The send

Shell

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

Three things happen in one transaction. The wallet is locked and the quoted amount moves to a hold, or the call is refused with `insufficient_balance` before any provider is touched. The authorization is checked: a test key is allowed, a live API key needs `approval_id` or a policy that allows the plan. The job is written as `queued` with an audit row, and a dispatch is queued for the worker.

Always send an `Idempotency-Key`. A retry after a timeout returns the same job. A different body under the same key is a 409.

## What are the six job states?

Job states and what they mean for money

| State | Meaning | Hold |
| --- | --- | --- |
| queued | accepted, waiting for the worker | held |
| active | the provider has the fax, attempts in progress. Busy and no answer are retried up to three times | held |
| delivered | every page confirmed, evidence on the attempt | captured |
| partial | some pages confirmed. Never retransmitted on its own | confirmed pages captured, rest released |
| failed | attempts exhausted, invalid number, render failure or cancel. `failure_code` says which | released |
| unknown | no outcome by the deadline. The reconcile tick keeps asking the provider. Never retransmitted on its own | held until reconciled |

Read the job with `GET /v1/faxes/{id}`, its attempts with `/attempts`, its ordered events with `/events`, and the receipt with `/receipt`. `POST /v1/faxes/{id}/cancel` cancels a queued job at once and is best effort once sending has started.

## What does the receipt hold?

Job id, destination, sender, pages, provider reference, document sha256, amount captured, completion time. It is also sent by mail to the organization's notification address for live jobs, and the same fields are on the `fax.delivered` event.

## From the blog

- [Send a fax from a computer: four ways that work in 2026](https://faxforagents.com/blog/send-fax-from-computer/)
