npm package: @subnoto/api-client
Description: TypeScript SDK for Subnoto Public API
Author: Subnoto
Latest version: 2.13.6

# Subnoto TypeScript SDK

TypeScript client for the Subnoto Public API with Oak tunnel encryption and HTTP signature authentication.

## Table of contents

- [Installation](#installation)
- [Usage](#usage)
- [Configuration](#configuration)
- [Upload Helper](#upload-helper)
- [Full example: upload and send](#full-example-upload-and-send)
- [Error handling and tunnel retry](#error-handling-and-tunnel-retry)
- [Documentation](#documentation)
- [Development](#development)

## Installation

**Prerequisites:** Node.js. The upload examples below use Node's `fs` module and are intended for Node.js.

```bash
pnpm add @subnoto/api-client
```

## Usage

```typescript
import { SubnotoClient } from "@subnoto/api-client";

const client = new SubnotoClient({
    accessKey: process.env.API_ACCESS_KEY,
    secretKey: process.env.API_SECRET_KEY
});

// Use openapi-fetch syntax
const { data, error } = await client.POST("/public/workspace/list", { body: {} });
const whoami = await client.POST("/public/utils/whoami", { body: {} });
const contacts = await client.POST("/public/contact/list", {
    body: { workspaceUuid: "..." }
});
```

## Configuration

Pass credentials via the config object; environment variables (e.g. `SUBNOTO_ACCESS_KEY`, `SUBNOTO_SECRET_KEY`)
are recommended for local development and CI.

| Option      | Type   | Required | Description                  |
| ----------- | ------ | -------- | ---------------------------- |
| `accessKey` | string | Yes      | API access key               |
| `secretKey` | string | Yes      | API secret key, hex-encoded  |

## Upload Helper

```typescript
import { SubnotoClient } from "@subnoto/api-client";
import { readFileSync } from "fs";

const client = new SubnotoClient({
    accessKey: process.env.API_ACCESS_KEY,
    secretKey: process.env.API_SECRET_KEY
});

// Read your PDF file
const fileBuffer = readFileSync("path/to/document.pdf");

// Upload document - handles encryption and S3 upload automatically
const { envelopeUuid, documentUuid } = await client.uploadDocument({
    workspaceUuid: "your-workspace-uuid",
    fileBuffer: fileBuffer,
    envelopeTitle: "My Document"
});

console.log("Document uploaded:", envelopeUuid, documentUuid);
```

## Full example: upload and send

The `uploadDocumentAndSend` helper runs the full flow in one call: upload the main document, optionally update
envelope options, add recipients, add attachments, add blocks (signatures, text, etc.), then send.

```typescript
import { SubnotoClient } from "@subnoto/api-client";
import { readFileSync } from "fs";

const client = new SubnotoClient({
    accessKey: process.env.API_ACCESS_KEY!,
    secretKey: process.env.API_SECRET_KEY!
});

const workspaceUuid = "your-workspace-uuid";
const mainPdf = readFileSync("path/to/contract.pdf");
const appendixPdf = readFileSync("path/to/appendix.pdf");

const { envelopeUuid, documentUuid } = await client.uploadDocumentAndSend({
    workspaceUuid,
    fileBuffer: mainPdf,
    envelopeTitle: "Contract with appendix",
    recipients: [
        {
            type: "manual",
            email: "signer@example.com",
            firstname: "Jane",
            lastname: "Doe"
        }
    ],
    options: {
        title: "Sales contract",
        tags: ["contract", "2024"],
        expirationPeriod: 30,
        documents: [{ title: "Main contract", mustReadAllPages: true }]
    },
    attachments: [{ documentTitle: "Appendix A", fileBuffer: appendixPdf }],
    blocks: [
        {
            page: "1",
            x: 100,
            y: 200,
            type: "signature",
            recipientEmail: "signer@example.com"
        },
        {
            page: "1",
            x: 100,
            y: 250,
            type: "text",
            text: "Signed by signer"
        }
    ]
});

console.log("Envelope sent:", envelopeUuid, documentUuid);
```

Steps run in order: upload → update envelope options (if `options`) → add recipients → add attachments (if
`attachments`) → add blocks (if `blocks`) → send. If any step fails, a `SubnotoError` is thrown.

## Error handling and tunnel retry

The SDK follows the [Subnoto error reference](https://subnoto.com/documentation/developers/reference/errors).
See that page for all error codes.
On tunnel 401 errors (`TUNNEL_SESSION_NOT_FOUND` or `TUNNEL_ERROR`), the client automatically invalidates the
tunnel session and **retries the request up to 3 times** with a new session.
You do not need to manage tunnel lifecycle when using `SubnotoClient`.

For custom handling you can use the exported helpers and codes:

```typescript
import { SubnotoClient, isTunnelError, SubnotoError } from "@subnoto/api-client";

const { data, error } = await client.POST("/public/utils/whoami", { body: {} });
if (error) {
    if (isTunnelError(error)) {
        // SDK already retried (up to 3 times); this is the result after retries
    }
    throw SubnotoError.fromApiError(error);
}
```

Use `getErrorCode(error)` if you need the code without throwing.
The thrown `SubnotoError` has `.message`, `.code`, and `.statusCode`.

## Documentation

The API documentation is [available here](https://subnoto.com/documentation/developers/openapi).

## Development

To regenerate types from the OpenAPI spec (contributors):

```bash
bazel run //libs/api-client-ts:update_generated
```