> Lettrove docs 1.x · https://docs.lettrove.com/docs/api/node

# `@lettrove/node`

```bash
npm install @lettrove/node
```

Node.js 20 or newer, and edge runtimes (it uses only `fetch` and Web Crypto). It refuses to run in a
browser.

```ts
import { Lettrove, LettroveApiError, verifyWebhook } from '@lettrove/node';

const lettrove = new Lettrove({ secretKey: process.env.LETTROVE_SECRET_KEY! });
```

## `new Lettrove(options)`

| Option | Type | Default | |
|---|---|---|---|
| `secretKey` | `string` | required | `lt_sk_test_…` or `lt_sk_live_…`, from your environment. A publishable key, a cut-off key or a key with spaces is refused at once, with a message saying which. |
| `timeoutMs` | `number` | `15000` | How long to wait for an answer. |
| `fetch` | `typeof fetch` | the global | Your own `fetch`, for runtimes or tests that need one. |

`lettrove.environment` is `'test'` or `'live'`, read from the key.

## `tokens.create(input)` → `{ token, expiresAt }`

| Input | |
|---|---|
| `user.id` | Required. Your own id for the person (1–256 characters). |
| `origin` | Required. The page the editor opens on: the request's `Origin` header. |
| `ttl` | Optional. 60–900 seconds; default 900. |

[Minting tokens](/docs/server/tokens)

## `designs.list(input)` → `{ designs, nextCursor }`

| Input | |
|---|---|
| `user.id` | Required. |
| `limit` | 1–200, default 50. |
| `cursor` | `nextCursor` from the previous page. |

Each design: `{ id, name, mode, revision, createdAt, updatedAt }`, newest first.

## `designs.get(designId, { user })` → `Design`

The summary fields, plus `doc`: the design document.

## `designs.export(designId, input)`

| Input | |
|---|---|
| `user.id` | Required. |
| `merge` | `{ contact?, unsubscribeUrl?, preferencesUrl? }` — [Merge tags](/docs/editor/merge-tags) |
| `text` | `{ links?, images?, preheader? }` — how the plain text is written |
| `format` | `'html'` (default), `'pdf'`, `'png'` or `'zip'` |
| `fullPage` | For `'png'`: the whole design (default) or the first screen |

With `format` `'html'` or none, resolves with `ExportedDesign`: `exportId`, `designId`, `revision`,
`mode`, `html`, `chunks`, `text`, `subject`, `preheader`, `mergeTags`, `rendererVersion`, `bytes`,
`warnings`, `design`. With a file format, `ExportedFile`: `exportId`, `designId`, `revision`, `mode`,
`format`, `url`, `expiresAt`, `filename`, `bytes`, `warnings`, `design`. The types follow `format`.

## `render(input)` → `RenderedDesign`

| Input | |
|---|---|
| `doc` | Required. A design document you hold. |
| `designId` | Optional. Its id: a popup's handle and a form's event carry it. |
| `merge`, `text` | As for `designs.export`. |

The fields of `ExportedDesign` without `exportId`, `designId` and `revision`: nothing is recorded.

## `users.erase(userId)` → `void`

Erases one person and everything they made. Resolves when the erasure is under way.
[Erasure and privacy](/docs/data/erasure)

## `verifyWebhook(rawBody, signature, secret, options?)` → `WebhookEvent`

| Argument | |
|---|---|
| `rawBody` | The request body as a string, exactly as it arrived |
| `signature` | The `Lettrove-Signature` header |
| `secret` | The webhook's signing secret (`whsec_…`) |
| `options.toleranceSeconds` | How old a delivery may be; default 300 |

Throws `LettroveApiError` with code `signature_invalid` when the signature does not match, the body was
changed, or the delivery is too old. [Webhooks](/docs/server/webhooks)

## `LettroveApiError`

| Field | |
|---|---|
| `code` | A stable [error code](/docs/api/errors) |
| `message` | What happened |
| `status` | The HTTP status, or `0` when Lettrove could not be reached (`network_error`) |
| `requestId` | Quote it to support |
| `retryAfter` | Seconds to wait, when `rate_limited` |
| `docsUrl` | The code's row in [Error codes](/docs/api/errors), for your logs |
