PDF Seals
Applies one or more seals to a PDF and records its certification (number, date and sealed document). It is the same function used by the rent-agreement document certification: same engine, same result, exposed as an API call.
There are two ways to call it:
POST /api/partner/certifications— the call that covers both cases. The bundle is an optional field: withuser_bundle_idthe certification stays recorded on that practice (and if the certification step exists the document is filed among its attachments too); without it, you just send us the document, which is what you need when whatever you are certifying has no DokiCasa practice behind it.POST /api/partner/user-bundle/{id}/seal— the original route, bundle only. It stays where it is: if you have already integrated it, nothing changes for you.
| Base URL | https://api.dokicasa.it |
| Auth | Authorization: Bearer <api_token> |
| Content-Type | multipart/form-data (with upload) or application/json (with attachments) |
The call is synchronous: by the time it responds, the sealed PDF is ready and recorded.
WARNING
Not available on the sandbox: sealing goes through the qualified signature provider, so every call produces a real document and is billed.
Prerequisites
- Active pricing for the
SEAL_PDFtype on your account. Without it the call returns403 PARTNER_PRICING_REQUIRED: contact us to enable it. - A bundle (
user_bundle) owned by your user, only for the call on a bundle. The certification step does not need to exist. The bundle id is returned by practice creation —POST /api/v3/form/{slug}— in theuser_bundle_idfield.POST /api/partner/certificationsneeds no bundle at all. - At least one seal registered on your account, on your parent account, or belonging to the certification body (the association) you are entitled to certify for — in that case the seal may live on a different account.
Your seals
GET /api/users/me/seals
Lists the available seals: yours, your parent account's if you have one, and those of the certification body you are entitled to certify for.
[
{
"id": 42,
"first_name": "Mario",
"last_name": "Rossi",
"email": "mario.rossi@example.it",
"image": "sigilli/mario-rossi.png",
"is_verified": true,
"certification_body_id": null,
"certification_body_name": null
},
{
"id": 57,
"first_name": "Associazione",
"last_name": "Proprietari",
"email": "info@associazione.it",
"image": "sigilli/associazione.png",
"is_verified": true,
"certification_body_id": 3,
"certification_body_name": "Associazione Proprietari"
}
]The id from this list is the one to pass when applying seals.
certification_body_id and certification_body_name tell you which body you are certifying on behalf of with that seal: the number, the footer text and the seal position on the page are dictated by the body. They are null for seals that belong to no body, which behave exactly as they always have.
If your account is enabled for more than one body, the list contains the seals of all of them, each with its own body.
Applying the seals
POST /api/partner/user-bundle/{user_bundle_id}/seal
| Field | Type | Notes |
|---|---|---|
seals[].id | integer | One or more seals, applied in the given order. Required unless you pass seal_id |
seal_id | integer | Shorthand for the single-seal case, alternative to seals[] |
file | file | PDF to seal, max 20 MB. When present, it takes precedence over attachments |
attachments[].url | string | Alternative to the file: public URLs of the PDFs to seal, merged into a single document |
attachments[].name | string | Attachment name, optional (shown in error messages) |
The seal must belong to you, to your parent account or to the certification body you are entitled to certify for, and the bundle must be yours — otherwise 403.
With an uploaded PDF (two seals)
curl -X POST https://api.dokicasa.it/api/partner/user-bundle/12345/seal \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "file=@contract.pdf" \
-F "seals[0][id]=42" \
-F "seals[1][id]=57"With attachments already online (one seal)
curl -X POST https://api.dokicasa.it/api/partner/user-bundle/12345/seal \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"seals": [{ "id": 42 }],
"attachments": [
{ "name": "Contract", "url": "https://your-storage/contract.pdf" },
{ "name": "Annex A", "url": "https://your-storage/annex-a.pdf" }
]
}'Response — 201 Created
{
"status": "READY",
"numero_certificazione": "12/2026",
"document": {
"name": "certificazione_12345_12.pdf",
"path": "https://r3-it.storage.cloud.it/storage/bundles-certifications/12345/certificazione_12345_12.pdf"
},
"seals": [
{
"id": 7,
"certification_body_id": 3,
"certification_body_name": "Associazione Proprietari"
}
]
}seals lists the applied seals, each with its body (null if the seal belongs to no body).
The certification is recorded on the bundle. If the bundle already has the certification step, the same document is also added to that step's practice attachments, named Documento Certificato: you can read it back from GET /api/user-service/{id}/attachments.
Certifying with or without a bundle
POST /api/partner/certifications
Same sealing, same numbering, same document out. The bundle is your choice, field by field: pass user_bundle_id and the certification is recorded on that practice exactly as on the seals route; leave it out and there is no DokiCasa practice behind it — no step to prepare, no bundle to create first.
| Field | Type | Notes |
|---|---|---|
external_id | string | Required. The certification id on your own system, max 100 characters |
user_bundle_id | integer | Optional. The bundle to record the certification on. Leave it out to certify a document that has no practice on our side |
seals[].id | integer | One or more seals, applied in the order received. Required unless you pass seal_id |
seal_id | integer | Shortcut for the single-seal case |
file | file | PDF to certify, max 20 MB. When uploaded, it takes precedence over attachments |
attachments[].url | string | Instead of the file: public URLs of the PDFs, merged into a single document |
attachments[].name | string | Attachment name, optional |
# Your document, no practice on our side
curl -X POST https://api.dokicasa.it/api/partner/certifications \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "external_id=CONTRACT-2026-0031" \
-F "file=@contract.pdf" \
-F "seals[0][id]=42"
# Same call, recorded on one of our bundles
curl -X POST https://api.dokicasa.it/api/partner/certifications \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "external_id=CONTRACT-2026-0031" \
-F "user_bundle_id=12345" \
-F "file=@contract.pdf" \
-F "seals[0][id]=42"With user_bundle_id the response carries that bundle, the document is recorded on the practice and — if the certification step exists — filed among its attachments as Documento Certificato. The bundle must be yours, otherwise 403 BUNDLE_NOT_OWNED.
Response — 201 Created
{
"id": 918,
"status": "READY",
"external_id": "CONTRACT-2026-0031",
"numero_certificazione": "13/2026",
"number": 13,
"year": 2026,
"certified_at": "2026-09-18 11:20:44",
"user_bundle_id": null,
"document": {
"name": "certificazione_doc-918_13.pdf",
"path": "https://r3-it.storage.cloud.it/storage/certifications/918/certificazione_doc-918_13.pdf"
},
"documents": [{ "name": "certificazione_doc-918_13.pdf", "path": "..." }],
"seals": [{ "id": 42, "certification_body_id": 3, "certification_body_name": "Owners Association" }]
}external_id, or: never certify twice
Every call produces a real qualified signature and consumes a serial number. external_id is your protection against duplicates: call again with the same key and you get back the certification already issued — 200 OK instead of 201 — without signing again and without being charged twice.
So a network timeout can be retried without a second thought: what matters is sending the same key. If a certification with that key is still being processed the answer is 409 CERTIFICATION_IN_PROGRESS; if an earlier attempt failed, the new one keeps the number already assigned instead of burning another.
Reading certifications back
GET /api/partner/certifications/{id}
GET /api/partner/certifications?external_id=...&status=READY&per_page=25The list is your own register, most recent first, with data and meta (current_page, last_page, per_page, total). It covers bundle certifications too: the .../seal response carries certification_id, which is the id to use here.
What the document looks like
- Multiple attachments are merged into a single PDF, in the order you pass them.
- Every page gets the
Certificazione N/YYYYlabel in the top-left corner, with the text of the certifying body underneath (for associations that signed the territorial agreement it is the usual wording). - Seals are applied on every page, at the bottom, left to right: two seals sit side by side, not one on top of the other.
- A single seal may sit on the right instead, when it is a tenants' association seal: that is how two associations certify the same document, one at a time, each from its own side.
- The same seal passed twice is applied only once.
Numbering
The certification number is progressive per year. The series belongs to the certification body, when the seals used belong to one; otherwise it belongs to the parent account, or to you if you have no parent. Every series starts from 1.
A bundle that already has a number keeps it: sealing the same bundle again (for example after fixing an attachment) updates the document without burning a new number and without leaving gaps in the series. Without a bundle the same holds for external_id: that is the key that ties the attempts together.
PDF requirements
Every page must be A4 portrait: seals are placed at fixed coordinates, so a different format would put them out of place. Landscape or A3 documents are rejected with 422 NOT_A4, telling you which attachment is out of format and the detected size.
Errors
| HTTP | error | When |
|---|---|---|
403 | PARTNER_PRICING_REQUIRED | No SEAL_PDF pricing configured for your account |
403 | BUNDLE_NOT_OWNED | The bundle does not belong to your user |
403 | CERTIFICATION_NOT_OWNED | The certification you are reading back is not yours |
409 | CERTIFICATION_IN_PROGRESS | A certification with the same external_id is being processed |
403 | SEAL_NOT_OWNED | One of the seals is neither yours, nor your parent's, nor your body's |
400 | AMOUNT_NOT_ENOUGH | Not enough wallet balance. The response includes missing_amount and checkout_link |
422 | NO_DOCUMENT | Neither file nor attachments: nothing to seal |
422 | NOT_A4 | An attachment is not A4 portrait |
On top of these, the call returns 422 with message and errors (standard validation format) when the input is not acceptable in the first place:
attachments[].urlmust be http or https pointing at a public address: internal, private or other-scheme URLs are rejected;- at most 10 seals and 20 attachments per call;
external_idis required and no longer than 100 characters;user_bundle_id, when passed, must be an existing, non-deleted bundle.
NOTE
You are charged only once the document is produced: if sealing fails, the call returns an error and nothing is deducted.