# SDK Node.js : générer des documents


Nous fournissons un [SDK Node.js](https://github.com/pdfmonkey/pdfmonkey-node) pour se connecter à PDFMonkey. Ce package est le moyen le plus rapide d’utiliser notre API depuis JavaScript ou TypeScript.

- npm : [https://www.npmjs.com/package/pdfmonkey](https://www.npmjs.com/package/pdfmonkey)
- GitHub : [https://github.com/pdfmonkey/pdfmonkey-node](https://github.com/pdfmonkey/pdfmonkey-node)

Le SDK n’a aucune dépendance runtime, fournit des builds ESM et CommonJS, et est entièrement typé. Il ne repose que sur l’API `fetch` globale et Web Crypto : il fonctionne donc sur Node.js 20+, Bun, Deno et les runtimes edge comme Cloudflare Workers ou Vercel Edge Functions.

## Installation

```bash
$ npm install pdfmonkey
```

Ou avec le gestionnaire de paquets de votre choix :

```bash
$ pnpm add pdfmonkey
$ yarn add pdfmonkey
$ bun add pdfmonkey
```

## Utilisation

### Configuration de l’authentification

#### Utilisation de la variable d’environnement par défaut

Le SDK recherche la variable d’environnement `PDFMONKEY_API_KEY`. Cette variable doit contenir votre clé API obtenue sur https://dashboard.pdfmonkey.io/account.

```bash
PDFMONKEY_API_KEY=j39ckj4…
```

Une fois la variable définie, vous pouvez créer un client sans argument :

```ts
import { PDFMonkey } from "pdfmonkey";

const client = new PDFMonkey();
```

#### Configuration manuelle des identifiants

Vous pouvez aussi passer la clé API explicitement, sous forme de chaîne ou dans un objet d’options :

```ts
const client = new PDFMonkey("j39ckj4…");

// ou, avec des options supplémentaires
const client = new PDFMonkey({
  apiKey: "j39ckj4…",
  timeout: 30_000,
});
```

#### Identifiants par tenant

Les identifiants sont liés à une instance de client. Si vous avez besoin d’identifiants par requête (par ex. en multi-tenant), créez un client par tenant :

```ts
const tenantClient = new PDFMonkey(tenant.pdfmonkeyApiKey);

const card = await tenantClient.documents.generateSync({
  document_template_id: "b13ebd75-…",
  payload: { name: "John Doe" },
});
```

### Documents

#### Génération synchrone

Si vous souhaitez attendre la fin de la génération d’un document avant de poursuivre votre flux de travail, utilisez `generateSync`. Cette méthode envoie une demande de génération et attend le succès ou l’échec avant de renvoyer une [DocumentCard](../api/documents.md#the-documentcard-object).

```ts
const card = await client.documents.generateSync({
  document_template_id: "b13ebd75-d290-409b-9cac-8f597ae3e785",
  payload: { name: "John Doe" },
});

card.status; // => 'success'
card.download_url; // => 'https://…'
```

La requête expire après 2 minutes par défaut. Vous pouvez modifier ce délai avec l’option `timeout` :

```ts
const card = await client.documents.generateSync(
  { document_template_id: "…", payload: { name: "John Doe" } },
  { timeout: 300_000 },
);
```

> [!WARNING]
> **L’URL de téléchargement est temporaire**
>
> L’URL de téléchargement d’un document n’est valide que **pendant 1 heure**. Passé ce délai, récupérez à nouveau la carte du document pour en obtenir une nouvelle :
> 
> ```ts
> const fresh = await client.documentCards.get(card.id);
> fresh.download_url; // => nouvelle URL, valide 1 heure
> ```


#### Génération asynchrone

PDFMonkey a été conçu avec un flux de travail asynchrone en tête. Il fournit des [webhooks](../generation-de-documents/webhooks.md) pour vous informer du succès ou de l’échec de la génération d’un document.

Pour tirer parti de ce comportement et continuer à travailler pendant la génération de votre document, créez le document avec `status: 'pending'` :

```ts
const document = await client.documents.create({
  document_template_id: "b13ebd75-d290-409b-9cac-8f597ae3e785",
  payload: { name: "John Doe" },
  status: "pending",
});

document.status; // => 'pending'
document.download_url; // => null
```

Si vous avez configuré une URL de webhook, elle sera appelée avec votre document une fois la génération terminée. Consultez la page [Webhooks](../generation-de-documents/webhooks.md) pour le format du payload et la vérification de signature.

Si vous préférez interroger l’API plutôt que d’utiliser des webhooks, `waitForGeneration` interroge l’API jusqu’à ce que le document atteigne un statut final :

```ts
const completed = await client.documents.waitForGeneration(document.id, {
  interval: 2000, // intervalle initial en ms (défaut : 2000)
  maxInterval: 10_000, // plafond du backoff exponentiel (défaut : 10 000)
  timeout: 120_000, // abandon après ce délai en ms (défaut : 120 000)
  signal: AbortSignal.timeout(60_000), // AbortSignal optionnel
});

completed.status; // => 'success'
completed.download_url; // => 'https://…'
```

Cette méthode lève une `PDFMonkeyError` si le document termine en `failure` ou `error`, ou si le délai est dépassé.

#### Documents brouillons

Vous pouvez créer un document brouillon qui ne sera pas mis en file d’attente pour la génération. C’est le comportement par défaut quand `status` est omis.

> [!TIP]
> C’est utile pour intégrer un aperçu via `preview_url` avant de déclencher la génération. C’est ce que nous faisons dans le tableau de bord PDFMonkey pour vous montrer un aperçu de votre document avant de le générer, en utilisant une `iframe`.


```ts
const draft = await client.documents.create({
  document_template_id: "b13ebd75-d290-409b-9cac-8f597ae3e785",
  payload: { name: "John Doe" },
});
draft.status; // => 'draft'
draft.preview_url; // => 'https://…'

// Quand vous êtes prêt, déclenchez la génération :
const pending = await client.documents.update(draft.id, { status: "pending" });
pending.status; // => 'pending'

// Puis attendez la fin :
const completed = await client.documents.waitForGeneration(draft.id);
completed.status; // => 'success'
```

#### Joindre des métadonnées

En plus du payload du document, vous pouvez ajouter des métadonnées lors de la génération. Passez la propriété `meta` à `generateSync`, `create` ou `update`. Elle accepte un objet ou une chaîne JSON déjà sérialisée :

```ts
const card = await client.documents.generateSync({
  document_template_id: templateId,
  payload: payload,
  meta: {
    _filename: "john-doe-contract.pdf", // définit le nom du fichier téléchargé
    _password: "secret123", // chiffre le PDF (AES-256)
    client_id: "123xxx123", // vos propres métadonnées
  },
});

card.meta;
// => '{"_filename":"john-doe-contract.pdf","_password":"secret123","client_id":"123xxx123"}'
```

Consultez [Nom de fichier personnalisé](../generation-de-documents/nom-de-fichier-personnalise.md) et [Protection par mot de passe](../generation-de-documents/protection-par-mot-de-passe.md) pour le détail des clés `_filename` et `_password`.

L’API renvoie `meta` sous forme de chaîne JSON. Utilisez `parseMeta` pour retrouver l’objet structuré que vous avez envoyé :

```ts
import { parseMeta } from "pdfmonkey";

const meta = parseMeta(card.meta); // DocumentMeta | null
meta?._filename; // => 'john-doe-contract.pdf'
meta?.client_id; // => '123xxx123'
```

#### Génération d’images

La génération d’images suit le même flux API que la génération de PDF. L’attribut `output_type` du template indique s’il produit une sortie `'pdf'` ou `'image'`. Les options spécifiques aux images sont passées via la propriété `meta` :

```ts
const card = await client.documents.generateSync({
  document_template_id: templateId,
  payload: payload,
  meta: {
    _type: "png", // webp (défaut), png ou jpg
    _width: 800, // pixels
    _height: 600, // pixels
    _quality: 80, // webp uniquement, défaut 100
  },
});
card.download_url; // => URL vers l’image générée
```

#### Télécharger le fichier

Plutôt que de récupérer `download_url` vous-même, laissez le SDK s’en charger. Passez un document, une carte de document ou un identifiant :

```ts
// Sous forme de Uint8Array
const bytes = await client.documents.download(card);
await fs.promises.writeFile("contract.pdf", bytes);

// Sous forme de ReadableStream — à rediriger directement vers le disque ou une réponse HTTP
const stream = await client.documents.downloadStream(card.id);
```

Ces deux méthodes lèvent une `PDFMonkeyError` si le document n’a pas encore de `download_url` : attendez d’abord la fin de la génération.

#### Mettre à jour un document

```ts
const updated = await client.documents.update(document.id, {
  payload: { name: "Jane Doe" },
  status: "pending",
});
```

#### Lister les documents

Le listage renvoie des cartes de documents allégées, il se trouve donc sur `client.documentCards` :

```ts
const page = await client.documentCards.list({ page: 1, status: "success" });

for (const card of page.data) {
  console.log(card.id, card.status);
}

page.currentPage; // => 1
page.totalPages; // => 5

// Naviguer entre les pages
if (page.hasNextPage()) {
  const next = await page.getNextPage();
}
```

Vous pouvez filtrer par `document_template_id`, `status`, `workspace_id` et `updated_since`.

#### Récupérer un document

> [!CAUTION]
> **Préférez `documentCards.get` à `documents.get`**
>
> Récupérer un document complet inclut son payload, ce qui peut représenter un volume important selon les données fournies. Nous recommandons **fortement** d’utiliser `client.documentCards.get` sauf si vous avez une raison spécifique de récupérer le document complet.


Pour récupérer uniquement la représentation légère (recommandé) :

```ts
const card = await client.documentCards.get(
  "76bebeb9-9eb1-481a-bc3c-faf43dc3ac81",
);
```

Pour récupérer le document complet, payload inclus :

```ts
const document = await client.documents.get(
  "76bebeb9-9eb1-481a-bc3c-faf43dc3ac81",
);
```

#### Supprimer un document

```ts
await client.documents.delete("76bebeb9-9eb1-481a-bc3c-faf43dc3ac81");
```

### Gestion des erreurs

Les erreurs API et les erreurs réseau lèvent des exceptions typées :

```ts
import {
  APIConnectionError,
  APIError,
  AuthenticationError,
  NotFoundError,
  RateLimitError,
  UnprocessableEntityError,
} from "pdfmonkey";

try {
  await client.documents.create({
    document_template_id: templateId,
    payload: data,
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Clé API invalide (401)
  } else if (error instanceof NotFoundError) {
    // Ressource introuvable (404)
  } else if (error instanceof UnprocessableEntityError) {
    error.body; // => { errors: { document_template_id: ["can't be blank"] } }
  } else if (error instanceof RateLimitError) {
    error.retryAfter; // => secondes à attendre, issues de l’en-tête Retry-After
  } else if (error instanceof APIError) {
    error.status; // => tout autre statut HTTP d’erreur
  } else if (error instanceof APIConnectionError) {
    error.cause; // => erreur réseau d’origine
  }
}
```

Toutes les classes d’exception héritent de `PDFMonkeyError`, ce qui permet de les intercepter de manière générale :

```ts
import { PDFMonkeyError } from "pdfmonkey";

try {
  await client.documents.generateSync({
    document_template_id: templateId,
    payload: data,
  });
} catch (error) {
  if (error instanceof PDFMonkeyError) {
    console.error(`Une erreur est survenue : ${error.message}`);
  }
}
```

Le client réessaie automatiquement les requêtes qui échouent avec un statut 408, 429 ou 5xx (2 tentatives par défaut, avec backoff exponentiel respectant l’en-tête `Retry-After`). Consultez la section [Configuration](#configuration) pour ajuster ce comportement.

### Templates

#### Récupérer un template

> [!CAUTION]
> **Les templates complets peuvent être volumineux**
>
> Récupérer un template complet inclut son body et ses paramètres, qui peuvent être volumineux. Utilisez `list` quand vous n’avez besoin que des métadonnées.


```ts
const template = await client.documentTemplates.get(
  "b13ebd75-d290-409b-9cac-8f597ae3e785",
);
template.identifier; // => 'my-invoice'
template.body; // => '<h1>Invoice</h1>…'  (version publiée)
template.body_draft; // => '<h1>Invoice v2</h1>…'  (version brouillon)
```

#### Créer un template

Lors de la création d’un template, écrivez dans les champs brouillons (`body_draft`, `scss_style_draft`, `sample_data_draft`, `settings_draft`) :

```ts
const template = await client.documentTemplates.create({
  identifier: "my-invoice",
  body_draft: "<h1>Invoice</h1>",
});
template.body_draft; // => '<h1>Invoice</h1>'
```

Ne renseignez pas `pdf_engine_draft_id` : l’API sélectionne automatiquement le moteur le plus récent. Consultez la section [Moteurs](#moteurs) si vous devez figer une version précise.

#### Mettre à jour un template

Comme `create`, `update` écrit dans les champs brouillons :

```ts
const updated = await client.documentTemplates.update(template.id, {
  body_draft: "<h1>Updated Invoice</h1>",
});
updated.body_draft; // => '<h1>Updated Invoice</h1>'
```

#### Lister les templates

```ts
const page = await client.documentTemplates.list({
  workspace_id: "f4ab650c-…",
});
```

#### Supprimer un template

```ts
await client.documentTemplates.delete("b13ebd75-…");
```

### Template Folders

```ts
// Lister les dossiers
const folders = await client.templateFolders.list();

// Créer un dossier
const folder = await client.templateFolders.create({ identifier: "invoices" });

// Récupérer un dossier
const folder = await client.templateFolders.get("folder-id");

// Mettre à jour un dossier
await client.templateFolders.update("folder-id", { identifier: "receipts" });

// Supprimer un dossier
await client.templateFolders.delete("folder-id");
```

Pour créer un template dans un dossier spécifique, passez le `template_folder_id` :

```ts
const folder = await client.templateFolders.create({ identifier: "invoices" });

const template = await client.documentTemplates.create({
  identifier: "monthly-invoice",
  body_draft: "<h1>Invoice</h1>",
  template_folder_id: folder.id,
});
```

### Snippets

Les snippets sont des composants HTML réutilisables qui peuvent être inclus dans les templates.

```ts
// Lister les snippets
const snippets = await client.snippets.list();

// Créer un snippet
const snippet = await client.snippets.create({
  identifier: "header",
  code: '<div class="header">…</div>',
  workspace_id: "f4ab650c-…",
});

// Récupérer un snippet
const snippet = await client.snippets.get("snippet-id");

// Mettre à jour un snippet
await client.snippets.update("snippet-id", {
  code: '<div class="header">Updated</div>',
});

// Supprimer un snippet
await client.snippets.delete("snippet-id");
```

### Workspaces

Les workspaces sont des ressources en lecture seule. Ils peuvent être listés et récupérés, mais pas créés, mis à jour ou supprimés via l’API.

```ts
// Lister les workspaces
const page = await client.workspaces.list();

for (const workspace of page.data) {
  console.log(workspace.identifier);
}

// Récupérer un workspace
const workspace = await client.workspaces.get("workspace-id");
workspace.identifier; // => 'my-app'
```

### Moteurs

Lister les moteurs de rendu PDF disponibles :

```ts
const engines = await client.pdfEngines.list();

for (const engine of engines) {
  console.log(
    `${engine.name} v${engine.version} (déprécié : ${engine.deprecated_on ?? "non"})`,
  );
}
```

La plupart des intégrations n’en ont pas besoin : l’API choisit le moteur le plus récent pour les nouveaux templates. Utilisez-le uniquement pour figer un template sur une version précise :

```ts
const engines = await client.pdfEngines.list();
const chromium = engines.find((engine) => engine.name === "chromium");

await client.documentTemplates.update(template.id, {
  pdf_engine_draft_id: chromium.id,
});
```

### Utilisateur courant

Récupérer les informations de l’utilisateur authentifié :

```ts
const user = await client.currentUser.get();

user.email; // => 'user@example.com'
user.current_plan; // => 'pro'
user.available_documents; // => 1000
```

### Pagination

Toutes les méthodes de liste renvoient un objet `Page<T>` avec une navigation intégrée :

```ts
const page = await client.documentCards.list({ page: 1 });

page.data; // => éléments de cette page
page.currentPage; // => 1
page.totalPages; // => 5

// Naviguer vers la page suivante/précédente
if (page.hasNextPage()) {
  const next = await page.getNextPage();
}
if (page.hasPreviousPage()) {
  const prev = await page.getPreviousPage();
}

// Aller à une page précise
const last = await page.getPage(page.totalPages);
```

> [!NOTE]
> `page.data` ne contient que les éléments **de la page courante**. Pour traiter toutes les pages, naviguez manuellement :


```ts
let page = await client.documentTemplates.list();

while (true) {
  for (const template of page.data) {
    process(template);
  }
  if (!page.hasNextPage()) break;
  page = await page.getNextPage();
}
```

## Configuration

Toutes les options sont facultatives. Passez-les au constructeur :

```ts
const client = new PDFMonkey({
  apiKey: "j39ckj4…", // ou définissez PDFMONKEY_API_KEY dans l’environnement
  baseURL: "https://api.pdfmonkey.io/api/v1", // défaut
  timeout: 30_000, // délai d’expiration des requêtes en ms (défaut : 30 s)
  maxRetries: 2, // nouvelles tentatives sur 408/429/5xx (défaut : 2)
  fetch: customFetch, // fournissez votre propre implémentation de fetch
  logger: console, // journalisation de débogage
  defaultHeaders: { "X-Source": "my-app" }, // envoyés avec chaque requête
  retryDelay: (attempt) => attempt * 250, // stratégie de backoff personnalisée
  hooks: {
    // intercepteurs de requête/réponse/erreur
    onRequest: (ctx) => {
      ctx.headers["X-Trace-Id"] = newTraceId();
    },
    onResponse: (ctx) => metrics.observe(ctx.durationMs, ctx.response.status),
  },
});
```

### Options par requête

Chaque méthode de ressource accepte un objet d’options en dernier argument pour surcharger `signal`, `timeout` et `maxRetries` le temps d’un appel :

```ts
await client.documents.create(
  { document_template_id: "b13ebd75-…", payload: { invoice: 1 } },
  {
    signal: AbortSignal.timeout(10_000),
    timeout: 60_000,
    maxRetries: 0,
  },
);
```


## FAQ

**Comment installer le SDK Node.js PDFMonkey ?**

Exécutez npm install pdfmonkey (ou l’équivalent avec pnpm, yarn ou bun). Définissez votre clé API via la variable d’environnement PDFMONKEY_API_KEY ou passez-la au constructeur PDFMonkey. Le package n’a aucune dépendance runtime et fournit des builds ESM et CommonJS.

**Quelle est la différence entre generateSync et create dans le SDK Node.js PDFMonkey ?**

generateSync envoie une seule requête et attend la fin de la génération du document, puis renvoie une DocumentCard avec une URL de téléchargement. create renvoie immédiatement : avec le statut pending, la génération est mise en file d’attente et vous gérez le résultat via des webhooks ou waitForGeneration ; avec le statut draft (par défaut), rien n’est généré tant que vous ne mettez pas à jour le statut.

**Peut-on utiliser le SDK Node.js PDFMonkey dans des runtimes edge comme Cloudflare Workers ou Vercel Edge ?**

Oui. Le SDK ne repose que sur l’API fetch globale et Web Crypto, il fonctionne donc sur Node.js 20+, Bun, Deno, Cloudflare Workers et Vercel Edge Functions. Vous pouvez fournir une implémentation fetch personnalisée si votre runtime en a besoin.

**Comment gérer les erreurs dans le SDK Node.js PDFMonkey ?**

Les erreurs API lèvent des sous-classes typées d’APIError comme AuthenticationError (401), NotFoundError (404), UnprocessableEntityError (422) et RateLimitError (429, avec une propriété retryAfter). Les échecs réseau lèvent APIConnectionError. Toutes les erreurs héritent de PDFMonkeyError pour une interception globale.
