Mises à jour de produit
New in Firma.dev: Sign the Whole Deal Packet in One Request, Each File Sealed on Its Own

A B2B deal usually closes on a stack of files: a master services agreement, a statement of work, an NDA and an order form, all going to the same customer for the same signature. As of API v1.39.0 that whole packet can go out as one Firma.dev signing request, and every file in it comes back as its own sealed document.
Multi-document signing is live for every Firma.dev account
This is new, and it's already switched on for everyone. The release adds:
A template or signing request can hold up to 20 documents, with a limit of 100 MB per document set.
POST /templates,POST /signing-requestsandPOST /signing-requests/create-and-sendacceptdocument_ids, a list of uploads in document order.New
/documentsroutes add, replace, rename, reorder, remove and download documents one at a time.Anchor tags can target a single document with
document_index.Each document of a completed signing request downloads as its own individually sealed file, with its own SHA-256 hash.
The changelog dates the release October 12, 2026, and multi-document features have been available to every company since October 13, 2026. There's nothing to enable and no flag to request.
Pricing doesn't move either. One signing request is one credit, €0.049 (~5¢ USD), whether it carries one document or twenty. The docs FAQ says so directly: one credit equals one signing request, regardless of how many signers or documents it contains. It's still pay-as-you-go with no monthly minimums and no per-seat fees.
What a four-document packet used to take
Until this release, a signing request held one document. Sending a packet meant writing plumbing around that limit, and it usually looked like this:
Step | What your code had to do |
|---|---|
1. Merge | Combine the MSA, SOW, NDA and order form into one PDF |
2. Offset | Add the page count of every earlier file to each field's page number |
3. Send | Create the signing request from the merged PDF |
4. Split | Cut the single sealed PDF apart by page range |
5. Map | Attach each slice to the right record in your app |
Steps 2 and 4 are where it breaks. A customer swaps in a two-page NDA where a three-page one used to be, and every field after it lands on the wrong page. The split ranges drift the same way... and for a SaaS platform whose own cutsomers upload these sets, each tenant's packet is slightly different, so the bug turns up one tenant at a time.
With v1.39.0 the merge, offset and split steps go away. Each file goes in as its own upload, moves through one signing flow and comes back sealed on its own.
Sending the packet with document_ids
Upload each file with POST /documents
Every document in the set starts as an upload. POST /documents takes a file name, size and content type and returns a temporary document_id plus a presigned upload_url. You PUT the raw file bytes to that URL. It's one upload per file... so a four-document packet means four uploads before the signing request even exists.
This path is also the one to use for large files. Since v1.40.0, any request body over 6 MiB returns 413 REQUEST_TOO_LARGE, so big PDFs belong on POST /documents regardless. A single upload can be up to 50 MB (the whole set tops out at 100 MB).
Create and send in document order
Pass 1 to 20 upload ids in document_ids, in the order the documents should appear. POST /signing-requests/create-and-send takes them. So do POST /signing-requests, for a draft you send later, and POST /templates.
Each create takes exactly one document source: document, document_id, document_ids or template_id. Send more than one and the API returns 400 CONFLICTING_DOCUMENT_SOURCE. A repeated id returns 400 DUPLICATE_DOCUMENT_ID, more than 20 returns 400 MAX_DOCUMENTS_EXCEEDED, and a set over 100 MB returns 413 DOCUMENT_SET_TOO_LARGE.
Point anchor tags at the right document
Anchor tags now accept document_index, the 1-based position of the target document in document_ids. With one document it's optional and defaults to 1. With more than one it's required, and leaving it out returns 400 ANCHOR_DOCUMENT_REQUIRED. With it, the {{SIGN_HERE}} in the NDA stays separate from the {{SIGN_HERE}} in the SOW, even when every file in the packet uses the same marker.
If anchor tags are new to you, the anchor tags walkthrough covers offsets, occurrences and sizing.
Every document comes back sealed on its own
GET /signing-requests/{id} returns a documents array. Each entry has document_id, position, label, original_filename and page_count, and on signing requests it also carries document_only_hash, the SHA-256 of the individually sealed document once the certificate is bound (null before that).
To fetch a file, call GET /signing-requests/{id}/documents/{document_id}/download. It returns a signed URL valid for 1 hour:
type=original(the default) returns the document as uploaded.type=documentreturns the individually sealed document once the certificate is bound. Before then it returns404 DOCUMENT_SIGNED_NOT_AVAILABLE, so treat that code as "not ready yet" and come back a little later.
For a SaaS app this removes the last bit of glue code. The loop below writes the signed MSA to the contract record, the signed NDA to the NDA record and so on, with each file's hash stored next to it. And because position follows the order you sent in document_ids, it maps straight back to the packet you built.
If you also want the request-level downloads (the document-only PDF and the certificate on its own), those are covered in the split PDF downloads post. Every signing request also keeps its audit trail.
Templates that hold a document set
A template can carry the whole packet too, which fits platforms that send the same set to every new customer. Create it with document_ids on POST /templates, then maintain each file under /templates/{id}/documents:
POST /templates/{id}/documentsadds a document atposition, or last when you leave position out.PATCH /templates/{id}/documents/{document_id}replaces the document with a new upload (document_id) or renames it (label). Send exactly one of the two per call.PUT /templates/{id}/documents/orderreorders the set. The list must name every document exactly once, or it returns400 INVALID_DOCUMENT_ORDER.DELETE /templates/{id}/documents/{document_id}removes one. The last remaining document can't be removed.
A replacmeent keeps the fields in place as long as the new file has the same page count and every page is the same size within 1 pt. Otherwise you get 400 PAGE_COUNT_MISMATCH or 400 DIMENSION_MISMATCH. So when legal updates the NDA, you swap that one file... and the fields on the MSA, SOW and order form stay where they were. The replace template document feature page covers replacement in more depth.
There's also a concurrency guard, and it matters most in multi-tenant setups (two services, or two admins, editing the same template). documents_layout_version increases whenever documents are added, removed or reordered. Field writes through PUT and PATCH on /templates/{id} and /signing-requests/{id} send the value from your last read. It's required once there's more than one document or the layout version is above 1 (409 DOCUMENTS_LAYOUT_VERSION_REQUIRED if missing), and a stale value returns 409 DOCUMENT_SET_VERSION_CONFLICT. If a tenant admin reorders the packet in your UI while a background job is moving a field, the second write fails with a 409 and nobody's change gets silently overwirtten.
Signing requests use the same /signing-requests/{id}/documents routes while they're still drafts. Once a request is sent, finished, cancelled or declined, every document write returns 409 DOCUMENT_SET_LOCKED.
What changes in an existing integration
The release is additive. Requests that never use document_ids, document_index or the /documents routes behave exactly as before. Three things are worth checking:
Generated clients. If you build a client from the OpenAPI spec,
documentis now optional onPOST /templates, and the seal participant schema in signing-request responses is now namedSealParticipantApi(it wasSealParticipant).Strict error parsing. If your code rejects unknown error codes, add the new ones. The
MultiDocumentErrorCodeschema in the API reference lists them all.Template copies.
POST /templates/{id}/copyturns a multi-document template into a template with one merged document. Field content and absolute page numbers are kept. Per-document labels and positions are dropped, which is worth planning for if you copy templates into customer workspaces.
On the legal side nothing changes. Firma.dev is compliant with the ESIGN Act, UETA, eIDAS (SES and AES) and UK eIDAS, and signatures are legally binding in 55+ countries, for every document in the set.
Summary
Multi-document signing is live in the Firma.dev API for every account. Upload each file with POST /documents, pass up to 20 ids in document_ids, aim anchor tags with document_index, and download each document individually sealed with its own document_only_hash. Templates can hold the same sets and update one file at a time, with documents_layout_version guarding concurrent edits. The whole packet costs one credit, €0.049 (~5¢ USD). Full details are in the API changelog.
Get started with Firma.dev for free, no credit card required.
Articles connexes
Notre plateforme est conçue pour permettre aux entreprises de toutes tailles de travailler plus intelligemment et d'atteindre leurs objectifs avec confiance.



