# TypeScript API Client

> Official TypeScript API client for Korala

Source: https://docs.korala.ai/sdks/typescript

---

The official TypeScript API client provides a type-safe interface for the Korala API.

## Installation

```bash
npm install @korala/api-client
# or
yarn add @korala/api-client
# or
pnpm add @korala/api-client
```

## Quick Start

```typescript

const korala = new KoralaClient({
  apiKeyId: process.env.KORALA_API_KEY_ID!,
  apiSecret: process.env.KORALA_API_SECRET!,
});

// Create a document
const { documentId, uploadUrl } = await korala.documents.createUploadUrl({
  filename: 'contract.pdf',
  contentType: 'application/pdf',
});

// Upload the file to S3
await fetch(uploadUrl, {
  method: 'PUT',
  body: pdfBuffer,
  headers: { 'Content-Type': 'application/pdf' },
});

// Confirm the upload
await korala.documents.confirmUpload(documentId);

// Add a signer
const signer = await korala.signers.create(documentId, {
  email: 'john@example.com',
  name: 'John Doe',
});

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

// Send for signing
await korala.documents.send(documentId);
```

## Configuration

```typescript
interface KoralaClientConfig {
  // Required
  apiKeyId: string;     // API key ID
  apiSecret: string;    // API secret for HMAC signing

  // Optional
  baseUrl?: string;     // API base URL (default: https://api.korala.ai/api/v1)
  timeout?: number;     // Request timeout in ms (default: 30000)
}
```

## Documents

### Create Upload URL

```typescript
const { documentId, uploadUrl, key } = await korala.documents.createUploadUrl({
  filename: 'contract.pdf',
  contentType: 'application/pdf',
});

// Upload the file to the presigned URL
await fetch(uploadUrl, {
  method: 'PUT',
  body: pdfBuffer,
  headers: { 'Content-Type': 'application/pdf' },
});

// Confirm upload
await korala.documents.confirmUpload(documentId);
```

### Get Document

```typescript
const document = await korala.documents.get(documentId);

console.log(document.status);
console.log(document.originalFileUrl);
console.log(document.signedFileUrl);
```

### List Documents

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

### Send Document

```typescript
await korala.documents.send(documentId);
```

### Void Document

```typescript
await korala.documents.void(documentId);
```

### Get Audit Trail

```typescript
const events = await korala.documents.getAuditTrail(documentId);
```

### Bulk Sign

Sign multiple documents at once using a saved signature:

```typescript
const result = await korala.documents.bulkSign({
  documentIds: ['doc-1', 'doc-2', 'doc-3'],
  signerEmail: 'ceo@company.com',
  autoFillDateValue: '2026-02-18', // optional, defaults to today
});

console.log(`Signed: ${result.signed}, Failed: ${result.failed}`);

for (const doc of result.results) {
  if (!doc.success) {
    console.error(`${doc.documentId}: ${doc.error}`);
  }
}
```

#### IP Attribution

When calling bulk sign on behalf of an end user, pass their IP for audit trail attribution:

```typescript
const result = await korala.documents.bulkSign(
  {
    documentIds: ['doc-1', 'doc-2'],
    signerEmail: 'ceo@company.com',
  },
  { clientIp: '203.0.113.42' },
);
```

See the [Bulk Signing guide](../guides/bulk-signing) for the complete workflow.

## Signing Packets

Create an authenticated packet-management client, then send one recipient to
Korala to review and authorize up to 50 documents:

```typescript
const packet = await korala.signingPackets.create({
  name: 'Series A closing',
  externalId: 'closing-2026-0142',
  recipient: { name: 'Alex Manager', email: 'alex@example.com' },
  items: [
    { documentId: subscription.id, signerId: subscriptionSigner.id },
    { documentId: sideLetter.id, signerId: sideLetterSigner.id },
  ],
  redirectUrl: 'https://fund.example.com/closing/complete',
});

const sent = await korala.signingPackets.send(packet.id, {
  delivery: 'email',
});

console.log(sent.signingUrl, sent.deliveryStatus);
```

The API key can prepare, send, inspect, remind, void, and retrieve audit data.
The recipient must verify their email and complete the packet in Korala. See
the [Signing Packets guide](../guides/signing-packets) for state handling,
webhooks, audit evidence, and limits.

## Signatures

Manage saved signatures for signers. Bulk sign uses saved signatures to auto-fill Signature and Initials fields.

### Create Signature

```typescript
const signature = await korala.signatures.create({
  email: 'ceo@company.com',
  name: 'Jane Smith',
  signatureImageUrl: 'data:image/png;base64,...',
  isDefault: true, // optional, defaults to true
});
```

### List Signatures

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

// Filter by email
const ceoSignatures = await korala.signatures.list({ email: 'ceo@company.com' });
```

### Get Signature

```typescript
const signature = await korala.signatures.get(signatureId);
```

### Delete Signature

```typescript
await korala.signatures.delete(signatureId);
```

## Signers

### Add Signer

```typescript
const signer = await korala.signers.create(documentId, {
  email: 'john@example.com',
  name: 'John Doe',
  signingOrder: 1, // optional
});
```

### List Signers

```typescript
const signers = await korala.signers.list(documentId);
```

### Remove Signer

```typescript
await korala.signers.delete(documentId, signerId);
```

## Fields

### Add Field

```typescript
const field = await korala.fields.create(documentId, {
  signerId: signer.id,
  fieldType: 'signature', // 'signature' | 'initials' | 'date' | 'text' | 'checkbox'
  pageNumber: 1,
  xPosition: 100,
  yPosition: 500,
  width: 200,
  height: 50,
});
```

### List Fields

```typescript
const fields = await korala.fields.list(documentId);
```

### Remove Field

```typescript
await korala.fields.delete(documentId, fieldId);
```

## Webhooks

### Create Webhook

```typescript
const webhook = await korala.webhooks.create({
  url: 'https://your-app.com/webhooks/korala',
  events: ['document_completed', 'document_signed'],
});

// Save the secret for signature verification!
console.log(webhook.secret);
```

### List Webhooks

```typescript
const webhooks = await korala.webhooks.list();
```

### Get Webhook

```typescript
const webhook = await korala.webhooks.get(webhookId);
```

### Update Webhook

```typescript
await korala.webhooks.update(webhookId, {
  events: ['document_completed'],
  isActive: true,
});
```

### Delete Webhook

```typescript
await korala.webhooks.delete(webhookId);
```

### Get Delivery History

```typescript
const deliveries = await korala.webhooks.getDeliveries(webhookId);
```

### Send Test Event

```typescript
const delivery = await korala.webhooks.sendTestEvent(webhookId);
```

## Error Handling

```typescript

try {
  await korala.documents.send(documentId);
} catch (error) {
  if (error instanceof KoralaValidationError) {
    console.error('Validation failed:', error.errors);
  } else if (error instanceof KoralaError) {
    console.error(`API error ${error.status}: ${error.message}`);
  } else {
    throw error;
  }
}
```

## TypeScript Types

The client exports types for all responses:

```typescript
import type {
  DocumentDto,
  DocumentStatus,
  SignerDto,
  SignerStatus,
  FieldDto,
  FieldType,
  WebhookDto,
  WebhookEventType,
  WebhookPayloadDto,
  // Event-specific data types
  DocumentCreatedEventData,
  DocumentSentEventData,
  DocumentViewedEventData,
  DocumentSignedEventData,
  DocumentCompletedEventData,
  DocumentFailedEventData,
  DocumentVoidedEventData,
  DocumentDeclinedEventData,
  TestEventData,
} from '@korala/api-client';

function handleDocument(doc: DocumentDto) {
  if (doc.status === 'completed') {
    console.log(`Completed at: ${doc.completedAt}`);
  }
}

function handleWebhook(payload: WebhookPayloadDto) {
  switch (payload.event) {
    case 'document_signed': {
      const data = payload.data as DocumentSignedEventData;
      console.log(`Signer ${data.signerId} signed, all done: ${data.allSigned}`);
      break;
    }
    case 'document_completed': {
      const data = payload.data as DocumentCompletedEventData;
      console.log(`Document completed at ${data.completedAt}`);
      break;
    }
  }
}
```

## Webhook Verification

The client includes a helper for verifying webhook signatures:

```typescript

app.post('/webhooks/korala', express.raw({ type: 'application/json' }), (req, res) => {
  const isValid = verifyWebhookSignature({
    secret: process.env.WEBHOOK_SECRET!,
    signature: req.headers['x-signature'] as string,
    timestamp: req.headers['x-timestamp'] as string,
    body: req.body.toString(),
  });

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body.toString());
  // Process event...

  res.json({ received: true });
});
```

## Collections and paginated lists

Use `client.collections` to create, find, archive, restore, and organize
collections. See the [collections workflow](https://docs.korala.ai/guides/collections) for external-ID
reconciliation, explicit creation destinations, and packet preparation.

Use `client.documents.listPage(query)` for the pagination envelope
`{ data, total, page, limit, totalPages }`. The deprecated `documents.list()`
now returns the first page’s items, correcting its previous mismatch between
the declared array and the server’s envelope. It does not fetch every page.
`templates.list(query)` already returns an envelope; `templates.listPage(query)`
is an alias. Both resources accept collection and unassigned filters.
