Skip to Content
Documents and templatesDocuments

Documents

Documents are the core resource in Korala. This guide covers creating documents, managing their lifecycle, and retrieving signed copies.

Document Lifecycle

Document lifecycleCreate a draft, then send it. A pending document completes when everyone signs, or ends as voided, expired or declined.SendAll signVoidTime expiresSigner declinesDraftPendingCompletedVoidedExpiredDeclined
Create a draft, then send it. A pending document completes when everyone signs, or ends as voided, expired or declined.
StatusDescription
draftDocument created but not sent for signing
pendingSent to signers, awaiting signatures
completedAll signatures collected, document sealed
voidedCancelled before completion
expiredExpiration date passed before completion

Creating a Document

Document creation is a two-step process:

  1. Get an upload URL - Request a pre-signed URL for uploading your PDF
  2. Confirm the upload - Tell Korala the upload is complete
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:

// 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

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

// 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:

FileDescription
originalFileUrlThe original uploaded PDF
signedFileUrlPDF with visual signatures and cryptographic seal
certificateFileUrlCertificate of Completion (audit trail)
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:

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.

Last updated on