---
seo_title: SDK Node.js de conversion de fichiers, npm | EnConvert
meta_desc: Installez @enconvert/node-sdk depuis npm pour Node.js 18+ : méthodes de conversion typées et espace de noms client.v2 pour percevoir, extraire et surveiller le web.
keywords: sdk conversion de fichiers nodejs, client api conversion npm, convertir des fichiers en nodejs, sdk url vers pdf nodejs, heic vers webp nodejs, docx vers pdf node js, compresser une image nodejs, anything to markdown nodejs, anything to pdf nodejs, client api conversion typescript, enconvert node sdk, sdk scraping web nodejs, url vers markdown nodejs, extraction structurée nodejs, pipeline ingestion rag nodejs, surveillance de changements de site nodejs
---

# SDK Node.js de conversion de fichiers

`@enconvert/node-sdk` est le client JavaScript et TypeScript officiel de l'API EnConvert. Treize méthodes de conversion typées sont mappées 1:1 sur des endpoints REST comme `POST /v1/convert/url-to-pdf`, et un second espace de noms, `client.v2`, ajoute la web intelligence : percevoir une URL sous forme d'artefacts prêts pour un agent, découvrir les URL d'un site, lancer une recherche web, extraire des données structurées, ingérer un site en JSONL prêt pour le RAG, et surveiller les changements d'une page. Il cible Node.js 18+ sans aucune dépendance runtime, s'appuie sur les `fetch`, `FormData` et `node:stream` natifs, et récupère de façon transparente après un timeout du reverse proxy en interrogeant le statut de la tâche. Livré en builds ESM et CJS avec des déclarations TypeScript complètes.

<div class="alert alert-info">
<strong>npm :</strong> <code>@enconvert/node-sdk</code> · <strong>Source :</strong> <a href="https://github.com/enconvert/node-sdk">enconvert/node-sdk</a> · <strong>Node :</strong> 18+
</div>

---

## Installation

```bash
npm install @enconvert/node-sdk
```

```bash
pnpm add @enconvert/node-sdk
```

```bash
yarn add @enconvert/node-sdk
```

---

## Démarrage rapide

```ts
import { Enconvert } from "@enconvert/node-sdk";

const client = new Enconvert({ apiKey: process.env.ENCONVERT_API_KEY! });

// V1 : convertit une URL en PDF et l'écrit en flux sur le disque.
const result = await client.convertUrlToPdf("https://example.com", {
    saveTo: "page.pdf",
});
console.log(result.presignedUrl);

// V2 : lit une page comme votre agent devrait le faire, avec un score de qualité attaché.
const op = await client.v2.perceive("https://example.com", {
    outputs: ["markdown", "structured"],
});
console.log(op.outputs.markdown.url, op.renderQuality); // par ex. 0.93
```

Le SDK fonctionne dans tous les runtimes Node modernes (Node 18+, Bun, Deno via le spécificateur npm). Il est **exclusivement côté serveur** : n'embarquez donc jamais votre clé API privée dans une application navigateur.

---

## Ce que le client expose

Un seul client, deux surfaces. Les deux sont atteintes depuis la même instance `Enconvert` et partagent une seule clé API.

| Surface | Atteinte via | Ce qu'elle couvre |
|---------|-----------|----------------|
| Conversion de fichiers | `client.convertUrlToPdf(...)`, `client.convertImage(...)`, et ainsi de suite | Treize méthodes typées pour le rendu d'URL, la conversion d'images, la compression d'images, la conversion de documents, plus l'interrogation des tâches et des lots de site entier. Voir [Conversion de fichiers](#conversion-de-fichiers). |
| Web intelligence (V2) | `client.v2.perceive(...)`, `client.v2.distill(...)`, et ainsi de suite | Vingt-trois méthodes réparties sur six capacités : perceive, discover, lookup, distill, ingest, watch. Voir [Web intelligence (V2)](#web-intelligence-v2). |

Les endpoints V2 exigent une clé API privée (`sk_...`) ; les clés publiques sont rejetées. Voir [Authentification](/fr/docs/authentication.md) pour comprendre ce qui distingue les deux types de clés, et la [V1 et V2](/fr/docs/concepts/v1-and-v2.md) pour la surface REST derrière `client.v2`.

---

## Conversion de fichiers

La surface de conversion expose treize méthodes mappées 1:1 sur l'API REST :

| Méthode | Endpoint | Renvoie |
|--------|----------|---------|
| `convertUrlToPdf(url, options?)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `convertUrlToScreenshot(url, options?)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `convertUrlToMarkdown(url, options?)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `convertImage(file, options)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `convertDocument(file, options?)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `compressImage(file, options?)` | `POST /v1/convert/compress-image` | `ConversionResult` |
| `convertToMarkdown(file, options?)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `convertToPdf(file, options?)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `getJobStatus(jobId)` | `GET /v1/convert/status/{jobId}` | `JobStatus` |
| `convertWebsiteToPdf(url, options?)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `convertWebsiteToScreenshot(url, options?)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `getBatchStatus(batchId)` | `GET /v1/convert/batch/{batchId}` | `BatchStatus` |
| `waitForBatch(batchId, options?)` | `GET /v1/convert/batch/{batchId}` (interrogé) | `BatchStatus` |

Chaque méthode renvoie une promesse typée. Tous les champs d'options sont facultatifs sauf indication contraire. Les quatre dernières sont des utilitaires de lot pour un site entier : elles soumettent et interrogent des tâches asynchrones, et renvoient donc un `BatchSubmission` ou un `BatchStatus` plutôt qu'un `ConversionResult`.

---

### `convertUrlToPdf`

Rendez n'importe quelle URL publique en PDF.

```ts
const result = await client.convertUrlToPdf("https://example.com", {
    pdfOptions: { pageSize: "A4", orientation: "landscape" },
    singlePage: false,
    viewportWidth: 1440,
    saveTo: "report.pdf",
});
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `saveTo` | `string` | -- | Chemin local vers lequel écrire le PDF en flux. Les répertoires parents sont créés automatiquement. |
| `singlePage` | `boolean` | `true` | `true` produit une seule page continue. `false` pagine en utilisant `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions` | -- | Format de page, orientation, marges, échelle, niveaux de gris, en-tête et pied de page. Voir [Options PDF](#options-pdf). |
| `viewportWidth` | `number` | `1920` | Largeur de la fenêtre du navigateur, en pixels. |
| `viewportHeight` | `number` | `1080` | Hauteur de la fenêtre du navigateur, en pixels. |
| `loadMedia` | `boolean` | `true` | Attend les images et les vidéos avant la capture. |
| `enableScroll` | `boolean` | `true` | Fait défiler la page de haut en bas pour déclencher les chargements différés. |
| `outputFilename` | `string` | auto | Remplace le nom de fichier généré. `.pdf` est ajouté s'il manque. |

---

### `convertUrlToScreenshot`

Capturez un PNG pleine page de n'importe quelle URL.

```ts
const result = await client.convertUrlToScreenshot("https://example.com", {
    viewportWidth: 1440,
    saveTo: "screenshot.png",
});
```

Accepte les mêmes options de fenêtre, de médias, de défilement et de nom de fichier que `convertUrlToPdf` (sans `singlePage` ni `pdfOptions`).

---

### `convertUrlToMarkdown`

Extrayez du Markdown GitHub-Flavored propre depuis n'importe quelle URL. Le convertisseur supprime la navigation, les pieds de page, les publicités et les scripts, conserve le corps principal de l'article, et ajoute en tête un frontmatter YAML (titre, description, url, liens, images).

```ts
const result = await client.convertUrlToMarkdown("https://example.com/article", {
    saveTo: "article.md",
});
```

Pratique pour construire des pipelines RAG, importer du contenu tiers dans un CMS, ou générer des données d'entraînement. Si vous voulez un score de qualité de rendu à côté du Markdown, utilisez plutôt [`client.v2.perceive`](#perceive).

---

### `convertImage`

Convertissez entre `jpeg`, `png`, `svg`, `heic` et `webp`.

```ts
// Depuis un chemin
await client.convertImage("photo.heic", {
    outputFormat: "webp",
    saveTo: "photo.webp",
});

// Depuis des octets
import { readFile } from "node:fs/promises";
const buf = await readFile("photo.heic");

await client.convertImage(
    { data: buf, filename: "photo.heic" },
    { outputFormat: "webp", saveTo: "photo.webp" },
);

// Rastérise un SVG à une largeur fixe
await client.convertImage("logo.svg", { outputFormat: "png", width: 512, saveTo: "logo.png" });
```

Le format d'entrée est détecté à partir de l'extension du chemin ou du nom de fichier. Le format de sortie est obligatoire.

| Option | Type | Obligatoire | Description |
|--------|------|----------|-------------|
| `outputFormat` | `string` | Oui | Format cible : `jpeg`, `png`, `svg`, `heic` ou `webp` (et `jpeg` pour une entrée `.pdf`). Les alias `jpg`, `yml`, `htm` et `md` sont normalisés. Les paires non prises en charge lèvent une erreur avant l'envoi de la requête. |
| `saveTo` | `string` | -- | Chemin local vers lequel écrire le résultat en flux. |
| `outputFilename` | `string` | -- | Remplace le nom de fichier généré. |
| `width` | `number` | -- | Entrée SVG uniquement (`svg-to-png`, `svg-to-jpeg`, `svg-to-webp`), de 1 à 10000. Seule, cette option met à l'échelle proportionnellement, la hauteur étant déduite du ratio du SVG. |
| `height` | `number` | -- | Entrée SVG uniquement (`svg-to-png`, `svg-to-jpeg`, `svg-to-webp`), de 1 à 10000. Seule, cette option met à l'échelle proportionnellement, la largeur étant déduite du ratio du SVG. |

Définissez `width` et `height` ensemble pour fixer un canevas exact, ce qui peut modifier le ratio d'aspect. Omettez les deux et la sortie conserve la largeur, la hauteur ou le `viewBox` intrinsèque du SVG. Le nombre total de pixels de sortie est plafonné à 25 000 000. Aucune de ces deux options n'est acceptée par `svg-to-heic`, et le SDK lève une erreur avant l'envoi de la requête si vous les passez à une autre conversion.

---

### `convertDocument`

Convertissez des documents et des formats de données. Le `outputFormat` par défaut est `"pdf"`.

```ts
// docx vers pdf
await client.convertDocument("report.docx", { saveTo: "report.pdf" });

// json vers yaml
await client.convertDocument("data.json", {
    outputFormat: "yaml",
    saveTo: "data.yaml",
});

// markdown vers pdf avec une mise en page personnalisée
await client.convertDocument("README.md", {
    outputFormat: "pdf",
    pdfOptions: { pageSize: "A4", margins: { top: 20, bottom: 20, left: 25, right: 25 } },
    saveTo: "readme.pdf",
});
```

**Entrées prises en charge :** `doc`, `docx`, `xls`, `xlsx`, `ppt`, `pptx`, `html`, `htm`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers`, `markdown` (`.md`, `.markdown`), `csv`, `json`, `xml`, `yaml` (`.yaml`, `.yml`), `toml`.

EPUB n'a pas de paire de conversion de document dédiée. Faites plutôt passer les fichiers `.epub` par [`convertToPdf`](#converttopdf) ou [`convertToMarkdown`](#converttomarkdown).

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `outputFormat` | `string` | `"pdf"` | Format cible. |
| `saveTo` | `string` | -- | Chemin local vers lequel écrire le résultat en flux. |
| `outputFilename` | `string` | -- | Remplace le nom de fichier généré. |
| `pdfOptions` | `PdfOptions` | -- | Mise en page. Pris en compte uniquement lorsque la sortie est un PDF. |

---

### `compressImage`

Réduisez un PNG, un JPEG ou un WebP sans changer son format.

```ts
// Passe sans perte uniquement
const result = await client.compressImage("photo.jpg", { saveTo: "photo-small.jpg" });

// Vise un budget de 200 Ko
const capped = await client.compressImage("photo.jpg", {
    targetSizeKb: 200,
    saveTo: "photo-capped.jpg",
});

console.log(capped.fileSize);
```

**Entrées prises en charge :** `.png`, `.jpg`, `.jpeg`, `.webp`.

La sortie conserve le format et l'extension de l'entrée : il n'y a donc pas de format de sortie à choisir. La première étape est sans perte : les métadonnées sont supprimées, le profil ICC et l'orientation EXIF sont préservés, et le résultat n'est jamais plus volumineux que l'entrée. Définir `targetSizeKb` ajoute une seconde étape qui réduit la taille avec le ratio d'aspect verrouillé jusqu'à atteindre le budget. Cette cible est au mieux : un budget inatteignable renvoie le plus petit fichier obtenu au lieu d'une erreur, alors vérifiez `result.fileSize`. Les APNG et WebP animés sont rejetés avec un `400`, et le canevas décodé est plafonné à 40 000 000 pixels.

| Option | Type | Obligatoire | Description |
|--------|------|----------|-------------|
| `targetSizeKb` | `number` | -- | Budget de taille en Ko, entier, minimum `1`. Omettez-le pour n'exécuter que la passe sans perte. |
| `saveTo` | `string` | -- | Chemin local vers lequel écrire le résultat en flux. |
| `outputFilename` | `string` | -- | Remplace le nom de fichier généré. L'extension d'entrée est conservée. |

---

### `convertToMarkdown`

Convertissez en Markdown n'importe quel document, tableur, présentation, ebook, fichier web ou fichier texte pris en charge.

```ts
await client.convertToMarkdown("handbook.docx", {
    saveTo: "handbook.md",
});
```

**Entrées prises en charge (22) :** `.csv`, `.doc`, `.docx`, `.epub`, `.htm`, `.html`, `.markdown`, `.md`, `.mdown`, `.mkd`, `.odp`, `.ods`, `.odt`, `.pdf`, `.ppt`, `.pptx`, `.rtf`, `.text`, `.txt`, `.xhtml`, `.xls`, `.xlsx`.

La sortie est un unique fichier `.md` conscient des titres, conçu pour le découpage RAG : la hiérarchie de titres du document survit à la conversion, si bien qu'un découpeur sémantique peut segmenter sur les titres plutôt que sur un nombre arbitraire de caractères. Cet endpoint n'a aucune option PDF. Toute autre extension lève une erreur avant qu'une requête ne soit envoyée.

| Option | Type | Obligatoire | Description |
|--------|------|----------|-------------|
| `saveTo` | `string` | -- | Chemin local vers lequel écrire le Markdown en flux. |
| `outputFilename` | `string` | -- | Remplace le nom de fichier généré. |

Si vous voulez aussi que le découpage soit fait pour vous, confiez les mêmes fichiers à [`client.v2.ingestFiles`](#ingest).

---

### `convertToPdf`

Convertissez en PDF n'importe quel document, image, ebook, fichier web ou fichier texte pris en charge.

```ts
// docx vers pdf
await client.convertToPdf("contract.docx", { saveTo: "contract.pdf" });

// html vers pdf avec une géométrie de page complète
await client.convertToPdf("invoice.html", {
    pdfOptions: { pageSize: "A4", margins: { top: 15, bottom: 15, left: 15, right: 15 } },
    saveTo: "invoice.pdf",
});

// pdf vers pdf en niveaux de gris (passthrough)
await client.convertToPdf("scan.pdf", {
    pdfOptions: { grayscale: true },
    saveTo: "scan-gray.pdf",
});
```

**Entrées prises en charge (36) :** `.bmp`, `.csv`, `.doc`, `.docx`, `.epub`, `.gif`, `.heic`, `.heif`, `.htm`, `.html`, `.jpeg`, `.jpg`, `.markdown`, `.md`, `.mdown`, `.mkd`, `.numbers`, `.odp`, `.ods`, `.odt`, `.ots`, `.pages`, `.pdf`, `.png`, `.ppt`, `.pptx`, `.rtf`, `.svg`, `.text`, `.tif`, `.tiff`, `.txt`, `.webp`, `.xhtml`, `.xls`, `.xlsx`.

Une entrée `.pdf` est acceptée et transmise telle quelle : avec `pdfOptions: { grayscale: true }`, cette méthode fait donc aussi office de chemin de normalisation PDF. EPUB est également géré ici, puisqu'il n'a pas de paire de conversion de document dédiée. Toute autre extension lève une erreur avant qu'une requête ne soit envoyée.

<div class="alert alert-warning">
<strong>La géométrie dépend de l'entrée.</strong> La géométrie de page complète (format de page, largeur et hauteur de page, orientation, marges, échelle, en-tête, pied de page) est prise en compte pour les entrées HTML (<code>.html</code>, <code>.htm</code>, <code>.xhtml</code>), Markdown, texte brut, EPUB, image et SVG. Les entrées Office, ODF, iWork, RTF et CSV ainsi que le passthrough PDF ne prennent en charge que <code>grayscale</code>, et renvoient <code>400</code> si une option de géométrie explicite est définie. <code>grayscale</code> lui-même est pris en compte pour toutes les entrées.
</div>

| Option | Type | Obligatoire | Description |
|--------|------|----------|-------------|
| `saveTo` | `string` | -- | Chemin local vers lequel écrire le PDF en flux. |
| `outputFilename` | `string` | -- | Remplace le nom de fichier généré. `.pdf` est ajouté s'il manque. |
| `pdfOptions` | `PdfOptions` | -- | Mise en page. Voir la réserve ci-dessus pour savoir quelles entrées prennent en compte la géométrie. |

---

### `getJobStatus`

Interrogez le statut d'une tâche asynchrone ou récupérée.

```ts
const status = await client.getJobStatus("job_abc123");

if (status.status === "success") {
    console.log(status.presignedUrl);
} else if (status.status === "failed") {
    console.error(status.error);
}
```

Renvoie `{ status: "processing" | "success" | "failed", presignedUrl?, objectKey?, error? }`.

<div class="alert alert-info">
<strong>Vous n'avez généralement pas besoin d'appeler ceci directement.</strong> Le SDK interroge automatiquement lorsqu'une requête synchrone renvoie une 5xx. Voir <a href="#timeout-recovery">Récupération après timeout</a> ci-dessous.
</div>

---

### Utilitaires de lot pour un site entier

`convertWebsiteToPdf` et `convertWebsiteToScreenshot` découvrent les pages d'un site, les mettent toutes en file d'attente, et regroupent les sorties dans un seul ZIP. Les deux renvoient immédiatement un `BatchSubmission` ; interrogez avec `getBatchStatus` ou bloquez avec `waitForBatch`. Les options partagées sont `crawlMode` (`"auto"`, `"sitemap"` ou `"full"`), `includePatterns`, `excludePatterns`, `notificationEmail` et `callbackUrl` ; `convertWebsiteToPdf` ajoute `singlePage` et `pdfOptions`.

```ts
const batch = await client.convertWebsiteToPdf("https://example.com", {
    crawlMode: "sitemap",
    excludePatterns: ["/tag/"],
});

const done = await client.waitForBatch(batch.batchId, { saveTo: "site.zip" });
console.log(done.status, done.completed, done.failed, done.zipDownloadUrl);
```

`waitForBatch` accepte `intervalMs` (défaut `5_000`), `timeoutMs` (défaut `1_800_000`, soit trente minutes) et `saveTo`. Il lève `APIError(504, ...)` si l'échéance est dépassée. Voir la [vue d'ensemble des endpoints](/fr/docs/endpoints.md) pour la surface REST.

---

## Web intelligence (V2)

Tout ce qui se trouve sous `client.v2` renvoie des données auxquelles un agent peut se fier, parce que chaque rendu V2 porte un score `renderQuality` compris entre 0.0 et 1.0. Une page bloquée, un défi anti-bot, un mur de connexion, une page d'erreur HTTP, un soft 404 ou une coquille vide d'application monopage revient avec un score bas plus des `deductions` et des `warnings` nommés : elle est donc signalée au lieu d'être prise pour du vrai contenu. Le contenu est tout de même renvoyé ; c'est vous qui décidez quoi en faire. Un score inférieur à environ 0,40 signifie que le rendu n'a réussi en aucun sens utile.

Vingt-trois méthodes réparties sur six capacités :

| Méthode | Endpoint | Renvoie |
|--------|----------|---------|
| `v2.perceive(url, options?)` | `POST /v2/perceive` | `PerceiveResult` |
| `v2.perceiveDirect(url, options?)` | `POST /v2/perceive` | `PerceiveDirectResult` |
| `v2.getPerceiveOperation(operationId)` | `GET /v2/perceive/{operationId}` | `PerceiveResult` |
| `v2.downloadPerceiveArtifact(operationId, output?)` | `GET /v2/perceive/{operationId}` | `PerceiveDirectResult` |
| `v2.perceiveBatch(urls, options?)` | `POST /v2/perceive/batch` | `PerceiveBatchResult` |
| `v2.getPerceiveBatch(jobId)` | `GET /v2/perceive/batch/{jobId}` | `PerceiveBatchResult` |
| `v2.discover(url, options?)` | `POST /v2/discover` | `DiscoverResult` |
| `v2.lookup(query, options?)` | `POST /v2/lookup` | `LookupResult` |
| `v2.distill(options)` | `POST /v2/distill` | `DistillResult` |
| `v2.ingest(options)` | `POST /v2/ingest` | `IngestJob` |
| `v2.ingestFiles(files, options?)` | `POST /v2/ingest/files` | `IngestJob` |
| `v2.listIngestJobs(options?)` | `GET /v2/ingest` | `IngestJobList` |
| `v2.getIngestJob(jobId)` | `GET /v2/ingest/{jobId}` | `IngestJob` |
| `v2.cancelIngestJob(jobId)` | `DELETE /v2/ingest/{jobId}` | `IngestJob` |
| `v2.retryIngestWebhook(jobId)` | `POST /v2/ingest/{jobId}/retry-webhook` | `WebhookRetryResult` |
| `v2.getWebhookSecret()` | `GET /v2/ingest/webhook-secret` | `WebhookSecret` |
| `v2.rotateWebhookSecret()` | `POST /v2/ingest/webhook-secret/rotate` | `WebhookSecret` |
| `v2.createWatcher(url, options?)` | `POST /v2/watch` | `Watcher` |
| `v2.listWatchers(options?)` | `GET /v2/watch` | `WatcherList` |
| `v2.getWatcher(watcherId)` | `GET /v2/watch/{watcherId}` | `Watcher` |
| `v2.getWatcherSnapshots(watcherId, options?)` | `GET /v2/watch/{watcherId}/snapshots` | `WatcherSnapshotList` |
| `v2.updateWatcher(watcherId, updates)` | `PATCH /v2/watch/{watcherId}` | `Watcher` |
| `v2.deleteWatcher(watcherId)` | `DELETE /v2/watch/{watcherId}` | `Watcher` |

Les options sont en camelCase sur la surface du SDK et sérialisées vers le format de transport snake_case de l'API ; les réponses sont retraduites en camelCase. Vos propres charges utiles (schémas d'extraction, données extraites, champs suivis, entrées de diff) passent intactes.

---

### Perceive

Rendez une URL sous forme des artefacts que vous demandez : Markdown, HTML nettoyé ou brut, une capture d'écran de la fenêtre ou de la page entière, un PDF, une liste de liens, une liste d'images, ou des données structurées. `perceive` est synchrone et renvoie l'opération terminée avec des URL d'artefacts signées pour 15 minutes. Référence complète : [Perceive](/fr/docs/endpoints/perceive.md).

```ts
const op = await client.v2.perceive("https://example.com", {
    outputs: ["markdown", "screenshot", "structured"],
    extract: ["tables", "metadata"],
    onlyMainContent: true,
    waitFor: "css:.article-body",
    viewport: { width: 1440, height: 900 },
});

console.log(op.renderQuality);        // de 0.0 à 1.0
console.log(op.deductions);           // par ex. { http_error: 0.7 }
console.log(op.outputs.markdown.url); // URL signée, 15 minutes
console.log(op.structured);

if ((op.renderQuality ?? 0) < 0.4) {
    console.warn("Bad read, do not feed this to the model:", op.warnings);
}

// Resignez les URL d'artefacts plus tard, sans refaire le rendu :
const again = await client.v2.getPerceiveOperation(op.operationId);
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `outputs` | `PerceiveOutputName[]` | `["markdown", "structured"]` | Au choix parmi `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `extract` | `PerceiveExtractName[]` | -- | Cibles heuristiques : `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `onlyMainContent` | `boolean` | `true` | Supprime la navigation, l'en-tête, le pied de page et les bandeaux de cookies de la sortie Markdown, derrière un garde-fou de fidélité. `false` renvoie la page entière intacte. |
| `schema` | `Record<string, unknown>` | -- | Schéma JSON pour l'extraction structurée au niveau LLM. |
| `waitFor` | `string` | -- | Sélecteur CSS (éventuellement `"css:..."`) ou `"js:<expr>"` à attendre avant la capture. |
| `waitTimeoutMs` | `number` | `30000` | De 0 à 60000. |
| `jsCode` | `string` | -- | JavaScript exécuté après la navigation. 20000 caractères maximum. |
| `viewport` | `{ width?, height? }` | `1920 x 1080` | Largeur de 320 à 3840, hauteur de 240 à 2160. |
| `headers` | `Record<string, string>` | -- | En-têtes de requête supplémentaires. |
| `cookies` | `BrowserCookie[]` | -- | Cookies injectés avant le rendu. Chacun a besoin de `name`, `value`, et soit `domain`, soit `url`. |
| `auth` | `{ username, password }` | -- | Authentification HTTP Basic. |
| `cacheMode` | `"enabled" \| "bypass" \| "refresh"` | `"enabled"` | Cache d'une heure. `bypass` l'ignore, `refresh` force un nouveau rendu. |
| `pdfOptions` | `PdfOptions` | -- | N'a de sens que lorsque `outputs` inclut `"pdf"`. Voir [Options PDF](#options-pdf). |
| `blockResources` | `PerceiveResourceType[]` | -- | Types de ressources que le navigateur ne doit pas charger, par exemple `["image", "font", "media"]`. |
| `respectRobots` | `boolean` | -- | Respecte les règles robots du site. |
| `mobile` | `boolean` | -- | Effectue le rendu avec un profil mobile. |
| `directDownload` | `boolean` | -- | `perceive` uniquement. Préférez `perceiveDirect`, qui le définit pour vous. |

<div class="alert alert-warning">
<strong>Trois options sont déclarées mais pas encore actives.</strong> <code>proxyUrl</code>, <code>geolocation</code> et <code>actionChain</code> sont typées sur <code>PerceiveOptions</code> mais actuellement rejetées côté serveur avec un <code>422</code>. Elles sont réservées, pas utilisables.
</div>

**Téléchargement direct.** `perceiveDirect` évite l'aller-retour par URL signée : le corps de la réponse HTTP contient les octets de l'artefact et les métadonnées voyagent dans les en-têtes. Cette méthode exige exactement une sortie produisant un artefact, et le SDK lève une erreur localement avant l'envoi si ce n'est pas le cas (`"structured"` peut accompagner la demande, mais il reste en ligne côté serveur et n'est pas renvoyé).

```ts
import { writeFile } from "node:fs/promises";

const direct = await client.v2.perceiveDirect("https://example.com", { outputs: ["markdown"] });
console.log(direct.contentType, direct.renderQuality, direct.sourceStatusCode);
await writeFile(direct.filename ?? "page.md", direct.content);

// Retélécharge un artefact stocké lors d'une opération antérieure, en octets bruts.
// `output` peut être omis lorsque l'opération n'a produit qu'un seul artefact.
const raw = await client.v2.downloadPerceiveArtifact(op.operationId, "markdown");
```

`downloadPerceiveArtifact` renvoie `410` une fois l'artefact stocké expiré, et `400` (en listant les sorties disponibles) si l'opération a produit plus d'un artefact et que vous avez omis `output`.

**Lots.** `perceiveBatch` prend jusqu'à 1000 URL avec un seul bloc d'options partagé. Les petits lots s'exécutent en ligne et reviennent terminés ; les plus gros renvoient le statut `"queued"`, alors interrogez `getPerceiveBatch` avec le `jobId` renvoyé.

```ts
const batch = await client.v2.perceiveBatch(["https://example.com/a", "https://example.com/b"], {
    outputs: ["markdown"],
    outputMode: "zip",
});

let job = await client.v2.getPerceiveBatch(batch.jobId);
while (job.status === "queued" || job.status === "processing") {
    await new Promise((r) => setTimeout(r, 3000));
    job = await client.v2.getPerceiveBatch(batch.jobId);
}
console.log(job.completed, job.failed, job.zip?.url);
```

`outputMode` vaut `"manifest"` (défaut, une entrée par URL dans `items`) ou `"zip"` (tous les artefacts réussis regroupés une fois la tâche terminée). L'endpoint de lot rejette `directDownload` ; utilisez `outputMode: "zip"` à la place.

---

### Discover

Listez les URL d'un site sans rien rendre. Aucun navigateur n'intervient : c'est donc rapide et peu coûteux comparé au fait de percevoir chaque page. Référence complète : [Discover](/fr/docs/coming-soon/discover.md).

```ts
const found = await client.v2.discover("https://example.com", {
    mode: "hybrid",
    maxUrls: 200,
    maxDepth: 3,
    excludePatterns: ["/tag/", "/author/"],
    sameDomainOnly: true,
});

console.log(found.total, found.truncated, found.sources); // par ex. { sitemap: 42, crawl: 30 }
for (const url of found.urls) console.log(url);
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `mode` | `"sitemap" \| "crawl" \| "hybrid"` | `"hybrid"` | Sitemap seul, crawl HTTP seul, ou les deux. |
| `maxUrls` | `number` | `100` | De 1 à 1000. `truncated` vaut `true` lorsqu'il existait plus d'URL que ce plafond ne le permettait. |
| `maxDepth` | `number` | `2` | De 1 à 5. Profondeur de crawl depuis l'URL de départ. |
| `includePatterns` | `string[]` | -- | Liste d'autorisation par regex, 50 entrées maximum. |
| `excludePatterns` | `string[]` | -- | Liste de refus par regex appliquée après `includePatterns`, 50 entrées maximum. |
| `sameDomainOnly` | `boolean` | `true` | Maintient le crawl sur le domaine de départ. |
| `respectRobots` | `boolean` | -- | Respecte les règles robots du site. `robotsRespected` dans le résultat indique ce qui s'est passé. |

---

### Lookup

Lancez une recherche web catégorisée, et faites éventuellement percevoir automatiquement les meilleurs résultats pour que chaque occurrence porte son propre `PerceiveResult` complet en ligne. Référence complète : [Lookup](/fr/docs/coming-soon/lookup.md).

```ts
const search = await client.v2.lookup("best static site generators", {
    category: "web",
    numResults: 10,
    country: "us",
    locale: "en",
    timeFilter: "month",
    perceiveTop: 3,
});

for (const hit of search.results) {
    console.log(hit.position, hit.title, hit.url);
    if (hit.perceive) {
        console.log("  quality:", hit.perceive.renderQuality);
        console.log("  markdown:", hit.perceive.outputs.markdown?.url);
    }
}
console.log(search.answerBox, search.knowledgeGraph);
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `category` | `"web" \| "news" \| "images" \| "scholar" \| "patents" \| "maps"` | `"web"` | Verticale de recherche. |
| `country` | `string` | -- | Code pays Google `gl`, par exemple `"us"` ou `"in"`. |
| `locale` | `string` | -- | Langue d'interface Google `hl`, par exemple `"en"`. |
| `timeFilter` | `"hour" \| "day" \| "week" \| "month" \| "year"` | -- | Fenêtre de fraîcheur. |
| `numResults` | `number` | `10` | De 1 à 100. |
| `page` | `number` | `1` | De 1 à 10. |
| `location` | `string` | -- | Localisation en texte libre, par exemple `"Austin, Texas"`. |
| `autocorrect` | `boolean` | `true` | Laisse le fournisseur corriger les fautes de frappe évidentes. |
| `perceiveTop` | `number` | `0` | De 0 à 10. Rend automatiquement les N premières URL de résultats ; chacune est un rendu navigateur complet. |

`perceiveTop` dans le résultat indique combien de résultats ont réellement été perçus, ce qui peut être inférieur à ce que vous avez demandé, et `perceiveOperationIds` vous donne les identifiants d'opérations pour resigner plus tard.

---

### Distill

Extraction structurée pilotée par schéma. Donnez-lui une forme et un ensemble d'URL (ou un site à découvrir d'abord) et il renvoie des enregistrements correspondant à cette forme. Un `cssSchema` facultatif répond à tout ce qu'il peut à partir de sélecteurs avant toute escalade vers le niveau LLM. Référence complète : [Distill](/fr/docs/coming-soon/distill.md).

```ts
const extraction = await client.v2.distill({
    urls: ["https://example.com/pricing"],
    schema: { plans: "list of plan names with monthly prices" },
    cssSchema: {
        baseSelector: ".plan-card",
        fields: [
            { name: "name", type: "text", selector: "h3" },
            { name: "price", type: "text", selector: ".price" },
            { name: "url", type: "attribute", selector: "a", attribute: "href" },
        ],
    },
});

const first = extraction.results[0];
console.log(first.data);
console.log(first.extractionTier);  // "css" | "llm" | "mixed" | "none"
console.log(first.fieldsFromCss, first.fieldsFromLlm, first.renderQuality);
```

Passez exactement l'un de `urls` ou `discoverFrom` ; le SDK lève une erreur localement si vous passez les deux ou aucun, et il lève aussi une erreur si `schema` est absent ou n'est pas un objet.

```ts
// Découvre d'abord un site, puis extrait de chaque page trouvée.
await client.v2.distill({
    discoverFrom: { url: "https://example.com", mode: "sitemap", maxPages: 10 },
    schema: { title: "page title", summary: "one-line summary" },
});
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `urls` | `string[]` | -- | URL explicites à traiter, 50 maximum. Mutuellement exclusif avec `discoverFrom`. |
| `discoverFrom` | `{ url, mode?, maxPages? }` | -- | Découvre d'abord, puis extrait. `maxPages` va de 1 à 50, vaut 10 par défaut, et plafonne à la fois la découverte et l'extraction. |
| `schema` | `Record<string, unknown>` | obligatoire | Un objet JSON-Schema (`{ type: "object", properties: {...} }`) ou une table plate `{ champ: description }`. |
| `cssSchema` | `CssSchema` | -- | Passe de sélecteurs gratuite exécutée avant toute escalade LLM. |
| `waitFor` | `string` | -- | Sélecteur CSS ou `"js:<expr>"` à attendre. |
| `waitTimeoutMs` | `number` | `30000` | De 0 à 60000. |
| `headers` | `Record<string, string>` | -- | En-têtes de requête supplémentaires. |
| `cookies` | `BrowserCookie[]` | -- | Cookies injectés avant le rendu. |
| `respectRobots` | `boolean` | -- | Respecte les règles robots du site. |

Un `CssSchema` possède un `baseSelector` (le conteneur répété, un enregistrement par correspondance), une liste `fields`, un `name` facultatif, et un `targetField` facultatif nommant la propriété du schéma de sortie que les enregistrements remplissent. Chaque champ s'écrit `{ name, type, selector?, attribute?, pattern?, default?, transform?, fields? }` où `type` vaut `text`, `attribute`, `html`, `regex`, `nested`, `list` ou `nested_list`. `attribute` est obligatoire pour les champs `attribute`, `pattern` pour les champs `regex`, et un tableau `fields` non vide pour les types imbriqués (profondeur maximale de 5).

---

### Ingest

Transformez un site, ou un lot de documents téléversés, en JSONL découpé et prêt pour le RAG. Ingest est toujours asynchrone : les deux points d'entrée renvoient un `IngestJob` en file d'attente, et vous l'interrogez ou vous configurez un webhook. Référence complète : [Ingest](/fr/docs/endpoints/ingest.md).

```ts
// Depuis un site.
const job = await client.v2.ingest({
    mode: "sitemap",
    url: "https://docs.example.com",
    maxPages: 100,
    chunk: { maxWords: 512, sentenceOverlap: 1 },
    webhookUrl: "https://my.app/hooks/enconvert",
});

// Depuis des fichiers téléversés : PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD,
// ainsi que les documents bureautiques anciens ou ODF.
const fileJob = await client.v2.ingestFiles(["handbook.pdf", "notes.docx"], {
    chunk: { maxWords: 512, sentenceOverlap: 1 },
});

// Interrogez l'un ou l'autre de la même façon. États non terminaux : queued, discovering, processing.
let status = await client.v2.getIngestJob(job.jobId);
while (!["completed", "failed", "canceled"].includes(status.status)) {
    await new Promise((r) => setTimeout(r, 5000));
    status = await client.v2.getIngestJob(job.jobId);
}
console.log(status.totalChunks, status.outputUrl, status.errorMessage);

const page = await client.v2.listIngestJobs({ limit: 20, skip: 0 });
console.log(page.jobs.length, page.hasMore);

await client.v2.cancelIngestJob(job.jobId); // idempotent
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `mode` | `"urls" \| "sitemap" \| "crawl"` | `"urls"` | `"urls"` a besoin de `urls` et rejette `url`. `"sitemap"` et `"crawl"` ont besoin d'une `url` de départ et rejettent `urls`. Les deux règles sont vérifiées localement avant la requête. L'union `IngestMode` contient aussi `"files"`, que `ingestFiles` renseigne sur sa tâche ; ne le passez pas ici. |
| `url` | `string` | -- | URL de départ pour `sitemap` et `crawl`. |
| `urls` | `string[]` | -- | URL explicites pour le mode `"urls"`, 1000 maximum. |
| `maxPages` | `number` | `50` | Plafond de découverte pour `sitemap` et `crawl`, de 1 à 1000. |
| `maxDepth` | `number` | `2` | De 1 à 5. |
| `sameDomainOnly` | `boolean` | `true` | Maintient le crawl sur le domaine de départ. |
| `includePatterns` / `excludePatterns` | `string[]` | -- | Liste d'autorisation et liste de refus par regex. |
| `respectRobots` | `boolean` | -- | Respecte les règles robots du site. |
| `waitFor` / `waitTimeoutMs` | `string` / `number` | -- / `30000` | Attente de rendu par page. |
| `chunk` | `{ maxWords?, sentenceOverlap? }` | `512` / `1` | `maxWords` va de 32 à 4000, `sentenceOverlap` de 0 à 10. |
| `webhookUrl` | `string` | -- | Webhook de fin, signé en HMAC. |

`ingestFiles` accepte un `FileInput[]`, c'est-à-dire des chaînes de chemin, des `Uint8Array` / `Buffer`, ou des objets `{ data, filename, contentType? }`, dans n'importe quel mélange. Il ne prend que `chunk` et `webhookUrl`, et lève une erreur localement sur une liste vide.

**Signature des webhooks.** Les webhooks de fin sont signés en HMAC. Récupérez le secret (il est créé au premier appel) pour vérifier les livraisons, faites-le tourner quand vous en avez besoin, et relivrez un webhook que votre endpoint a manqué.

```ts
const secret = await client.v2.getWebhookSecret();
console.log(secret.signatureHeader, secret.timestampHeader);
console.log(secret.signatureScheme, secret.replayToleranceSeconds);

// La rotation invalide immédiatement les signatures faites avec le secret précédent.
const rotated = await client.v2.rotateWebhookSecret();

// Relivre le webhook d'une tâche terminée.
const retry = await client.v2.retryIngestWebhook(job.jobId);
console.log(retry.delivered, retry.attempts, retry.statusCode, retry.detail);
```

`retryIngestWebhook` renvoie `409` lorsque la tâche n'est pas terminée et `400` lorsque la tâche n'a aucun webhook configuré.

---

### Watch

Créez un observateur qui refait le rendu d'une URL à une cadence fixe et vous prévient lorsque la page change, par e-mail, par webhook, ou les deux. Référence complète : [Watch](/fr/docs/coming-soon/watch.md).

```ts
const watcher = await client.v2.createWatcher("https://example.com/pricing", {
    frequencyMinutes: 60,
    diffMode: "auto",
    webhookUrl: "https://my.app/hooks/changes",
    notifyEmail: true,
});
console.log(watcher.watcherId, watcher.nextCheckAt);

const list = await client.v2.listWatchers({ limit: 20 });
const one = await client.v2.getWatcher(watcher.watcherId);

const history = await client.v2.getWatcherSnapshots(watcher.watcherId, { limit: 10 });
for (const snap of history.snapshots) {
    console.log(snap.checkedAt, snap.hasChanges, snap.similarity, snap.changeCount);
}

await client.v2.updateWatcher(watcher.watcherId, { status: "paused" });
await client.v2.updateWatcher(watcher.watcherId, { webhookUrl: "" }); // efface le webhook
await client.v2.deleteWatcher(watcher.watcherId);                     // suppression douce, idempotente
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `frequencyMinutes` | `number` | `60` | Minutes entre deux vérifications, de 60 à 43200. Le plancher horaire est strict. |
| `diffMode` | `"auto" \| "text" \| "structured" \| "tables" \| "metadata"` | `"auto"` | `"auto"` laisse le moteur de diff choisir selon le type de contenu. |
| `trackFields` | `Record<string, unknown>` | -- | Sous-ensemble de champs ou de sélecteurs pour restreindre le diff. |
| `webhookUrl` | `string` | -- | Webhook de notification de changement, signé en HMAC. |
| `notifyEmail` | `boolean` | `true` | Envoie un e-mail au propriétaire du projet en cas de changement. |

`updateWatcher` prend les mêmes champs plus `status` (`"active"` ou `"paused"`) et en exige au moins un ; le SDK lève une erreur localement sur une mise à jour vide. Passer `webhookUrl: ""` efface explicitement le webhook. La suppression est une suppression douce : `deleteWatcher` renvoie l'observateur marqué supprimé avec le statut `"deleted"`, et un observateur supprimé répond `404` depuis `getWatcher`.

Chaque instantané porte `checkedAt`, `hasChanges`, `similarity` (de 0.0 à 1.0 par rapport à la capture précédente), `renderQuality`, `changeCount`, et un tableau `changes`.

<div class="alert alert-warning">
<strong>Les diffs d'instantanés contiennent du contenu de page non fiable.</strong> Les entrées de <code>snapshot.changes</code> viennent directement de la page surveillée. Échappez-les avant de les afficher en HTML ou de les écrire dans un visualiseur de logs.
</div>

---

## Options PDF

Passées via le champ `pdfOptions` sur `convertUrlToPdf`, `convertDocument`, `convertToPdf`, `convertWebsiteToPdf` et `client.v2.perceive` (lorsque `outputs` inclut `"pdf"`).

```ts
const result = await client.convertUrlToPdf("https://example.com", {
    pdfOptions: {
        pageSize: "A4",
        orientation: "landscape",
        margins: { top: 10, bottom: 10, left: 15, right: 15 },
        scale: 0.9,
        grayscale: false,
    },
    saveTo: "report.pdf",
});
```

| Champ | Type | Description |
|-------|------|-------------|
| `pageSize` | `string` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"`, etc. |
| `pageWidth` / `pageHeight` | `number` | Géométrie de page explicite, en remplacement de `pageSize`. |
| `orientation` | `"portrait" \| "landscape"` | Portrait par défaut. |
| `margins` | `{ top, bottom, left, right }` (mm) | Les quatre sont facultatifs. |
| `scale` | `number` | Échelle de rendu, par ex. `0.9` pour 90 %. |
| `grayscale` | `boolean` | Post-traite le PDF via Ghostscript pour le passer en niveaux de gris. |
| `header` | `PdfHeaderFooter` | `{ content?, height? }`. `content` est plafonné à 2000 caractères. |
| `footer` | `PdfHeaderFooter` | Même forme que `header`. |

Chaque paramètre est décrit en détail dans [Tâches synchrones et asynchrones](/fr/docs/concepts/sync-and-async.md).

## Gestion des erreurs

Les erreurs sont des classes d'exception typées que vous pouvez filtrer avec `instanceof`. La même hiérarchie couvre à la fois les méthodes de conversion et `client.v2`.

```ts
import {
    Enconvert,
    APIError,
    AuthenticationError,
    QuotaError,
    RateLimitError,
} from "@enconvert/node-sdk";

try {
    await client.v2.perceive("https://example.com", { outputs: ["markdown"] });
} catch (e) {
    if (e instanceof AuthenticationError) {
        console.error("Invalid API key. Check ENCONVERT_API_KEY.");
    } else if (e instanceof QuotaError) {
        console.error("Request rejected with 402.");
    } else if (e instanceof RateLimitError) {
        console.error("Too many requests. Back off and retry.");
    } else if (e instanceof APIError) {
        console.error(`API error [${e.statusCode}]: ${e.message}`);
    } else {
        throw e;
    }
}
```

| Classe | Levée sur | Code de statut |
|-------|-----------|-------------|
| `AuthenticationError` | Clé invalide, manquante ou révoquée | `401`, `403` (les deux rapportent `statusCode` `401`) |
| `QuotaError` | Levée sur un HTTP 402 | `402` |
| `RateLimitError` | Trop de requêtes | `429` |
| `APIError` | Toute autre 4xx / 5xx | le code réel |
| `EnconvertError` | Classe de base de toutes les précédentes | -- |

`QuotaError` et `RateLimitError` étendent toutes deux `APIError`, qui étend `EnconvertError` : ordonnez donc vos vérifications `instanceof` de la plus spécifique à la plus générale. Chaque `APIError` porte un champ `statusCode`.

Certaines défaillances n'atteignent jamais le réseau : une extension de fichier non prise en charge, un appel `distill` avec à la fois `urls` et `discoverFrom`, un appel `ingest` dont le mode et les arguments se contredisent, un appel `perceiveDirect` avec plus d'une sortie d'artefact, ou un appel `updateWatcher` sans aucun champ. Ceux-là lèvent une `Error` ordinaire localement, pour que vous trouviez l'erreur en développement.

La table complète des messages d'erreur se trouve dans la référence [Codes d'erreur](/fr/docs/reference/errors.md).

---

## Récupération après timeout

Les conversions URL vers PDF longues ou les documents volumineux peuvent dépasser la limite de timeout de 60 à 120 secondes du reverse proxy, même quand la conversion finit par réussir côté serveur. Le SDK gère cela de façon transparente sur les méthodes de conversion V1 :

1. Avant chaque requête, le SDK génère un UUID et l'envoie comme `job_id` dans le corps de la requête.
2. Si la requête initiale renvoie une 5xx, le SDK bascule discrètement sur l'interrogation de `GET /v1/convert/status/{job_id}` toutes les 3 secondes.
3. Dès que la tâche est enregistrée comme `success`, le SDK renvoie le résultat. Dès qu'elle est enregistrée comme `failed`, le SDK lève `APIError`.
4. L'échéance d'interrogation est de 5 minutes. Si elle est dépassée, le SDK lève `APIError(504, "Conversion timed out")`.

Vous n'avez aucun code à écrire pour cela, ça fonctionne tout seul. Définissez `timeout` sur le constructeur si vous voulez borner la requête initiale.

La V2 utilise des objets de tâche explicites plutôt qu'une récupération implicite : `perceiveBatch` et `ingest` renvoient un identifiant que vous interrogez avec `getPerceiveBatch` et `getIngestJob`, et `ingest` peut appeler un webhook à la place.

---

## Configuration

```ts
const client = new Enconvert({
    apiKey: process.env.ENCONVERT_API_KEY!,
    timeout: 300_000, // ms, 5 min par défaut
    baseUrl: "https://api.enconvert.com", // à surcharger pour des passerelles auto-hébergées
});
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `apiKey` | `string` | -- (obligatoire) | Clé API privée (`sk_...`). Le constructeur lève une erreur immédiatement si elle manque. |
| `timeout` | `number` | `300_000` | Timeout de requête en ms. Interrompt le `fetch` sous-jacent via `AbortController`. |
| `baseUrl` | `string` | `https://api.enconvert.com` | URL de base de l'API. Les barres obliques finales sont supprimées. |

La clé voyage dans un en-tête `X-API-Key` sur chaque requête, en V1 comme en V2. `client.v2` est construit pour vous et partage la clé, l'URL de base et le timeout du client : il n'y a donc rien de plus à configurer.

<div class="alert alert-warning">
<strong>N'écrivez jamais la clé API en dur.</strong> Lisez-la depuis une variable d'environnement ou votre gestionnaire de secrets. Quiconque obtient votre clé privée peut lancer des conversions et des opérations V2 sur votre compte. Faites tourner vos clés depuis le <a href="/fr/dashboard">tableau de bord</a>.
</div>

---

## Forme du résultat

Chaque méthode de conversion renvoie un `ConversionResult` :

```ts
interface ConversionResult {
    presignedUrl: string;          // URL signée pour télécharger la sortie (1 heure)
    objectKey: string;             // clé de l'objet de stockage
    filename: string;              // nom de fichier côté serveur
    fileSize?: number;             // octets
    conversionTimeSeconds?: number;
    jobId?: string;                // présent lorsque la récupération après timeout a interrogé
}
```

L'URL pré-signée est valable une heure. Si vous avez besoin d'un accès permanent, téléchargez le fichier (utilisez `saveTo`, ou récupérez l'URL vous-même) et stockez-le dans votre propre bucket.

Les résultats V2 ont une forme différente. Un `PerceiveResult` porte `operationId`, `status`, `url`, `urlFinal`, `contentHash`, `renderQuality`, `statusCode`, `deductions`, `cacheHit`, une table `outputs` indexée par nom de sortie, `structured`, `extractionTier`, `tokens`, `costCents`, `durationMs`, `optionsEcho`, `error` et `warnings`. Chaque entrée de `outputs` est un `V2OutputArtifact` de la forme `{ url?, objectKey, sizeBytes, contentType, expiresIn }`, où `expiresIn` est en secondes et vaut `900` par défaut. Les URL d'artefacts V2 durent donc 15 minutes plutôt qu'une heure, et elles sont resignées à chaque lecture : rappeler `getPerceiveOperation(operationId)` vous donne donc des liens frais sans refaire le rendu de la page.

---

## TypeScript

Les définitions de types sont livrées avec le package : aucune installation `@types/...` n'est nécessaire. Le package est publié en double (ESM + CJS) avec des `exports`, `types` et `.d.ts` / `.d.cts` corrects, si bien qu'il fonctionne sous n'importe quel mode de résolution de modules Node.

```ts
import type {
    ClientOptions,
    CompressImageOptions,
    ConversionResult,
    ConvertDocumentOptions,
    ConvertImageOptions,
    ConvertToMarkdownOptions,
    ConvertToPdfOptions,
    FileInput,
    JobStatus,
    PdfOptions,
    UrlToMarkdownOptions,
    UrlToPdfOptions,
    UrlToScreenshotOptions,
    // Les types V2 proviennent du même point d'entrée.
    DiscoverOptions, DiscoverResult,
    DistillOptions, DistillResult,
    IngestJob, IngestOptions,
    LookupOptions, LookupResult,
    PerceiveOptions, PerceiveResult, PerceiveOutputName,
    PerceiveBatchResult, PerceiveDirectResult,
    Watcher, WatcherSnapshotList,
} from "@enconvert/node-sdk";
```

La classe `EnconvertV2` elle-même est également exportée, si vous voulez typer un paramètre de fonction comme étant l'espace de noms V2.

---

## Mise à jour

Le package embarque une petite CLI, `enconvert-sdk`, pour se maintenir à jour.

```bash
npx enconvert-sdk upgrade
```

```bash
npx enconvert-sdk upgrade --dry-run
```

```bash
npx enconvert-sdk version
```

`upgrade` détecte npm, pnpm, yarn ou bun à partir du gestionnaire de paquets ambiant et affiche toujours la commande d'installation exacte avant de l'exécuter : rien n'arrive donc à votre lockfile sans que vous le voyiez. `--dry-run` affiche cette commande et s'arrête. `version` indique la version du SDK installée.

---

## Source et tickets

- **npm :** [@enconvert/node-sdk](https://www.npmjs.com/package/@enconvert/node-sdk)
- **GitHub :** [enconvert/node-sdk](https://github.com/enconvert/node-sdk)
- **Licence :** MIT
- **Autres langages :** [Tous les SDK](/fr/docs/guides/integrations/sdks.md)

---

## Questions fréquentes

### Comment convertir des fichiers en Node.js avec un package npm ?

Installez `@enconvert/node-sdk`, créez un client avec votre clé API (`new Enconvert({ apiKey: process.env.ENCONVERT_API_KEY! })`), et appelez une méthode typée comme `convertUrlToPdf`, `convertImage` ou `convertDocument`. Passez `saveTo` pour écrire le résultat en flux directement sur le disque.

### Comment récupérer une page web en Markdown propre avec Node.js ?

Appelez `client.v2.perceive(url, { outputs: ["markdown"] })`. Vous obtenez une URL signée pour 15 minutes vers le Markdown dans `op.outputs.markdown.url`, plus un score `renderQuality` pour la lecture. Si vous voulez les octets directement au lieu d'une URL, appelez `client.v2.perceiveDirect(url, { outputs: ["markdown"] })` et lisez `result.content`.

### Qu'est-ce que renderQuality et pourquoi est-ce important ?

`renderQuality` est un score de 0.0 à 1.0 attaché à chaque rendu V2. Un défi anti-bot, un mur de connexion, une page d'erreur HTTP, un soft 404 ou une coquille vide d'application monopage obtiennent tous un score bas et reviennent avec des `deductions` et des `warnings` nommés : une mauvaise lecture est donc signalée au lieu d'entrer discrètement dans le contexte de votre agent comme s'il s'agissait de la vraie page. Un score inférieur à environ 0,40 signifie que le rendu a échoué en pratique, même si la requête a renvoyé 200.

### Comment convertir du HEIC en WebP en Node.js ?

Appelez `convertImage` avec le fichier HEIC (un chemin ou un objet buffer `{ data, filename }`) et `outputFormat: "webp"`. Le SDK convertit entre `jpeg`, `png`, `svg`, `heic` et `webp` ; le format d'entrée est détecté à partir de l'extension du nom de fichier.

### Comment compresser une image en Node.js sans changer son format ?

Appelez `compressImage` avec un fichier `.png`, `.jpg`, `.jpeg` ou `.webp`. La sortie conserve le format et l'extension de l'entrée, supprime les métadonnées tout en préservant le profil ICC et l'orientation EXIF, et n'est jamais plus volumineuse que l'entrée. Ajoutez `targetSizeKb` pour réduire la taille vers un budget donné ; la cible est au mieux, alors lisez `result.fileSize` pour voir ce qui a réellement été atteint.

### Comment convertir n'importe quel document en Markdown pour un pipeline RAG ?

Appelez `convertToMarkdown` avec le fichier et passez `saveTo` pour écrire le `.md` directement sur le disque. Il accepte 22 extensions couvrant Office, OpenDocument, PDF, EPUB, HTML, CSV et texte brut, et renvoie un seul fichier Markdown conscient des titres : votre découpeur peut donc segmenter sur les titres du document plutôt que sur un nombre arbitraire de caractères.

### Comment transformer un site web entier en chunks prêts pour le RAG avec Node.js ?

Appelez `client.v2.ingest({ mode: "sitemap", url, maxPages, chunk: { maxWords: 512, sentenceOverlap: 1 } })`. Ingest est toujours asynchrone : interrogez donc `client.v2.getIngestJob(job.jobId)` jusqu'à ce que `status` vaille `"completed"` et lisez `outputUrl` pour le JSONL signé, ou définissez `webhookUrl` et laissez le webhook de fin vous prévenir. Pour des documents locaux plutôt qu'un site, `client.v2.ingestFiles([...])` exécute le même pipeline.

### Comment extraire du JSON structuré depuis une page en Node.js ?

Appelez `client.v2.distill({ urls, schema })` où `schema` est soit un objet JSON-Schema, soit une table plate `{ champ: description }`. Ajoutez un `cssSchema` et la passe de sélecteurs répond à tout ce qu'elle peut avant toute escalade vers le niveau LLM ; `result.extractionTier`, `fieldsFromCss` et `fieldsFromLlm` vous disent quel niveau a fait le travail.

### Comment surveiller les changements d'une page web en Node.js ?

Appelez `client.v2.createWatcher(url, { frequencyMinutes: 60, diffMode: "auto", webhookUrl })`. Le plancher horaire est strict, donc 60 est la cadence minimale. Lisez l'historique avec `getWatcherSnapshots`, mettez en pause avec `updateWatcher(id, { status: "paused" })`, et supprimez avec `deleteWatcher`, qui est une suppression douce et idempotente.

### Comment le SDK gère-t-il les conversions longues qui atteignent le timeout du reverse proxy ?

Avant chaque requête de conversion V1, le SDK génère un UUID et l'envoie comme `job_id` ; si la requête renvoie une 5xx, il interroge discrètement `GET /v1/convert/status/{job_id}` toutes les 3 secondes jusqu'à ce que la tâche soit `success` ou `failed`. L'échéance d'interrogation est de 5 minutes, après quoi il lève `APIError(504, "Conversion timed out")`. La V2 utilise plutôt des identifiants de tâche explicites, interrogés avec `getPerceiveBatch` ou `getIngestJob`.

### Puis-je utiliser le SDK Node.js dans une application navigateur ?

Non, le SDK est exclusivement côté serveur, parce qu'il s'authentifie avec une clé API privée (`sk_...`) qui ne doit jamais être embarquée dans du code côté client. Il fonctionne sous Node 18+, Bun et Deno via le spécificateur npm.

### Combien de temps l'URL de téléchargement pré-signée reste-t-elle valable ?

Le `presignedUrl` de chaque `ConversionResult` est valable une heure. Les URL d'artefacts V2 sont valables 15 minutes et sont resignées à chaque lecture : `getPerceiveOperation(operationId)` vous remet donc des liens frais. Pour un accès permanent, téléchargez le fichier et stockez-le dans votre propre bucket.
