# Documents

> Create, manage, and track documents through the signing lifecycle

Source: https://docs.korala.ai/guides/documents

---

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

## Document Lifecycle

```mermaid
stateDiagram-v2
    [*] --> Draft: Create
    Draft --> Pending: Send
    Pending --> Completed: All signed
    Pending --> Voided: Void
    Pending --> Expired: Expiration
    Pending --> Declined: Signer declines
    Completed --> [*]
    Voided --> [*]
    Expired --> [*]
    Declined --> [*]
```

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

1. **Get an upload URL** - Request a pre-signed URL for uploading your PDF
2. **Confirm the upload** - Tell Korala the upload is complete

    ```typescript
    import { 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');
    await fetch(uploadUrl, {
      method: 'PUT',
      body: pdfBuffer,
      headers: { 'Content-Type': 'application/pdf' },
    });

    // Step 3: Confirm the upload
    const document = await korala.documents.confirmUpload(documentId);

    console.log(`Document created: ${document.id}`);
    console.log(`Status: ${document.status}`); // 'draft'
    ```
    ```bash
    # Step 1: Get upload URL
    UPLOAD_RESPONSE=$(api_request "POST" "/api/v1/documents/upload-url" \
      '{"filename":"contract.pdf","contentType":"application/pdf"}')

    DOCUMENT_ID=$(echo $UPLOAD_RESPONSE | jq -r '.documentId')
    UPLOAD_URL=$(echo $UPLOAD_RESPONSE | jq -r '.uploadUrl')

    # Step 2: Upload the file
    curl -X PUT "$UPLOAD_URL" \
      -H "Content-Type: application/pdf" \
      --data-binary @contract.pdf

    # Step 3: Confirm the upload
    api_request "POST" "/api/v1/documents/${DOCUMENT_ID}/confirm-upload"
    ```

## 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: 'john@example.com',
      name: 'John Doe',
    });

    // Add signature fields
    await korala.fields.create(documentId, {
      signerId: signer.id,
      fieldType: 'signature',
      pageNumber: 1,
      xPosition: 100,
      yPosition: 500,
      width: 200,
      height: 50,
    });

    // Send for signing
    const document = await korala.documents.send(documentId, {
      electronicDisclosure: {
        id: 'acme-electronic-signing-terms',
        version: '2026-09-01',
        title: 'Electronic signature disclosure',
        contentHtml: '<p>Contact Acme Legal for a paper copy.</p>',
      },
    });

    console.log(`Status: ${document.status}`); // 'pending'
    ```
    ```bash
    # Send for signing
    api_request "POST" "/api/v1/documents/${DOCUMENT_ID}/send"
    ```

`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.

```typescript
// Expire this document if unsigned after 14 days.
const document = await korala.documents.send(documentId, { 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.

```typescript
await korala.organizations.updateSettings({
  reminderEnabled: true,
  reminderIntervalDays: 3, // default 3
  maxReminders: 3,         // default 3
  defaultExpirationDays: 30,
});
```

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](https://docs.korala.ai/guides/audit-trail).

## 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}`);
    }
    ```
    ```bash
    api_request "GET" "/api/v1/documents/${DOCUMENT_ID}"
    ```

### List All Documents

    ```typescript
    // List all documents
    const documents = await korala.documents.list();

    // Filter by status
    const pendingDocs = await korala.documents.list({ status: 'pending' });

    // Pagination
    const page2 = await korala.documents.list({ limit: 10, offset: 10 });
    ```
    ```bash
    # List all documents
    api_request "GET" "/api/v1/documents"

    # Filter by status
    api_request "GET" "/api/v1/documents?status=pending"

    # Pagination
    api_request "GET" "/api/v1/documents?limit=10&offset=10"
    ```

## 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') {
      // Download signed PDF
      const signedPdf = await fetch(document.signedFileUrl);
      fs.writeFileSync('signed-contract.pdf', await signedPdf.buffer());

      // Download certificate
      const certificate = await fetch(document.certificateFileUrl);
      fs.writeFileSync('certificate.pdf', await certificate.buffer());
    }
    ```

## Voiding a Document

Cancel a document before it's completed:

    ```typescript
    const document = await korala.documents.void(documentId);
    console.log(`Status: ${document.status}`); // 'voided'
    ```
    ```bash
    api_request "POST" "/api/v1/documents/${DOCUMENT_ID}/void"
    ```

Voiding a document is permanent. Korala notifies signers that you cancelled the document.

## Response Format

```json
{
  "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](https://docs.korala.ai/guides/collections) for scope, archive behavior, request
limits, and API and SDK examples.
