# Prepare and verify

Split a fill from documents into two calls: prepare reads the documents and proposes a value for every field without writing anything, you read the proposal (GET /proposals/{proposal_id}) and settle what is open, and commit writes it. Fetch the commit's receipt and filled PDF here, and check any filled PDF against its form with verify. See the guide at /docs/prepare-commit-verify.

## Prepare a proposal for a form you created

```
POST /forms/{form_id}/prepare
```

Authentication: Bearer API key required.

Read supporting documents and propose a value for every field, without writing the PDF. Returns 202 with a job id; the finished job carries the proposal_id. Bills one context fill.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| form_id | path | string | yes | A form you created, status ready. |
| context | body | file (multipart, repeatable) | no | Supporting document(s) to read. |
| policy | body | string | no | Optional. safe (the default) treats high- and medium-confidence values as ready; strict treats only high-confidence ones as ready. Anything else is 400. |
| Idempotency-Key | header | string | no | Optional. Retrying with the same key answers 200 with the original job instead of starting a new one. Scoped to your account, valid 24 hours, and shared by every call on the account, so use a fresh key for each request. |

```curl
curl -X POST https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/prepare \
  -H "Authorization: Bearer sk_live_yourkey" \
  -F "context=@./w9-prior-year.pdf"
```

```js
const body = new FormData();
body.append("context", docBlob, "w9-prior-year.pdf");

const res = await fetch("https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/prepare", {
  method: "POST",
  headers: { Authorization: "Bearer sk_live_yourkey" },
  body,
});
const { job_id } = await res.json(); // poll GET /forms/with-context/{job_id} for proposal_id
```

```python
import requests

res = requests.post(
    "https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/prepare",
    headers={"Authorization": "Bearer sk_live_yourkey"},
    files={"context": open("w9-prior-year.pdf", "rb")},
)
job = res.json()  # {"job_id": ..., "status": "processing"}
```

### Response

```json
{
  "job_id": "8c9d0e1f-2a3b-4c5d-9e6f-a1b2c3d4e5f6",
  "status": "processing"
}
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | A replay of an earlier request with the same Idempotency-Key: the original job. |
| 202 | Accepted; the prepare job started. |
| 400 | policy is not safe or strict. |
| 401 | Missing or invalid API key. |
| 402 | No payment method on file and the monthly free context fills are used up, or the form and its documents are over the free tier's 5-page cap. The body's detail carries code, message and billing_url. |
| 404 | No such form, or owned by another key. |
| 409 | Form not ready. |
| 410 | The form's documents have been deleted under its retention policy. |
| 413 | Too many context files (max 5), file too large (max 10 MB), or request over 30 MB. |
| 422 | document_same_as_form: a context file is this form's own blank original. Nothing is charged. |
| 429 | Rate limit exceeded. |

## Create a form and prepare a proposal

```
POST /forms/prepare
```

Authentication: Bearer API key required.

Upload a flat PDF plus supporting documents: Emboss detects the fields and prepares a proposal in one call. Returns 202 with a job id; bills one creation and one context fill.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| file | body | file (multipart) | yes | The flat PDF. |
| context | body | file (multipart, repeatable) | no | Supporting document(s) to read. |
| policy | body | string | no | Optional. safe (the default) or strict. Anything else is 400. |
| retention | body | string | no | Optional. ephemeral or account_default; default account_default. See the Ephemeral processing guide. |
| Idempotency-Key | header | string | no | Optional. Retrying with the same key answers 200 with the original job instead of starting a new one. Scoped to your account, valid 24 hours, and shared by every call on the account, so use a fresh key for each request. |

```curl
curl -X POST https://api.getemboss.ai/forms/prepare \
  -H "Authorization: Bearer sk_live_yourkey" \
  -F "file=@./w9.pdf" \
  -F "context=@./w9-prior-year.pdf"
```

```js
const body = new FormData();
body.append("file", formBlob, "w9.pdf");
body.append("context", docBlob, "w9-prior-year.pdf");

const res = await fetch("https://api.getemboss.ai/forms/prepare", {
  method: "POST",
  headers: { Authorization: "Bearer sk_live_yourkey" },
  body,
});
const { job_id } = await res.json();
```

```python
import requests

res = requests.post(
    "https://api.getemboss.ai/forms/prepare",
    headers={"Authorization": "Bearer sk_live_yourkey"},
    files={"file": open("w9.pdf", "rb"), "context": open("w9-prior-year.pdf", "rb")},
)
job = res.json()
```

### Response

```json
{
  "job_id": "8c9d0e1f-2a3b-4c5d-9e6f-a1b2c3d4e5f6",
  "status": "processing"
}
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | A replay of an earlier request with the same Idempotency-Key: the original job. |
| 202 | Accepted; detection and the prepare job started. |
| 400 | Not a readable PDF, policy is not safe or strict, or a bad retention value. |
| 401 | Missing or invalid API key. |
| 402 | No payment method on file and the monthly free allowance is used up, or the request is over the free tier's 5-page cap. The body's detail carries code, message and billing_url. |
| 413 | Too many context files (max 5), file too large (max 10 MB), too many pages (max 100), or request over 30 MB. |
| 422 | The file field is missing, or document_same_as_form: a context file is the uploaded form itself. Nothing is charged. |
| 429 | Rate limit exceeded. |

## Get the receipt

```
GET /proposals/{proposal_id}/receipt
```

Authentication: Bearer API key required.

The receipt of the proposal's newest commit: what was decided, by whom, on what evidence, the verification report, the second model's value check, and what the document still needs.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| proposal_id | path | string | yes | The proposal. |

```curl
curl https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/receipt \
  -H "Authorization: Bearer sk_live_yourkey"
```

```js
const res = await fetch("https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/receipt", {
  headers: { Authorization: "Bearer sk_live_yourkey" },
});
const receipt = await res.json();
```

```python
import requests

res = requests.get(
    "https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/receipt",
    headers={"Authorization": "Bearer sk_live_yourkey"},
)
receipt = res.json()
```

### Response

```json
{
  "receipt_version": "1",
  "emboss_version": "a1b2c3d4e5f6",
  "proposal_id": "3e714663-ff2c-45ac-ae11-3c7015facfef",
  "form_id": "6c47f7f5-f921-4698-910f-95dd7d81310b",
  "contract": { "schema_version": "1.0", "detector_version": "ffdnet-l-2026-08" },
  "policy": "safe",
  "mode": "two_call",
  "timestamps": { "proposed_at": "2026-10-06T18:31:49+00:00", "committed_at": "2026-10-06T18:40:02+00:00", "verified_at": "2026-10-06T18:40:04+00:00" },
  "fields": [
    { "field_id": 3, "label": "Name:", "value": "Acme Holdings LLC", "state": "ready", "origin": "document", "confidence": "high",
      "evidence": [{ "document_id": "d1", "document_name": "w9-prior-year.pdf", "page": 1, "method": "quote_check", "location": "Acme Holdings LLC" }], "note": null }
  ],
  "unresolved": { "fields": [], "attachments": [] },
  "verification": { "result": "complete", "checked_at": "2026-10-06T18:40:04+00:00", "issues": [], "adjustments": [],
    "counts": { "fields": 24, "written": 24, "required_unfilled": 0, "issues_blocking": 0, "issues_review": 0 } },
  "requirements": { "signatures": [], "attachments": [] },
  "files": { "filled_pdf": { "name": "filled.pdf", "bytes": 184320, "sha256": "285236fa25e49a62d43fffecac4f30a9799467fd0994db61bbbdb82fa9246925", "artifact_id": "99999999-aaaa-bbbb-cccc-dddddddddddd" } },
  "commits": [{ "n": 1, "at": "2026-10-06T18:40:02+00:00", "auto": false, "result": "complete", "values_supplied": 1 }],
  "retention": { "policy": "account_default", "documents_deleted_after": null },
  "checks": { "summary": { "ran": true, "checked": 24, "doubted": 0, "cost": 0.0021, "ms": 480 }, "fields": [] }
}
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | The receipt. |
| 401 | Missing or invalid API key. |
| 403 | A wrong proposal token. |
| 404 | No such proposal, or not yours, or no commit yet: "no receipt yet for this proposal". |

## Download the proposal's filled PDF

```
GET /proposals/{proposal_id}/pdf
```

Authentication: Bearer API key required.

The PDF the proposal's newest commit rendered. The response body is the raw %PDF bytes.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| proposal_id | path | string | yes | The proposal. |

```curl
curl https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/pdf \
  -H "Authorization: Bearer sk_live_yourkey" -o filled.pdf
```

```js
const res = await fetch("https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/pdf", {
  headers: { Authorization: "Bearer sk_live_yourkey" },
});
const pdf = await res.arrayBuffer();
```

```python
import requests

res = requests.get(
    "https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/pdf",
    headers={"Authorization": "Bearer sk_live_yourkey"},
)
open("filled.pdf", "wb").write(res.content)
```

### Response

```json
%PDF-1.7
…binary…
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | The PDF bytes (application/pdf). |
| 401 | Missing or invalid API key. |
| 403 | A wrong proposal token. |
| 404 | No such proposal, or not yours, or no commit yet: "no filled PDF yet for this proposal". |
| 410 | The form's documents have been deleted under its retention policy. |

## Verify a filled PDF

```
POST /forms/{form_id}/verify
```

Authentication: Bearer API key required.

Check a filled PDF you hold, however it was filled, against this form's contract. Returns the same report a commit gives: result, checked_at, issues, adjustments and counts. Bills one verify.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| form_id | path | string | yes | The form the PDF is a copy of. |
| file | body | file (multipart) | yes | The filled PDF. |

```curl
curl -X POST https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/verify \
  -H "Authorization: Bearer sk_live_yourkey" \
  -F "file=@./filled-by-hand.pdf"
```

```js
const body = new FormData();
body.append("file", pdfBlob, "filled-by-hand.pdf");

const res = await fetch("https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/verify", {
  method: "POST",
  headers: { Authorization: "Bearer sk_live_yourkey" },
  body,
});
const report = await res.json();
```

```python
import requests

res = requests.post(
    "https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/verify",
    headers={"Authorization": "Bearer sk_live_yourkey"},
    files={"file": open("filled-by-hand.pdf", "rb")},
)
report = res.json()
```

### Response

```json
{
  "result": "review_required",
  "checked_at": "2026-10-06T18:40:04+00:00",
  "issues": [
    { "field_id": 9, "page": 0, "kind": "signature_missing", "detail": "Signature", "blocking": false }
  ],
  "adjustments": [],
  "counts": { "fields": 24, "written": 23, "required_unfilled": 0, "issues_blocking": 0, "issues_review": 1 }
}
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | The verification report. |
| 400 | Not a PDF ("file must be a PDF"), unreadable, or its widgets do not match this form's contract. |
| 401 | Missing or invalid API key. |
| 402 | No payment method on file and the monthly free verifications are used up, or the form is over the free tier's 5-page cap. The body's detail carries code, message and billing_url. |
| 404 | No such form, or owned by another key. |
| 409 | Form not ready. |
| 413 | File too large (max 10 MB). |
| 429 | Rate limit exceeded. |

## See also

- [Prepare, commit and verify](https://getemboss.ai/docs/prepare-commit-verify)
- [Pre-fill forms from context documents](https://getemboss.ai/use-cases/prefill-from-context)
