# Forms

Create a form by uploading a flat PDF. Emboss detects its fields asynchronously; check the form's status before fetching its contract or filling it.

## Create a form

```
POST /forms
```

Authentication: Bearer API key required.

Upload a flat PDF. Returns 202 with an id while field detection runs in the background.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| file | body | file (multipart) | no | The flat PDF. Send this or library: one of the two is required. |
| 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. A request that is rejected (any 4xx) does not consume the key: fix the cause and retry with the same key. |
| callback_url | body | string (url) | no | Optional. A public https URL Emboss POSTs the result to when the job finishes (ready or failed), signed with X-Emboss-Signature using your account's callback signing secret (shown on the API Keys page of the dashboard). See the Callbacks guide. Private or non-https URLs are rejected with 400. |
| retention | body | string | no | Optional. ephemeral or account_default; default account_default. See the Ephemeral processing guide. |
| library | body | string | no | Optional. Create from a form already in the library instead of uploading a file: the slug of one of the entries at GET /library. Mutually exclusive with file; sending both is 400. |

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

```js
const body = new FormData();
body.append("file", fileBlob, "w9.pdf");

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

```python
import requests

res = requests.post(
    "https://api.getemboss.ai/forms",
    headers={"Authorization": "Bearer sk_..."},
    files={"file": open("w9.pdf", "rb")},
)
data = res.json()  # {"form_id": ..., "status": "processing"}
```

### Response

```json
{
  "form_id": "6c47f7f5-f921-4698-910f-95dd7d81310b",
  "status": "processing"
}
```

### Status codes

| Code | Meaning |
| --- | --- |
| 202 | Accepted; detection started. |
| 400 | Not a readable PDF; neither file nor library was sent: "field required: file"; both were sent: "send a file or a library slug, not both"; "invalid callback_url"; or a retention value other than ephemeral or account_default. |
| 401 | Missing or invalid API key. |
| 402 | No payment method on file and either 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 (over_free_tier or over_page_cap), message and billing_url, the page where a payment method can be added. |
| 404 | The library slug does not match any library form: "no library form named <slug>". |
| 413 | Upload too large (max 10 MB) or too many pages (max 100). |
| 422 | pdf_unsafe: the PDF's form fields are built in a way that could compromise the service, so it is refused before any price, charge or stored file. The body's detail carries code and message ("Emboss does not process PDF files that could compromise the service."). |
| 429 | Rate limit exceeded. |
| 503 | The library form's stored copy could not be read: "library copy unavailable"; retry. |

## Get a form

```
GET /forms/{id}
```

Authentication: Bearer API key required.

Check a form's detection status. Poll until status is processing, ready, or failed. reused_layout is true when this form's field map came from a blank-form layout Emboss already knew, rather than a fresh detection run.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| id | path | string | yes | The form id returned by POST /forms. |

```curl
curl https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b \
  -H "Authorization: Bearer sk_..."
```

```js
const res = await fetch("https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b", {
  headers: { Authorization: "Bearer sk_..." },
});
const form = await res.json();
```

```python
import requests

res = requests.get(
    "https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b",
    headers={"Authorization": "Bearer sk_..."},
)
form = res.json()
```

### Response

```json
{
  "id": "6c47f7f5-f921-4698-910f-95dd7d81310b",
  "status": "ready",
  "title": "Form W-9",
  "schema_version": "1.0",
  "reused_layout": false,
  "library": { "slug": "irs-w-9", "revision": "Rev. March 2024" },
  "error": null
}
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | The form status (processing \| ready \| failed). |
| 401 | Missing or invalid API key. |
| 404 | No such form, or owned by another key. |

## List your forms

```
GET /forms
```

Authentication: Bearer API key required.

List the forms this key owns, newest first. By default only forms whose detection finished (states=ready); pass states=all to include ones still processing or failed.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| states | query | string | no | ready (default) or all. With all, the listing pages, has_thumbnail and no_fields are populated, and total is the full count. |
| limit | query | integer | no | Page size, default 100, clamped to 1..200. Honoured with states=all; the default states=ready listing always returns up to 100 rows regardless of what you pass. |
| offset | query | integer | no | Rows to skip, default 0. Honoured with states=all; with states=ready the listing is a fixed 100-row page and total is the number of rows returned, not a full count. |

```curl
curl "https://api.getemboss.ai/forms?states=all&limit=20" \
  -H "Authorization: Bearer sk_..."
```

```js
const res = await fetch("https://api.getemboss.ai/forms?states=all&limit=20", {
  headers: { Authorization: "Bearer sk_..." },
});
const { forms, total } = await res.json();
```

```python
import requests

res = requests.get(
    "https://api.getemboss.ai/forms",
    params={"states": "all", "limit": 20},
    headers={"Authorization": "Bearer sk_..."},
)
forms = res.json()["forms"]
```

### Response

```json
{
  "forms": [
    {
      "id": "6c47f7f5-f921-4698-910f-95dd7d81310b",
      "title": "Form W-9",
      "status": "ready",
      "created_at": "2026-09-08T12:52:10Z",
      "has_thumbnail": true,
      "no_fields": false,
      "retention": {
        "policy": "account_default",
        "documents_deleted_after": null,
        "documents_deleted_at": null
      }
    }
  ],
  "total": 1,
  "limit": 100,
  "offset": 0
}
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | The page of forms, with total, limit and offset. |
| 401 | Missing or invalid API key. |

## Delete a form

```
DELETE /forms/{id}
```

Authentication: Bearer API key required.

Delete a form you own. Its stored documents are removed first, under every retention policy, then the form itself. A form with a fill, a batch or a fill-from-documents job still running is not deleted.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| id | path | string | yes | The form id returned by POST /forms. |

```curl
curl -X DELETE https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b \
  -H "Authorization: Bearer sk_..."
```

```js
const res = await fetch("https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b", {
  method: "DELETE",
  headers: { Authorization: "Bearer sk_..." },
});
const { ok } = await res.json();
```

```python
import requests

res = requests.delete(
    "https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b",
    headers={"Authorization": "Bearer sk_..."},
)
assert res.json()["ok"]
```

### Response

```json
{ "ok": true }
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | Deleted. |
| 401 | Missing or invalid API key. |
| 404 | No such form, or owned by another key. |
| 409 | A fill, a batch or a fill-from-documents job is still running on this form; try again when it finishes. |

## Download the fillable PDF

```
GET /forms/{form_id}/fillable
```

Authentication: Bearer API key required.

The blank fillable PDF: the detected fields turned into real form widgets, with no values entered. Ready forms only.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| form_id | path | string | yes | The form. |

```curl
curl https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/fillable \
  -H "Authorization: Bearer sk_..." -o fillable.pdf
```

```js
const res = await fetch("https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/fillable", {
  headers: { Authorization: "Bearer sk_..." },
});
const pdf = await res.arrayBuffer();
```

```python
import requests

res = requests.get(
    "https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/fillable",
    headers={"Authorization": "Bearer sk_..."},
)
open("fillable.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. |
| 404 | No such form, not yours, or not ready yet: "fillable PDF not available". |
| 410 | The form's documents have been deleted under its retention policy. |

## Download the thumbnail

```
GET /forms/{form_id}/thumbnail
```

Authentication: Bearer API key required.

A small PNG of the form's first page, for a picker or a library view.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| form_id | path | string | yes | The form. |

```curl
curl https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/thumbnail \
  -H "Authorization: Bearer sk_..." -o thumbnail.png
```

```js
const res = await fetch("https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/thumbnail", {
  headers: { Authorization: "Bearer sk_..." },
});
const png = await res.arrayBuffer();
```

```python
import requests

res = requests.get(
    "https://api.getemboss.ai/forms/6c47f7f5-f921-4698-910f-95dd7d81310b/thumbnail",
    headers={"Authorization": "Bearer sk_..."},
)
open("thumbnail.png", "wb").write(res.content)
```

### Response

```json
PNG image bytes (image/png)
```

### Status codes

| Code | Meaning |
| --- | --- |
| 200 | The PNG bytes (image/png). |
| 401 | Missing or invalid API key. |
| 404 | No such form, not yours, or no thumbnail was made: "thumbnail not available". |
| 410 | The form's documents have been deleted under its retention policy. |

## See also

- [Quickstart](https://getemboss.ai/docs/quickstart)
- [Turn a flat PDF into a fillable form](https://getemboss.ai/use-cases/flat-pdf-to-fillable)
