Document Signing
File Starter also provides a lightweight signing flow built on top of SigningRequest and SigningDocument.
Signing Model
SigningRequest: signing transaction header, recipient, status, expiration time, and relatedSigningDocumentlist.SigningDocument: one signable document under a signing request.
Current implementation stores a compact set of top-level fields on SigningDocument:
- business fields:
signingRequestId,templateId,signSlotCode,status - generated file fields:
signedImageId,signedPdfId - signer fields:
signerUserId,signerName,signedAt - audit correlation:
evidenceId - full evidence payload:
signatureEvidence(JsonNode)
signatureEvidence contains:
evidenceIdsignSlotCodeclientPayloadresolvedPlacementresolvedRenderOptionsserverEvidencesignatureMethodsignerUserId,signerNameserverSignedAtclientIp,userAgentsignatureImageFileId,generatedSignedFileId,originalTemplateFileIdoriginalPdfSha256,signatureImageSha256,signedPdfSha256
Status
SigningRequestStatus:
DraftSentInProgressCompletedCancelledExpired
SigningDocumentStatus:
PendingInProgressCompleted
Sign Endpoint
Endpoint:
POST /SigningDocument/sign?id={id}
Content type:
multipart/form-data
Parts:
signatureFile: handwritten signature image file, usually PNGpayload: JSON payload ofSigningDocumentSignRequest
Request DTO:
{
"signSlotCode": "EMPLOYEE_SIGN",
"placement": {
"page": 1,
"x": 120,
"y": 90,
"width": 180,
"height": 64,
"unit": "PT"
},
"evidence": {
"signatureMethod": "DRAW",
"clientSignedAt": "2026-03-24T10:15:30+08:00",
"clientTimeZone": "Asia/Shanghai",
"consentAccepted": true,
"consentTextVersion": "v1",
"signerDisplayName": "Alice",
"userAgent": "Mozilla/5.0",
"canvasWidth": 800,
"canvasHeight": 240
},
"renderOptions": {
"flattenToPdf": true,
"keepSignatureImage": true,
"imageScaleMode": "FIT"
}
}Response DTO:
{
"signingDocumentId": 9001,
"status": "Completed",
"signedFile": {
"fileId": 701,
"fileName": "contract_signed_20260324.pdf",
"fileType": "PDF",
"url": "https://...",
"size": 256,
"checksum": "..."
},
"signatureImageFile": {
"fileId": 700,
"fileName": "signature.png",
"fileType": "PNG",
"url": "https://...",
"size": 12,
"checksum": "..."
},
"signedAt": "2026-03-24T10:15:32+08:00",
"evidenceId": "abc123"
}Signing Rules
The current sign flow completes the following steps in one request:
- Validate current user, recipient, signing request status, and expiration time.
- Upload the signature image file and persist
signedImageId. - Build the original PDF from
DocumentTemplate. - Resolve signature placement:
- Prefer
signSlotCodeby locating a PDF form field in the source PDF. - Fallback to
placementif the slot does not exist or the source PDF has no matching field.
- Prefer
- Stamp the signature image onto the PDF.
- Upload the signed PDF and persist
signedPdfId. - Persist
signatureEvidence,evidenceId, signer info, and sign timestamp. - Update
SigningDocument.statusand refreshSigningRequest.status.
Placement Resolution
signSlotCodeis the recommended mode for template-defined sign slots.placementis the fallback for free positioning.- Supported placement units:
PTPXMMCMIN
Current Limitation
The current signing implementation builds the original PDF only from DocumentTemplate:
DocumentTemplate.fileIdwithPDFis used directlyDocumentTemplate.fileIdwithDOCXis rendered with empty data and then converted to PDFDocumentTemplate.htmlTemplateis rendered with empty data and converted to PDF
This means the current implementation is suitable for:
- signing fixed PDF templates
- signing static DOCX or HTML templates without business-row rendering
It does not yet support:
- signing a document that must first be rendered with business row data and then assigned to a
SigningDocument
REST APIs (Summary)
- Import
POST /import/importByTemplatePOST /import/dynamicImportGET /ImportTemplate/getTemplateFile
- Export
POST /export/exportByTemplate(dispatches to field-template or file-template mode based oncustomFileTemplate)POST /export/dynamicExport
- Document
GET /DocumentTemplate/generateDocument
- Signing
POST /SigningDocument/sign
- Template Listing
POST /ImportTemplate/listByModelPOST /ExportTemplate/listByModel
Examples
Export params (with cascaded fields):
{
"fields": ["id", "name", "code", "status", "deptId.name", "deptId.managerId.name"],
"filters": ["status", "=", "ACTIVE"],
"orders": ["createdTime", "DESC"],
"limit": 200,
"groupBy": [],
"effectiveDate": "2026-03-03"
}Import field mapping (with relation lookup):
[
{"header": "Product Code", "fieldName": "productCode", "required": true},
{"header": "Product Name", "fieldName": "productName", "required": true},
{"header": "Category Code", "fieldName": "categoryId.code", "required": true},
{"header": "Price", "fieldName": "price"}
]Import field mapping (direct FK id):
[
{"header": "Product Code", "fieldName": "productCode", "required": true},
{"header": "Product Name", "fieldName": "productName", "required": true},
{"header": "Price", "fieldName": "price"}
]Import env:
{
"deptId": 10,
"source": "manual"
}Last updated on