Fill from your data

When you already have the values, from your database, a form your user filled, wherever, fill the PDF directly. No inference: you map each value onto a field by its id, and Emboss writes exactly that. (If instead you want Emboss to read the values out of documents, see Fill from context docs.)

Know the price first

Every route has a fixed price, and the first few calls each month are free. To see what a specific document will cost, and whether your free tier still covers it, send the same PDF (and any context) to POST /forms/quote. It is free and it stores nothing. It does count as one call toward your hourly rate limit.

The price is exact for PDF documents. For an image, a Word file, a spreadsheet or text it is an estimate from the file's size: the job bills the size Emboss measures when it reads the document, which can be higher, for example for a photo of a dense page.

curl -X POST https://api.getemboss.ai/forms/quote \
  -H "Authorization: Bearer sk_..." \
  -F "file=@application.pdf" \
  -F "context=@customer.json"

The answer prices each route you could call next and says, per kind of work, whether the free tier covers it. The sample shows the shape; the amounts follow the live price list:

{
  "pages": 3,
  "context_pages": 1,
  "already_fillable": false,
  "effective": "2026-09-07",
  "billing": { "has_card": false, "exempt": false, "billing_url": "https://getemboss.ai/dashboard/billing" },
  "lines": {
    "create_form": { "cents": 5, "free": true,
      "kinds": { "create": { "covered": true, "reason": "within_allowance", "remaining": 4 } } },
    "create_and_fill_from_context": { "cents": 8, "free": false,
      "kinds": { "create": { "covered": true, "reason": "within_allowance", "remaining": 4 },
                 "with_context": { "covered": false, "reason": "allowance_used", "remaining": 0 } } },
    "fill_from_data": { "cents": 2, "free": true,
      "kinds": { "standard_fill": { "covered": true, "reason": "within_allowance", "remaining": 4 } } },
    "verify": { "cents": 1, "free": true,
      "kinds": { "verify": { "covered": true, "reason": "within_allowance", "remaining": 4 } } }
  }
}

reason is one of "within_allowance" (free, with how many free calls remain after this one), "allowance_used" (this month's free calls are spent), "over_page_cap" (the document is longer than the free tier allows), or "exempt". A line is free only when every kind it charges is covered. When has_card is true a line that is not free is charged to the card on file; otherwise the real call answers 402 with the same reason.

The free-tier verdict is judged on the form's page count for create, standard_fill, and verify, but on the form's pages plus the context's page-equivalents for with_context, since that is the combined size the with-context route itself bills against. The fourth line, verify, prices checking a filled PDF with POST /forms/{form_id}/verify.

The call itself can fail before it ever gets to price anything: 400 means the upload wasn't a PDF, 413 means it was too many pages, too much context, too many context files, or too large, and 422 means the file field was missing from the request.

1. Create the form and wait for detection

Upload the flat PDF, then poll until it's ready (see Tracking jobs):

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

2. Read the contract to find field ids

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

Each field has an integer id: that's what you'll target. See The contract shape.

3. Open a session

curl -X POST https://api.getemboss.ai/sessions \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "form_id": "6c47f7f5-f921-4698-910f-95dd7d81310b" }'

201 Created:

{
  "session_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "next_prompt": null,
  "missing": [1, 3],
  "pending_confirmation": [],
  "form_state": {
    "fields": {
      "1": { "candidates": [], "confidence": null, "evidence": [], "history": [], "invalid_reason": null,
             "note": null, "origin": null, "reoffered": false, "source": null, "status": "unfilled", "value": null }
    },
    "turn_count": 0
  }
}

missing lists the required fields still empty; form_state.fields has one entry per field id (one is shown). The session's own status (active, then completed once filled) is on GET /sessions/{sid}.

4. Write your values

PUT /sessions/{sid}/fields with a list of field_id → value updates:

curl -X PUT https://api.getemboss.ai/sessions/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d/fields \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "updates": [
        { "field_id": 1, "value": "Analytical Engines LLC" },
        { "field_id": 3, "value": "12-3456789" }
      ] }'
{ "session_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "applied": [1, 3] }

An unknown field_id or a value the field won't accept returns 422 with the refused ids in invalid_field_ids, and nothing in that request is applied.

Send every value as a string, a checkbox's too ("yes", not true).

Field typeAccepted values
text, date, comb, signatureAny text.
checkboxA yes or no word. yes, y, true, on, checked, x or 1 ticks the box; no, n, false, off, unchecked, 0 or an empty value leaves it empty.
radioExactly one of the field's options[].value from the contract (sometimes a generated opt_0, opt_1).

Saying yes to more than one box of one Yes/No question returns 422 with those ids in conflicting_field_ids.

5. Fill and download

curl -X POST https://api.getemboss.ai/sessions/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d/fill \
  -H "Authorization: Bearer sk_..."

curl https://api.getemboss.ai/sessions/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d/pdf \
  -H "Authorization: Bearer sk_..." \
  -o filled.pdf

That's the whole deterministic path: no guessing, just the values you provided.

Retention

POST /forms takes an optional retention body field: ephemeral or account_default. Leave it out and the account's own default applies. See Ephemeral processing for the window and what a 410 means once a form is swept.