npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sajn/sdk

v0.1.0

Published

Official TypeScript SDK for sajn API

Downloads

7

Readme

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

npm install @sajn/sdk

Quick Start

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: '[email protected]',
      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

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

// 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: '[email protected]',
      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.

// Create a contact first
const contact = await client.contacts.create({
  firstName: 'John',
  lastName: 'Smith',
  email: '[email protected]',
});

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

// 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: '[email protected]',
  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

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

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

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

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:

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:

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:

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

Examples

See the examples directory for more usage examples:

Requirements

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

Development

# Install dependencies
npm install

# Build the SDK
npm run build

# Run type checking
npm run type-check

# Run tests
npm test

License

MIT

Support