npm package: @sajn/sdk
Description: Official TypeScript SDK for sajn API
Author: sajn
Latest version: 0.1.0

# sajn TypeScript SDK

Official TypeScript SDK for the sajn API - a document signing and management platform.

## Features

- 🔐 **Type-safe** - Full TypeScript support with comprehensive type definitions
- 🚀 **Modern** - Built for Node.js 20+ using native fetch API
- 📦 **Tree-shakeable** - ESM and CommonJS support
- 🎯 **Resource-based** - Intuitive API organized by resources
- 🔄 **Promise-based** - Async/await support throughout
- 📤 **File uploads** - Built-in support for document uploads
- 🪝 **Webhooks** - Type definitions for webhook events

## Installation

```bash
npm install @sajn/sdk
```

## Quick Start

```typescript
import { sajnClient } from '@sajn/sdk';

const client = new sajnClient({
  apiKey: 'YOUR_API_TOKEN', // Get from https://app.sajn.se/settings/developer
});

// Create a document
const document = await client.documents.create({
  name: 'Service Agreement',
  type: 'BLANK',
  signers: [
    {
      name: 'John Doe',
      email: 'john@example.com',
      role: 'SIGNER',
    },
  ],
});

// Send for signing
await client.documents.send(document.documentId, {
  deliveryMethod: 'EMAIL', // REQUIRED: EMAIL, SMS, or NONE
  signatureType: 'DRAWING',
});
```

## Authentication

Get your API key from [https://app.sajn.se/settings/developer](https://app.sajn.se/settings/developer)

```typescript
const client = new sajnClient({
  apiKey: 'YOUR_API_TOKEN',
  baseUrl: 'https://app.sajn.se/api/v1', // Optional: defaults to production
  uploadUrl: 'https://upload.sajn.se/v1', // Optional: defaults to production
});
```

## API Reference

### Documents

```typescript
// List documents
const documentsList = await client.documents.list({
  page: 1,
  perPage: 10,
});

console.log(`Total pages: ${documentsList.totalPages}`);
console.log('Documents:', documentsList.documents);

// Get a document
const document = await client.documents.get('doc_id');

// Create a document
const newDocument = await client.documents.create({
  name: 'Contract',
  type: 'BLANK',
  expiresAt: '2025-12-31T23:59:59Z',
  signers: [
    {
      name: 'Jane Doe',
      email: 'jane@example.com',
      role: 'SIGNER',
      signingOrder: 1,
    },
  ],
});

console.log('Document ID:', newDocument.documentId);
console.log('Signing URLs:', newDocument.signers);

// Update a document
await client.documents.update('doc_id', {
  name: 'Updated Contract Name',
  documentMeta: {
    subject: 'Please sign this contract',
    message: 'This is an important contract',
  },
});

// Send for signing (REQUIRED: deliveryMethod)
await client.documents.send('doc_id', {
  deliveryMethod: 'EMAIL', // EMAIL, SMS, or NONE
  signatureType: 'DRAWING', // DRAWING, BANKID, SMS, or TYPED
  customMessage: 'Please review and sign',
});

// Download signed document
const download = await client.documents.download('doc_id', {
  downloadOriginalDocument: false, // false = signed PDF, true = original
});

console.log('Download URL:', download.downloadUrl);

// Delete a document
await client.documents.delete('doc_id');
```

### Signers

**Important:** Signers require a `contactId`. You must create a contact first, then use the contact's ID to create a signer.

```typescript
// Create a contact first
const contact = await client.contacts.create({
  firstName: 'John',
  lastName: 'Smith',
  email: 'john@example.com',
});

// Add the contact as a signer to a document
const signer = await client.documents.createSigner('doc_id', {
  contactId: contact.id, // REQUIRED
  role: 'SIGNER', // SIGNER, ORGANIZER, or REVIEWER
  signingOrder: 2,
});

// Update a signer
await client.documents.updateSigner('doc_id', 'signer_id', {
  name: 'John A. Smith',
  signingOrder: 1,
  phone: '+46701234567',
});

// Remove a signer
await client.documents.deleteSigner('doc_id', 'signer_id');
```

### Fields

**Note:** Field CRUD operations are not included in the SDK as they require complex internal document structure knowledge (fieldMeta schemas) that are not suitable for external API usage. Document fields (signature fields, text fields, PDF pages, etc.) are managed through the sajn web interface.

### Contacts

```typescript
// List contacts
const contactsList = await client.contacts.list({
  page: 1,
  perPage: 10,
});

console.log(`Total pages: ${contactsList.totalPages}`);
console.log('Contacts:', contactsList.contacts);

// Get a contact
const contact = await client.contacts.get('contact_id');

// Create a contact
const newContact = await client.contacts.create({
  firstName: 'John',
  lastName: 'Doe',
  email: 'john@example.com',
  phone: '+46701234567',
  ssn: '19900101-1234', // Optional
  companyId: 'company_id', // Optional
  companyRole: 'CEO', // Optional
});

// Update a contact
await client.contacts.update('contact_id', {
  phone: '+46709876543',
  companyRole: 'CTO',
});

// Delete a contact
await client.contacts.delete('contact_id');
```

### Companies

```typescript
// List companies
const companies = await client.companies.list();

// Get a company
const company = await client.companies.get('company_id');

// Create a company
const newCompany = await client.companies.create({
  name: 'Acme Corp',
  orgNumber: '556677-8899',
  country: 'SE',
});
```

### Tags

```typescript
// List tags
const tagsList = await client.tags.list({ page: 1, perPage: 10 });

console.log(`Total pages: ${tagsList.totalPages}`);
console.log('Tags:', tagsList.tags);

// Create a tag
const tag = await client.tags.create({
  name: 'Urgent',
  color: '#FF0000',
  availableFor: ['DOCUMENT', 'TEMPLATE'], // DOCUMENT, TEMPLATE, or CONTACT
});

// Update a tag
await client.tags.update('tag_id', {
  name: 'High Priority',
  availableFor: ['DOCUMENT', 'TEMPLATE', 'CONTACT'],
});

// Add tag to document
await client.tags.addToDocument('doc_id', {
  tagId: 'tag_id',
});

// Remove tag from document
await client.tags.removeFromDocument('doc_id', 'tag_id');
```

### Custom Fields

```typescript
// List custom fields
const customFieldsList = await client.customFields.list({
  page: 1,
  perPage: 10,
  type: 'DOCUMENT', // Filter by DOCUMENT or CONTACT
});

console.log(`Total pages: ${customFieldsList.totalPages}`);
console.log('Custom fields:', customFieldsList.customFields);

// Create a custom field
const field = await client.customFields.create({
  name: 'Department',
  type: 'DOCUMENT', // DOCUMENT or CONTACT
  inputType: 'SELECT', // TEXT, TEXTAREA, NUMBER, DATE, SELECT, CHECKBOX, RADIO
  required: true,
  options: JSON.stringify(['Sales', 'Marketing', 'Engineering']),
});

// Update a custom field
await client.customFields.update('field_id', {
  options: JSON.stringify(['Sales', 'Marketing', 'Engineering', 'Support']),
});

// Delete a custom field
await client.customFields.delete('field_id');
```

### File Uploads

```typescript
import { readFileSync } from 'fs';

// Upload a file
const fileBuffer = readFileSync('./contract.pdf');
const uploadResult = await client.uploads.uploadFile(fileBuffer, 'contract.pdf');

console.log(uploadResult.key); // Use this key when creating documents
console.log(uploadResult.file.id);
```

## Webhooks

The SDK includes TypeScript types for webhook events:

```typescript
import type { WebhookRequest, WebhookEvent } from '@sajn/sdk';

// Example Express.js webhook handler
app.post('/webhook', (req, res) => {
  const sajnSecret = req.headers['x-sajn-secret'];

  if (sajnSecret !== process.env.WEBHOOK_SECRET) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  const webhook: WebhookRequest = req.body;

  switch (webhook.event) {
    case 'DOCUMENT_CREATED':
      // Handle document creation
      break;
    case 'DOCUMENT_SENT':
      // Handle document sent
      break;
    case 'DOCUMENT_SIGNED':
      // Handle document signed
      break;
    case 'DOCUMENT_COMPLETED':
      // Handle document completed
      break;
    case 'DOCUMENT_REJECTED':
      // Handle document rejected
      break;
  }

  res.status(200).json({ success: true });
});
```

### Available Webhook Events

- `DOCUMENT_CREATED` - Document was created
- `DOCUMENT_SENT` - Document was sent for signing
- `DOCUMENT_OPENED` - Document was opened by a signer
- `DOCUMENT_SIGNED` - Document was signed by a signer
- `DOCUMENT_COMPLETED` - All signers have signed the document
- `DOCUMENT_REJECTED` - Document was rejected by a signer

## Error Handling

The SDK throws typed errors for different HTTP status codes:

```typescript
import {
  sajnError,
  sajnAPIError,
  sajnAuthenticationError,
  sajnNotFoundError,
  sajnValidationError,
} from '@sajn/sdk';

try {
  await client.documents.get('invalid_id');
} catch (error) {
  if (error instanceof sajnAuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof sajnNotFoundError) {
    console.error('Document not found');
  } else if (error instanceof sajnValidationError) {
    console.error('Validation error:', error.body);
  } else if (error instanceof sajnAPIError) {
    console.error(`API error ${error.status}:`, error.statusText);
  }
}
```

## TypeScript Support

The SDK is written in TypeScript and includes comprehensive type definitions:

```typescript
import type {
  Document,
  DocumentType,
  DocumentStatus,
  Signer,
  SignerRole,
  SignatureType,
  Contact,
  Company,
  Tag,
  CustomField,
} from '@sajn/sdk';
```

## Examples

See the [examples](./examples) directory for more usage examples:

- [basic-usage.ts](./examples/basic-usage.ts) - Basic document creation and sending
- [upload-document.ts](./examples/upload-document.ts) - File upload example
- [manage-contacts.ts](./examples/manage-contacts.ts) - Contact management
- [webhook-handling.ts](./examples/webhook-handling.ts) - Webhook handling
- [tags-and-custom-fields.ts](./examples/tags-and-custom-fields.ts) - Tags and custom fields

## Requirements

- Node.js 20 or higher
- TypeScript 5.x (if using TypeScript)

## Development

```bash
# Install dependencies
npm install

# Build the SDK
npm run build

# Run type checking
npm run type-check

# Run tests
npm test
```

## License

MIT

## Support

- Documentation: [https://docs.sajn.se](https://docs.sajn.se)
- API Reference: [https://app.sajn.se/api/v1/openapi](https://app.sajn.se/api/v1/openapi)
- Issues: [GitHub Issues](https://github.com/sajn/sajn-sdk-ts/issues)