The submission package

Once a form's answers are committed, hand the whole submission over in one file: the filled form, every attachment the form asked for in the order it asked for them, and a receipt embedded inside the PDF itself.

Attach a document before you commit

POST /proposals/{proposal_id}/attachments takes a multipart file and an optional for, naming which of the form's attachments_required entries this file satisfies.

curl -X POST https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/attachments \
  -H "Authorization: Bearer sk_..." \
  -F "file=@./passport.pdf" \
  -F "for=Proof of identity"
{ "n": 1, "for": "Proof of identity", "filename": "passport.pdf", "bytes": 182334, "sha256": "e3b0c...b855" }

Rules, all checked at upload so the commit itself never fails because of an attachment:

  • for, when given, must equal the name of one of the contract's attachments_required entries. Anything else: 400 "unknown requirement: <for>".
  • Only PDF, PNG or JPEG files are accepted, sniffed from the bytes, at the usual upload size limit. Anything else: 400 "attachment must be a PDF, PNG or JPEG".
  • Up to 20 attachments per proposal, and 20 MB across all of them together. Either limit: 400 "attachment limit reached".
  • Uploading the same for again replaces the earlier file.
  • Attachments can only be added while the proposal is proposed. Once it is committed or has expired: 409.

DELETE /proposals/{proposal_id}/attachments/{n} removes one by its number.

curl -X DELETE https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/attachments/1 \
  -H "Authorization: Bearer sk_..."

GET /proposals/{proposal_id} lists what has been attached so far and which requirements are already satisfied:

{
  "attachments": [{ "n": 1, "for": "Proof of identity", "filename": "passport.pdf", "bytes": 182334, "sha256": "e3b0c...b855" }],
  "requirements": { "attachments": [{ "name": "Proof of identity", "source": "page 2", "satisfied": true }] }
}

Build the package at commit

POST /proposals/{proposal_id}/commit takes an optional "package": true in its JSON body (default false). When true, after the filled PDF renders, Emboss assembles one package PDF:

  1. The filled form, all pages.
  2. The attachments whose for matches an attachments_required entry, in that requirement list's own order.
  3. Any unlabelled attachments, in upload order.
  4. Each image attachment becomes one page, scaled to fit the form's first page size and centred.
  5. The receipt is embedded inside the package as a file attachment named receipt.json, so the package carries its own record without a second download.

A commit with package: true and no attachments still produces a package: the form plus the embedded receipt, with every requirement listed as outstanding.

curl -X POST https://api.getemboss.ai/proposals/3e714663-ff2c-45ac-ae11-3c7015facfef/commit \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{"package": true}'

Once the commit job finishes, its status carries package_url. Unlike the pdf_url and receipt_url API addresses, which need your key, it is a download link on the website (https://getemboss.ai/d/<token>) that needs no key and expires 24 hours after it is made. After that, fetch the package by its artifact_id (its entry in the job's artifacts has role package) at GET /artifacts/{artifact_id}/content.

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

Once the job is ready, package_url sits alongside receipt_url and pdf_url on the poll response.

The receipt's outstanding list

The receipt's requirements.attachments becomes one entry per requirement:

{ "name": "Proof of identity", "source": "page 2",
  "satisfied_by": { "n": 1, "bytes": 182334, "sha256": "e3b0c...b855" } }

satisfied_by is null when nothing was attached for that requirement. The receipt's unresolved block is {"fields": [...], "attachments": [...]}: the fields with their candidates, and the attachments still outstanding, alongside the missing signatures, so an agent reads one list of everything still standing between the person and a filing, values and attachments together.

Retention

Attachments and packages are documents, and they follow the same retention rule as everything else Emboss stores: Ephemeral processing: source and output documents are deleted 60 to 70 minutes after the last activity on a form. Once a form is swept, its package and attachment routes answer 410 Gone with that same sentence as the problem detail, exactly like the filled PDF and the fillable copy. DELETE /forms/{id} removes them along with everything else on the form. See Security for the full list of what is kept and what is deleted.

Not on the one-call path

A one-call context fill has no proposal to attach a document to, so package is not offered on that path: there is nowhere to call the attachments route before the fill runs. Use prepare and commit when a submission needs attachments or a package.

On the pay-per-call API

On the pay-per-call API the commit takes no payment, so a package has to be bought at prepare: declare package=true on the prepare call and the quote prices it in. A commit there asking for a package the quote did not carry is refused with 400 "package was not quoted; prepare again with package=true".

Pricing

A package is billed once per commit that produces one, as one package at 2¢, which is flat, any length. The first 5 each month are free, and the free page cap is read off the form's own pages, not the package's. A second commit on the same proposal with package: true produces, and bills, a second package. See Pricing for the full schedule.

Also available as a tool

add_attachment on MCP and the attach skill on A2A take the same proposal_id, a file, and an optional for; commit_proposal and the commit skill both take the same package argument. See Read a filled form back for the matching read-side call, and pricing for the free tier.

Once a submission is filed, batch fill is the pattern to reach for when the same form has to be filled, and packaged, many times over.