Bulk Signing
Bulk signing signs hundreds of documents in one API call. Use it when one signer (e.g., a CEO) needs to sign many documents of the same type.
Overview
The bulk signing flow has three steps:
- Save a signature: Capture and store the signer’s signature image
- Create documents: Create documents from a template with signers and fields
- Bulk sign: Sign all documents at once using the saved signature
Step 1: Save a Signature
Before bulk signing, you need a saved signature for the signer. You can capture signatures using the <KoralaSignaturePad> React component or any other method that produces a base64 PNG data URL.
Format requirement: Signature images must be base64 PNG data URLs (
data:image/png;base64,...). The API rejects JPEG, SVG, and regular HTTP URLs. The maximum size is 500KB.
Using the React Signature Pad
import { KoralaSignaturePad } from '@korala/react';
function SignatureCapture() {
return (
<KoralaSignaturePad
onSignatureCreated={({ imageDataUrl }) => {
// Send to your backend to save via Korala API
fetch('/api/save-signature', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
signatureImageUrl: imageDataUrl,
email: '[email protected]',
name: 'Jane Smith',
}),
});
}}
/>
);
}Saving via the API
TypeScript
import { KoralaClient } from '@korala/api-client';
const korala = new KoralaClient({
apiKeyId: process.env.KORALA_API_KEY_ID!,
apiSecret: process.env.KORALA_API_SECRET!,
});
const signature = await korala.signatures.create({
email: '[email protected]',
name: 'Jane Smith',
signatureImageUrl: 'data:image/png;base64,...',
isDefault: true,
});
console.log(`Saved signature: ${signature.id}`);Managing Saved Signatures
// List signatures for a specific email
const signatures = await korala.signatures.list({ email: '[email protected]' });
// Get a specific signature
const signature = await korala.signatures.get(signatureId);
// Delete a signature
await korala.signatures.delete(signatureId);Step 2: Create Documents
Create your documents using templates. See the Documents guide for details on creating and sending documents.
// Create 200 NDAs from a template
const documentIds: string[] = [];
for (const recipient of recipients) {
const doc = await korala.templates.createDocument(templateId, {
signerRoles: {
'Company Representative': {
email: '[email protected]',
name: 'Jane Smith',
},
'Recipient': {
email: recipient.email,
name: recipient.name,
},
},
});
documentIds.push(doc.id);
// Send for signing
await korala.documents.send(doc.id);
}Step 3: Bulk Sign
Sign all documents at once using the saved signature:
TypeScript
const result = await korala.documents.bulkSign({
documentIds,
signerEmail: '[email protected]',
autoFillDateValue: '2026-02-18', // optional, defaults to today
});
console.log(`Signed: ${result.signed}`);
console.log(`Failed: ${result.failed}`);
// Check individual results
for (const doc of result.results) {
if (doc.success) {
console.log(`${doc.documentId}: signed (allSigned: ${doc.allSigned})`);
} else {
console.log(`${doc.documentId}: failed - ${doc.error}`);
}
}How Auto-Fill Works
During bulk signing, Korala fills fields by type:
| Field Type | Auto-Fill Behavior |
|---|---|
| Signature | Uses the signer’s default saved signature |
| Initials | Uses the signer’s default saved signature |
| Date | Uses autoFillDateValue (defaults to today) |
| Text | Requires a prefilled value (e.g., from template variables) |
| Checkbox | Requires a prefilled value (e.g., from template variables) |
Important: If no saved signature exists for the signer, any required Signature or Initials fields will fail with an error. You must save a signature via
POST /signatures(Step 1) before calling bulk sign.
Per-Document Overrides
You can override specific field values for individual documents:
const result = await korala.documents.bulkSign({
documentIds: ['doc-1', 'doc-2', 'doc-3'],
signerEmail: '[email protected]',
documents: [
{
documentId: 'doc-3',
fields: [
// Override a text field value
{ fieldId: 'field-xyz', value: 'Special terms apply' },
// Override a signature field with a different signature image
{
fieldId: 'field-sig',
value: 'Signed',
signatureImageUrl: 'data:image/png;base64,...',
},
],
},
],
});Per-document overrides take precedence over auto-fill.
Note: Signature images must be base64 PNG data URLs (
data:image/png;base64,...). The API rejects other formats (JPEG, SVG, regular URLs).
Handling Partial Success
Bulk sign always returns HTTP 200 with per-document results. Some documents may fail while others succeed:
const result = await korala.documents.bulkSign({
documentIds,
signerEmail: '[email protected]',
});
if (result.failed > 0) {
const failures = result.results.filter((r) => !r.success);
console.error('Failed documents:', failures);
// Retry failed documents
const retryIds = failures
.filter((f) => f.error !== 'Signer has already signed')
.map((f) => f.documentId);
if (retryIds.length > 0) {
await korala.documents.bulkSign({
documentIds: retryIds,
signerEmail: '[email protected]',
});
}
}Identifying Signers
You can identify the target signer by email or external ID:
// By email
await korala.documents.bulkSign({
documentIds,
signerEmail: '[email protected]',
});
// By external ID
await korala.documents.bulkSign({
documentIds,
signerExternalId: 'user_123',
});Provide exactly one of signerEmail or signerExternalId.
When one person holds several roles
A packet can legitimately list the same person more than once. An officer who signs for a company, for its parent guarantor and for a subsidiary appears as three signers sharing an email at signing orders 1, 2 and 3.
signerEmail matches all three. Korala takes the lowest signing order that
has not signed or declined, so repeated calls walk the roles in order:
// Call 1 signs signing order 1
await korala.documents.bulkSign({ documentIds, signerEmail: '[email protected]' });
// Call 2 signs signing order 2, and so on
await korala.documents.bulkSign({ documentIds, signerEmail: '[email protected]' });Each call signs one signer per document, and the signing order rules below still apply.
To name one role instead of taking whichever comes next, give each signer a
distinct externalId when you create the document, then address it with
signerExternalId:
await korala.templates.createDocument(templateId, {
name: 'Supply Agreement — Acme',
signers: {
Company: { name: 'J. Officer', email: '[email protected]', externalId: 'company' },
Guarantor: { name: 'J. Officer', email: '[email protected]', externalId: 'guarantor' },
Subsidiary: { name: 'J. Officer', email: '[email protected]', externalId: 'subsidiary' },
},
});
await korala.documents.bulkSign({ documentIds, signerExternalId: 'guarantor' });externalId belongs to one signer, so it still identifies a single role when
emails repeat, and it holds its meaning across every document you generate from
the template.
IP Attribution
When your server calls the bulk sign API on behalf of end users, the audit trail records your server’s IP address. To preserve the end-user IP for compliance, pass it via the X-Client-IP header. Korala stores the value as claimedIpAddress in the audit trail alongside the verified network IP.
TypeScript
const result = await korala.documents.bulkSign(
{
documentIds,
signerEmail: '[email protected]',
},
{ clientIp: endUserIpAddress },
);Note: The
X-Client-IPvalue must be a valid IP address (IPv4 or IPv6). Korala ignores invalid values. Korala validates the header’s format without verifying it; the recorded value is the partner’s assertion of the end-user’s IP.
Limits
- Maximum 500 documents per bulk sign request
- Documents must be in Pending status
- The target signer must not have already signed or declined
- Every signer at a lower signing order must have signed first, otherwise the
document fails with
Waiting for prior signers: ...
Response Format
{
"signed": 198,
"failed": 2,
"results": [
{
"documentId": "doc-1",
"success": true,
"allSigned": true
},
{
"documentId": "doc-2",
"success": true,
"allSigned": false
},
{
"documentId": "doc-x",
"success": false,
"error": "Document not found"
}
]
}When allSigned is true, Korala has queued the document for completion (cryptographic signing and timestamping). Listen for the document_completed webhook to know when the signed PDF is ready.