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 type | Accepted values |
|---|---|
text, date, comb, signature | Any text. |
checkbox | A 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. |
radio | Exactly 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.