Documents
Documents are the core resource in Korala. This guide covers creating documents, managing their lifecycle, and retrieving signed copies.
Document Lifecycle
| Status | Description |
|---|---|
draft | Document created but not sent for signing |
pending | Sent to signers, awaiting signatures |
completed | All signatures collected, document sealed |
voided | Cancelled before completion |
expired | Expiration date passed before completion |
Creating a Document
Document creation is a two-step process:
- Get an upload URL - Request a pre-signed URL for uploading your PDF
- Confirm the upload - Tell Korala the upload is complete
TypeScript
import { FieldType, KoralaClient } from '@korala/api-client';
import fs from 'fs';
const korala = new KoralaClient({
apiKeyId: 'your-api-key-id',
apiSecret: 'your-api-secret',
});
// Step 1: Get upload URL
const { documentId, uploadUrl, key } = await korala.documents.createUploadUrl({
filename: 'contract.pdf',
contentType: 'application/pdf',
});
// Step 2: Upload the file
const pdfBuffer = fs.readFileSync('contract.pdf');
const upload = await fetch(uploadUrl, {
method: 'PUT',
body: pdfBuffer,
headers: { 'Content-Type': 'application/pdf' },
});
if (!upload.ok) throw new Error(`Upload failed: ${upload.status}`);
// Step 3: Confirm the upload
const document = await korala.documents.confirmUpload(documentId);
console.log(`Document created: ${document.id}`);
console.log(`Status: ${document.status}`); // 'draft'Sending for Signatures
Once you’ve added signers and fields, send the document:
TypeScript
// Add signers first
const signer = await korala.signers.create(documentId, {
email: '[email protected]',
name: 'John Doe',
});
// Add signature fields
await korala.fields.create(documentId, {
signerId: signer.id,
fieldType: FieldType.Signature,
pageNumber: 1,
xPosition: 100,
yPosition: 500,
width: 200,
height: 50,
});
// Send with the default electronic-signature disclosure
const document = await korala.documents.send(documentId);
console.log(`Status: ${document.status}`); // 'pending'To supply custom disclosure content, use the REST endpoint with the
api_request helper.
The current TypeScript client does not expose this option:
api_request "POST" "/api/v1/documents/${DOCUMENT_ID}/send" \
'{"electronicDisclosure":{"id":"acme-electronic-signing-terms","version":"2026-09-01","title":"Electronic signature disclosure","contentHtml":"<p>Contact Acme Legal for a paper copy.</p>"}}'electronicDisclosure is optional. Use it when your organization needs custom
paper-copy, withdrawal, fee, or contact terms. Korala sanitizes the HTML and
freezes the profile when you send the document. The final signing action links
to the stored content; later edits cannot change it. Korala records the profile
ID, version, and content hash in the audit trail and Certificate of Completion.
Saving a reusable signature
In both the single-document and packet signing viewers, a signer can draw or type a signature using their signing link. To reuse a saved signature or select Save for future, they must first verify their email through the signer portal. A forwarded signing link does not grant access to saved signatures.
Signatures and initials are saved separately. A signature saved while signing one document is available in packets from the same organization, and vice versa. Changing a saved signature does not change an authorized packet action.
Reminders and Expiration
Korala can nudge idle signers and close out stale documents automatically, so you don’t have to chase people by hand.
Expiration. Pass expiresInDays when sending to set a deadline; if unsigned by then, the document moves to expired (a terminal state, like voided), signer links stop working, and a document.expired webhook fires. Omit it to fall back to your organization’s default expiry (defaultExpirationDays in org settings), or to never expire if neither is set.
Use the REST endpoint for this option:
api_request "POST" "/api/v1/documents/${DOCUMENT_ID}/send" \
'{"expiresInDays":14}'Reminders. Enable reminders once at the organization level; every unsigned signer whose turn it is (sequential order is respected) is then emailed on a cadence, up to a cap.
Organization settings control reminderEnabled, reminderIntervalDays,
maxReminders, and defaultExpirationDays. The TypeScript client does not
include an organization-settings resource.
Reminders and expiry are evaluated by a background scheduler, so they take effect within minutes of becoming due rather than exactly on the second. Documents sent with suppressNotifications (or in sandbox) are never reminded. Each reminder and the expiry are recorded in the audit trail.
Completion email. Once the signed PDF and its Certificate of Completion are ready, Korala emails every signer once, with a link to their signing page, where both files can be downloaded. Nothing is attached. A document sent from the dashboard, or by an AI assistant connected to your account, also emails the person who sent it; one sent with an API key does not, since the document_completed webhook reaches your integration. suppressNotifications and sandbox documents send no completion email.
Retrieving Documents
Get a Single Document
TypeScript
const document = await korala.documents.get(documentId);
console.log(`Name: ${document.name}`);
console.log(`Status: ${document.status}`);
console.log(`Original PDF: ${document.originalFileUrl}`);
if (document.status === 'completed') {
console.log(`Signed PDF: ${document.signedFileUrl}`);
console.log(`Certificate: ${document.certificateFileUrl}`);
console.log(`Completed: ${document.completedAt}`);
}List Documents
TypeScript
// Fetch one page of documents
const { data: documents, totalPages } = await korala.documents.listPage();
// Filter by status
const pendingDocs = await korala.documents.listPage({ status: 'pending' });
// Pagination
const page2 = await korala.documents.listPage({ limit: 10, page: 2 });Downloading Files
Completed documents have three files:
| File | Description |
|---|---|
originalFileUrl | The original uploaded PDF |
signedFileUrl | PDF with visual signatures and cryptographic seal |
certificateFileUrl | Certificate of Completion (audit trail) |
TypeScript
const document = await korala.documents.get(documentId);
if (document.status === 'completed' && document.signedFileUrl && document.certificateFileUrl) {
// Download signed PDF
const signedPdf = await fetch(document.signedFileUrl);
if (!signedPdf.ok) throw new Error(`Download failed: ${signedPdf.status}`);
fs.writeFileSync('signed-contract.pdf', Buffer.from(await signedPdf.arrayBuffer()));
// Download certificate
const certificate = await fetch(document.certificateFileUrl);
if (!certificate.ok) throw new Error(`Download failed: ${certificate.status}`);
fs.writeFileSync('certificate.pdf', Buffer.from(await certificate.arrayBuffer()));
}Voiding a Document
Cancel a document before it’s completed:
TypeScript
const document = await korala.documents.void(documentId);
console.log(`Status: ${document.status}`); // 'voided'Response Format
{
"id": "doc_abc123",
"name": "contract.pdf",
"status": "completed",
"originalFileUrl": "https://storage.example.com/original.pdf",
"signedFileUrl": "https://storage.example.com/signed.pdf",
"certificateFileUrl": "https://storage.example.com/certificate.pdf",
"expiresAt": null,
"completedAt": "2024-01-15T10:30:00Z",
"createdAt": "2024-01-14T09:00:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}Organize with collections
Documents can belong to several optional collections. Supply collectionIds
when requesting an upload URL, or organize existing documents in bulk. Filter
document lists by collectionId, unassigned, search, and status.
See Collections for scope, archive behavior, request limits, and API and SDK examples.