# Quickstart

Emboss turns a flat PDF into a filled one. You upload a form, Emboss detects the
fields, fills them, and gives you back a filled PDF, in a few HTTP calls. After
detection, Emboss also produces a structured **contract** of the fields it found,
so you can [inspect exactly what it detected](/docs/the-contract-shape) before you
rely on the output. It's not a black box.

## Before you start

- Create an API key in the [dashboard](/dashboard) (see [Authentication](/docs/authentication)).
- Have `curl` and `jq` installed: the polling loop below uses `jq`.
- Save a form to fill as `./w9.pdf`, and a supporting document as `./vendor-profile.pdf`.
- Not sure what a document will cost? [Ask for a quote first](/docs/fill-from-data#know-the-price-first); it is free and counts for nothing.
- Want to know how your documents are handled and when they're deleted? See [the security page](/security).

The free tier: The first 5 form creations, 5 context fills, 5 standard fills, 5 verifications, 5 reads, and 5 packages each month are free when the job is 5 pages or fewer (a context fill counts the form and its documents together); a longer job is charged and does not use a free one. See [pricing](/pricing).

For a fill from documents, the free tier's page limit counts the form's pages plus its documents' pages, so a short form with a long supporting document can be over it.

## Authentication

Every request is authenticated with a Bearer key. Keep your key out of client-side
code and source control. Set it as an environment variable:

```bash
export EMBOSS_API_KEY="sk_..."
```

Then pass it on every call: `-H "Authorization: Bearer $EMBOSS_API_KEY"`.

## Pick your endpoint

| Your goal | Endpoint |
|---|---|
| Detect fields only (no fill) | `POST /forms` |
| Fill a brand-new flat PDF from context | `POST /forms/with-context`, *this quickstart* |
| Fill a form you've already created from context | `POST /forms/{form_id}/with-context` |
| Fill from your own structured data | [Fill from your data](/docs/fill-from-data) |

This quickstart walks the second path: upload a flat PDF plus supporting documents
and get a filled PDF back.

Uploading a form Emboss already knows? See [the library of forms Emboss
already knows](/docs/library): creating one of those skips detection and is
ready in seconds, at the same price.

## 1. Upload the form

Send the form PDF to `POST /forms/with-context`. Add one or more `context` files:
supporting documents Emboss reads to infer field values. This starts an
asynchronous detection-and-fill job and returns `202 Accepted` with a `job_id`.

```bash
curl -X POST https://api.getemboss.ai/forms/with-context \
  -H "Authorization: Bearer $EMBOSS_API_KEY" \
  -F "file=@./w9.pdf" \
  -F "context=@./vendor-profile.pdf"
```

Expected: `202 Accepted`.

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

**Limits:** the form `file` must be a PDF, ≤ 10 MB and ≤ 100 pages. You may attach
up to 5 `context` files (PDF, DOCX, XLSX, image, or text), with ≤ 30 MB total
upload size. Past these you'll get a `413`.

(Just want detection, no fill? `POST /forms` with only the `file`. See the
[API reference](/docs/reference/create-form).)

## 2. Poll the job

The job runs asynchronously. Poll `GET /forms/with-context/{job_id}` until
`status` is `ready` or `failed`. Each poll is `200 OK`:

- `processing`: keep polling
- `ready`: done; the response includes a `session_id` and the `form_id` it filled. If Emboss found no fields to fill, `session_id` is `null`, `report.warnings` holds `no_fields_detected`, and `report.message` says what to do: send the blank form as the form, and the filled-in documents as `context`. The fill isn't charged, but creating the form is.
- `failed`: the response includes an `error` describing why

Poll every couple of seconds. Status checks never count toward the rate limit (a `429` comes only from calls that start work; see [the rate limit](/docs/authentication#rate-limit)). A job a restart cut off ends `failed` with code `interrupted`, so a loop never waits forever.

```bash
JOB_ID="6c47f7f5-f921-4698-910f-95dd7d81310b"
while true; do
  RESPONSE=$(curl -s "https://api.getemboss.ai/forms/with-context/$JOB_ID" \
    -H "Authorization: Bearer $EMBOSS_API_KEY")
  STATUS=$(echo "$RESPONSE" | jq -r .status)
  if [ "$STATUS" = "ready" ]; then
    SESSION_ID=$(echo "$RESPONSE" | jq -r .session_id)
    if [ "$SESSION_ID" = "null" ]; then
      echo "$RESPONSE" | jq .report   # no fields detected: nothing to download
      exit 1
    fi
    echo "ready: $SESSION_ID"
    break
  fi
  if [ "$STATUS" = "failed" ]; then
    echo "$RESPONSE" | jq .
    exit 1
  fi
  sleep 2
done
```

A ready job looks like:

```json
{ "job_id": "6c47f7f5-f921-4698-910f-95dd7d81310b", "status": "ready", "form_id": "0b5a9e7c-3d2f-4e1a-9c8b-7d6e5f4a3b2c", "session_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }
```

A failed job returns a structured error with a stable `code` and a human-readable `message`:

```json
{ "job_id": "6c47f7f5-f921-4698-910f-95dd7d81310b", "status": "failed", "error": { "code": "context_too_large", "message": "context too large: 512000 > 500000 tokens" } }
```

Branch on `error.code` for programmatic handling. The codes are `context_too_large`, `unsupported_context_file`, `context_fill_failed`, `apply_rejected`, `form_processing_failed`, `commit_failed` (writing or checking the filled PDF broke), `interrupted` (a restart cut the job off; submit it again), `reading_failed` (pay-per-call only: reading your documents failed on our side, so nothing was filled), and `job_failed` (fallback); `error.message` carries the detail.

## 3. Download the filled PDF

Fetch the rendered document from `GET /sessions/{sid}/pdf`. The response body is
raw PDF bytes, so write it straight to a file with `-o`:

```bash
curl https://api.getemboss.ai/sessions/$SESSION_ID/pdf \
  -H "Authorization: Bearer $EMBOSS_API_KEY" \
  -o filled.pdf
```

Verify you got a real PDF:

```bash
ls -lh filled.pdf
file filled.pdf   # filled.pdf: PDF document, ...
```

That's the full path: **upload → poll → download**.

## Next steps

- Inspect [the detected-fields contract](/docs/the-contract-shape).
- Read more about [filling from context documents](/docs/fill-from-context).
- Prefer JavaScript or Python? Every endpoint in the
  [API reference](/docs/reference/create-form) includes cURL, JavaScript, and
  Python samples.

**Safe retries:** pass an `Idempotency-Key` header, any unique string, like a
UUID, on a create request. If a call times out and you retry with the same key,
Emboss returns the original job instead of starting a new one, so you're never
charged twice for the same submission. Keys are scoped to your account and last
24 hours. A replay answers `200` with the original job. Every call on your
account shares one set of keys, so use a fresh key for each request.

## See also

- [Fill PDFs from your own data](https://getemboss.ai/use-cases/fill-pdfs-from-your-data)
- [Fillable PDF](https://getemboss.ai/glossary/fillable-pdf)
- [Authentication](https://getemboss.ai/docs/authentication)
- [Use Emboss in Claude](https://getemboss.ai/docs/claude)
- [Worked examples](https://getemboss.ai/docs/examples)
- [Forms](https://getemboss.ai/docs/reference/create-form)
- [Fill with context](https://getemboss.ai/docs/reference/fill-with-context)
- [The form library](https://getemboss.ai/docs/reference/library)
- [Security](https://getemboss.ai/security)
- [Available forms](https://getemboss.ai/docs/library)
