---
seo_title: SDK C# .NET de conversion de fichiers (NuGet) | EnConvert
meta_desc: SDK C# et .NET officiel EnConvert pour .NET 8+. Méthodes asynchrones de conversion de fichiers, plus l'espace de noms V2 : perceive, discover, distill, ingest, watch.
keywords: sdk conversion de fichiers c# .net, convertir des fichiers en c# .net, url vers pdf c# .net, api scraping web c# .net, docx vers pdf c# .net, enconvert csharp sdk, client api conversion nuget, heic vers webp c#, url vers markdown dotnet, api capture d'écran de site dotnet, pipeline ingestion rag c#
---

# SDK C# et .NET de conversion de fichiers

`Enconvert` est le client C# et .NET officiel de l'API EnConvert, publié sur NuGet et ciblant .NET 8 ou plus récent. Il convertit des fichiers depuis C# : URL vers PDF, DOCX vers PDF, HEIC vers WebP, JSON vers YAML, n'importe quel document vers Markdown, et des sites web entiers en une seule archive ZIP. Le même client porte un espace de noms `V2` pour le scraping et la web intelligence : une seule clé couvre donc perceive, discover, lookup, distill, ingest et watch. Chaque appel est asynchrone et annulable, chaque réponse est un record typé, et le package n'embarque aucune dépendance tierce.

<div class="alert alert-info">
<strong>NuGet :</strong> <code>Enconvert</code> · <strong>Source :</strong> <a href="https://github.com/conversionapi/csharp-sdk">conversionapi/csharp-sdk</a> · <strong>Runtime :</strong> .NET 8+ · <strong>Dépendances :</strong> aucune au-delà de la BCL
</div>

---

## Installation

```bash
dotnet add package Enconvert
```

L'assembly cible `net8.0` avec les types référence nullables activés, et repose uniquement sur `System.Net.Http` et `System.Text.Json`.

## Démarrage rapide

```csharp
using Enconvert;

using var client = new EnconvertClient(Environment.GetEnvironmentVariable("ENCONVERT_API_KEY")!);

// Convertir une page en direct en PDF et l'écrire sur le disque.
var pdf = await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions { SaveTo = "page.pdf" });
Console.WriteLine(pdf.PresignedUrl);

// Lire une page comme votre agent devrait le faire, avec un score de qualité attaché.
var op = await client.V2.PerceiveAsync("https://example.com", new PerceiveOptions { Outputs = new[] { "markdown", "structured" } });
Console.WriteLine($"{op.Outputs["markdown"].Url} scored {op.RenderQuality}");
```

`EnconvertClient` implémente `IDisposable` : déclarez-le donc avec `using` ou enregistrez-le en singleton. Il s'authentifie avec une clé API privée, ce qui signifie qu'il a sa place sur le serveur : ne livrez jamais la clé dans un build desktop, mobile ou Blazor WebAssembly. La mise en place des clés est traitée dans [Authentification](/fr/docs/authentication).

---

## Ce que le client expose

| Membre | Endpoint | Renvoie |
|--------|----------|---------|
| `ConvertUrlToPdfAsync(url, opts?)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `ConvertUrlToScreenshotAsync(url, opts?)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `ConvertUrlToMarkdownAsync(url, opts?)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `ConvertImageAsync(file, opts)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `ConvertDocumentAsync(file, opts?)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `ConvertToMarkdownAsync(file, opts?)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `ConvertToPdfAsync(file, opts?)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `ConvertWebsiteToPdfAsync(url, opts?)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `ConvertWebsiteToScreenshotAsync(url, opts?)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `GetJobStatusAsync(jobId)` | `GET /v1/convert/status/{jobId}` | `JobStatus` |
| `GetBatchStatusAsync(batchId)` | `GET /v1/convert/batch/{batchId}` | `BatchStatus` |
| `WaitForBatchAsync(batchId, opts?)` | `GET /v1/convert/batch/{batchId}` (interrogé en boucle) | `BatchStatus` |
| `V2` | la surface `/v2/*` | `EnconvertV2` |

`client.V2` regroupe 23 méthodes réparties sur six capacités :

| Capacité | Méthodes | Référence |
|------------|---------|-----------|
| Perceive | `PerceiveAsync`, `GetPerceiveOperationAsync`, `PerceiveBatchAsync`, `GetPerceiveBatchAsync`, `PerceiveDirectAsync`, `DownloadPerceiveArtifactAsync` | [/fr/docs/v2-perceive](/fr/docs/v2-perceive) |
| Discover | `DiscoverAsync` | [/fr/docs/v2-discover](/fr/docs/v2-discover) |
| Lookup | `LookupAsync` | [/fr/docs/v2-lookup](/fr/docs/v2-lookup) |
| Distill | `DistillAsync` | [/fr/docs/v2-distill](/fr/docs/v2-distill) |
| Ingest | `IngestAsync`, `IngestFilesAsync`, `ListIngestJobsAsync`, `GetIngestJobAsync`, `CancelIngestJobAsync`, `RetryIngestWebhookAsync`, `GetWebhookSecretAsync`, `RotateWebhookSecretAsync` | [/fr/docs/v2-ingest](/fr/docs/v2-ingest) |
| Watch | `CreateWatcherAsync`, `ListWatchersAsync`, `GetWatcherAsync`, `GetWatcherSnapshotsAsync`, `UpdateWatcherAsync`, `DeleteWatcherAsync` | [/fr/docs/v2-watch](/fr/docs/v2-watch) |

Chaque méthode prend un `CancellationToken` final facultatif. Les options sont des records à propriétés `init` : construisez-les donc avec un initialiseur d'objet et réutilisez-les avec `with`. Les clients pour les autres langages se trouvent dans l'[index des SDK](/fr/docs/sdks) ; la surface REST brute est décrite dans la [vue d'ensemble des endpoints](/fr/docs/endpoints-overview).

---

## Conversion de fichiers

### ConvertUrlToPdfAsync

Rend en PDF n'importe quelle URL accessible.

```csharp
var result = await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions
{
    SinglePage = false,
    ViewportWidth = 1440,
    PdfOptions = new PdfOptions { PageSize = "A4", Orientation = "landscape" },
    SaveTo = "report.pdf",
});

Console.WriteLine($"{result.Filename} ({result.FileSize} bytes)");
```

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `SaveTo` | `string?` | -- | Chemin local vers lequel écrire le PDF. Les répertoires parents manquants sont créés. |
| `SinglePage` | `bool?` | `true` | `true` produit une seule page continue. `false` pagine en utilisant `PdfOptions.PageSize`. |
| `PdfOptions` | `PdfOptions?` | -- | Taille de page, orientation, marges, échelle, niveaux de gris, en-tête, pied de page. Voir [Options PDF](#options-pdf). |
| `ViewportWidth`, `ViewportHeight` | `int?` | `1920`, `1080` | Fenêtre d'affichage du navigateur, en pixels. |
| `LoadMedia`, `EnableScroll` | `bool?` | `true` | Attend les images et les vidéos avant la capture ; 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é. |
| `Auth` | `HttpBasicAuth?` | -- | Identifiants HTTP Basic pour une page derrière une authentification. |
| `Cookies`, `Headers` | `IReadOnlyList<BrowserCookie>?`, `IReadOnlyDictionary<string, string>?` | -- | Jusqu'à 50 cookies injectés avant le rendu, et jusqu'à 20 en-têtes de requête supplémentaires. Les en-têtes hop-by-hop sont rejetés. |

<div class="alert alert-warning">
<strong>Ne combinez pas <code>Auth</code> avec votre propre en-tête <code>Authorization</code>.</strong> L'API rejette ce conflit plutôt que de deviner quel identifiant l'emporte. Choisissez-en un.
</div>

### ConvertUrlToScreenshotAsync

Capture un PNG de n'importe quelle URL.

```csharp
await client.ConvertUrlToScreenshotAsync("https://example.com", new UrlToScreenshotOptions
{
    ViewportWidth = 1440,
    SaveTo = "screenshot.png",
});
```

`UrlToScreenshotOptions` accepte les mêmes options de fenêtre d'affichage, de médias, de défilement, de nom de fichier et d'accès que `ConvertUrlToPdfAsync`, à l'exception de `SinglePage` et `PdfOptions`.

### ConvertUrlToMarkdownAsync

Extrait du Markdown GitHub-Flavored propre à partir d'une URL. La navigation, les pieds de page, les publicités et les scripts sont supprimés, le corps principal de l'article est conservé, et un frontmatter YAML (title, description, url, links, images) est ajouté en tête.

```csharp
await client.ConvertUrlToMarkdownAsync("https://example.com/article", new UrlToMarkdownOptions { SaveTo = "article.md" });
```

`UrlToMarkdownOptions` partage les mêmes options de rendu et d'accès. Pour les lectures destinées à un agent qui ont aussi besoin d'un score de qualité et d'une extraction structurée, utilisez plutôt [Perceive](#perceive).

### ConvertImageAsync

Convertit entre `jpeg`, `png`, `svg`, `heic` et `webp` dans n'importe quel sens, et rastérise un PDF en JPEG.

```csharp
// Depuis un chemin.
await client.ConvertImageAsync("photo.heic", new ConvertImageOptions { OutputFormat = "webp", SaveTo = "photo.webp" });

// Depuis des octets, avec un nom de fichier explicite pour que le format d'entrée soit détectable.
var bytes = await File.ReadAllBytesAsync("scan.pdf");
await client.ConvertImageAsync(new FileInput(bytes, "scan.pdf"), new ConvertImageOptions
{
    OutputFormat = "jpeg",
    SaveTo = "scan.jpeg",
});
```

Trois surcharges acceptent un chemin `string`, un `byte[]` brut, ou un record `FileInput`. Le format d'entrée vient de l'extension du nom de fichier : préférez donc `FileInput` quand vous détenez des octets, car la surcharge `byte[]` nue envoie `upload.bin`, ce qui ne fonctionne que sur les endpoints à détection automatique.

| Option | Type | Requis | Description |
|--------|------|----------|-------------|
| `OutputFormat` | `string` | Oui | Format cible. `jpg`, `yml`, `htm` et `md` sont normalisés vers leurs noms canoniques. |
| `SaveTo` | `string?` | -- | Chemin local vers lequel écrire le résultat. |
| `OutputFilename` | `string?` | -- | Remplace le nom de fichier généré. |

Les paires non prises en charge lèvent une `ArgumentException` avant qu'aucune requête HTTP ne soit émise : une faute de frappe ne coûte donc rien.

### ConvertDocumentAsync

Convertit des documents et des formats de données. `OutputFormat` vaut `"pdf"` par défaut.

```csharp
// docx vers pdf
await client.ConvertDocumentAsync("report.docx", new ConvertDocumentOptions { SaveTo = "report.pdf" });

// json vers yaml
await client.ConvertDocumentAsync("data.json", new ConvertDocumentOptions { OutputFormat = "yaml", SaveTo = "data.yaml" });

// markdown vers pdf avec géométrie de page
await client.ConvertDocumentAsync("README.md", new ConvertDocumentOptions
{
    PdfOptions = new PdfOptions { PageSize = "A4", Margins = new PdfMargins { Top = 20, Bottom = 20 } },
    SaveTo = "readme.pdf",
});
```

**Entrées prises en charge :** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`. L'EPUB n'a pas de paire de conversion documentaire dédiée : faites donc passer les `.epub` par [`ConvertToPdfAsync`](#converttopdfasync) ou [`ConvertToMarkdownAsync`](#converttomarkdownasync).

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `OutputFormat` | `string?` | `"pdf"` | Format cible. |
| `SaveTo` | `string?` | -- | Chemin local vers lequel écrire le résultat. |
| `OutputFilename` | `string?` | -- | Remplace le nom de fichier généré. |
| `PdfOptions` | `PdfOptions?` | -- | Mise en page. Utile uniquement lorsque la sortie est un PDF. |

Comme `ConvertImageAsync`, cette méthode dispose de surcharges chemin, `byte[]` et `FileInput`.

### ConvertToMarkdownAsync

Envoyez presque n'importe quel document vers l'endpoint Markdown à détection automatique : PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, ainsi que les formats bureautiques hérités et ODF. Les images ne sont pas prises en charge ici.

```csharp
await client.ConvertToMarkdownAsync("handbook.docx", new ConvertToMarkdownOptions { SaveTo = "handbook.md" });
```

Le format est détecté côté serveur : il n'y a donc aucune vérification d'extension côté client et aucune option PDF sur cet endpoint. `ConvertToMarkdownOptions` ne porte que `SaveTo` et `OutputFilename`. La sortie est un unique fichier `.md` qui respecte la hiérarchie des titres, ce qui en fait une solide première étape dans un pipeline RAG : un découpeur sémantique peut segmenter sur la hiérarchie de titres du document plutôt que sur un nombre de caractères arbitraire.

### ConvertToPdfAsync

L'autre endpoint à détection automatique : bureautique, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, texte brut, images matricielles, SVG, EPUB, ou un PDF existant transmis tel quel pour normalisation.

```csharp
// Diapositives vers PDF.
await client.ConvertToPdfAsync("slides.pptx", new ConvertToPdfOptions { SaveTo = "slides.pdf" });

// PDF transmis tel quel, converti en niveaux de gris.
await client.ConvertToPdfAsync("scan.pdf", new ConvertToPdfOptions
{
    PdfOptions = new PdfOptions { Grayscale = true },
    SaveTo = "gray.pdf",
});
```

<div class="alert alert-warning">
<strong>Seul <code>Grayscale</code> est pris en compte ici.</strong> L'endpoint anything-to-pdf ignore le reste de <code>PdfOptions</code>. Lorsque vous avez besoin d'une taille de page, d'une orientation, de marges ou d'un en-tête et d'un pied de page, faites passer le HTML et le Markdown par <code>ConvertDocumentAsync</code>, ou rendez la page avec <code>ConvertUrlToPdfAsync</code>.
</div>

`ConvertToPdfOptions` porte `SaveTo`, `OutputFilename` et `PdfOptions`, et dispose des trois mêmes surcharges d'entrée que les méthodes ci-dessus.

### Lots sur un site entier

`ConvertWebsiteToPdfAsync` et `ConvertWebsiteToScreenshotAsync` découvrent chaque page d'un site, convertissent chacune d'elles en arrière-plan, et regroupent les résultats dans une seule archive ZIP. Les deux sont uniquement asynchrones et exigent une clé API privée.

```csharp
var batch = await client.ConvertWebsiteToPdfAsync("https://example.com", new WebsiteToPdfOptions
{
    CrawlMode = "sitemap",
    ExcludePatterns = new[] { "/blog/tag/" },
    NotificationEmail = "ops@example.com",
});
Console.WriteLine($"{batch.BatchId}: {batch.UrlCount} pages via {batch.DiscoveryMethod}");

// Bloque jusqu'à ce que le ZIP soit prêt, puis l'enregistre.
var status = await client.WaitForBatchAsync(batch.BatchId, new WaitForBatchOptions { SaveTo = "site.zip" });
Console.WriteLine($"{status.Completed} of {status.Total} converted, {status.Failed} failed");
```

Pour interroger à votre propre rythme, appelez plutôt `GetBatchStatusAsync(batchId)` et lisez `ZipDownloadUrl` une fois que `Status` a quitté l'état `"processing"`.

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `CrawlMode` | `string?` | `"auto"` | `"auto"`, `"sitemap"` (sitemap.xml uniquement), ou `"full"` (sitemap plus un crawl en largeur). |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Ne crawler, ou au contraire ignorer, que les URL correspondant à ces motifs. Mode full crawl. |
| `NotificationEmail`, `CallbackUrl` | `string?` | propriétaire du projet, -- | Adresse prévenue à la fin du lot, et webhook appelé en POST à la fin du traitement. |
| `SinglePage`, `PdfOptions` | `bool?`, `PdfOptions?` | -- | Lots PDF uniquement. Les options de fenêtre d'affichage, de médias, de défilement et d'accès sont partagées avec les méthodes portant sur une URL unique. |

`WaitForBatchOptions` prend `IntervalMs` (`5_000` par défaut), `TimeoutMs` (`1_800_000` par défaut, soit 30 minutes) et `SaveTo`. Dépasser le délai lève une `ApiException` avec le statut `504`.

### GetJobStatusAsync

Interroge un job de conversion unique par son identifiant.

```csharp
var status = await client.GetJobStatusAsync("job_abc123");

if (status.Status == "success") Console.WriteLine(status.PresignedUrl);
else if (status.Status == "failed") Console.Error.WriteLine(status.Error);
```

<div class="alert alert-info">
<strong>Vous en avez rarement besoin directement.</strong> Le client interroge déjà le statut du job pour vous quand une requête synchrone meurt au niveau du proxy. Voir <a href="#timeout-recovery">Récupération des timeouts</a>.
</div>

### Paires de conversion prises en charge

Le SDK reflète la carte des convertisseurs de la passerelle et implémente 43 paires `{input}-to-{output}` typées.

| Entrée | Sorties |
|-------|---------|
| `json` | `csv`, `toml`, `xml`, `yaml` |
| `xml` | `csv`, `json` |
| `yaml` | `json` |
| `csv` | `json`, `xml` |
| `toml` | `json` |
| `markdown` | `html`, `pdf` |
| `html` | `pdf` |
| `doc`, `excel`, `ppt`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers` | `pdf` |
| `jpeg`, `png`, `svg`, `heic`, `webp` | les uns vers les autres, les 20 paires |
| `pdf` | `jpeg` |

La classe statique `Formats` expose le même tableau, ce qui vous permet de valider avant de construire une interface ou une file de jobs :

```csharp
Formats.ValidOutputsFor("json");        // ["csv", "toml", "xml", "yaml"]
Formats.ValidOutputsFor("pdf");         // ["jpeg"]
Formats.ImplementedConversions;         // l'ensemble complet des 43 noms d'endpoints
Formats.NormalizeOutputFormat(".JPG");  // "jpeg"
```

Tout ce qui sort de ce tableau passe par `ConvertToPdfAsync` ou `ConvertToMarkdownAsync`, qui détectent le format côté serveur. La référence complète des paramètres se trouve dans [Paramètres et options](/fr/docs/parameters-options).

---

## Web intelligence (V2)

Chaque lecture V2 porte `RenderQuality`, un score de 0.0 à 1.0 qui indique avec quelle honnêteté la page s'est rendue. Un challenge anti-bot, un mur de cookies ou de connexion, une erreur HTTP ou une coquille d'application monopage vide reviennent avec un score bas, une carte `Deductions` renseignée nommant les contrôles déclenchés, et une liste `Warnings`, le contenu restant tout de même renvoyé. C'est tout l'intérêt : une mauvaise lecture est signalée au lieu d'entrer silencieusement dans le contexte de votre agent. Le même score apparaît sur les résultats de perceive, les éléments de distill, les résultats de lookup perçus automatiquement, et les snapshots de watchers. Les concepts sont traités dans la [vue d'ensemble V2](/fr/docs/v2-overview). Les endpoints V2 exigent une clé API privée ; les clés publiques sont rejetées.

### Perceive

Rend une URL vers des artefacts prêts pour un agent.

```csharp
var op = await client.V2.PerceiveAsync("https://example.com", new PerceiveOptions
{
    Outputs = new[] { "markdown", "screenshot", "structured" },
    Extract = new[] { "tables", "metadata" },
    OnlyMainContent = true,
});

Console.WriteLine(op.RenderQuality);            // de 0.0 à 1.0
Console.WriteLine(op.StatusCode);               // statut HTTP amont
Console.WriteLine(op.Outputs["markdown"].Url);  // signée pour 15 minutes
Console.WriteLine(op.Structured);               // JsonObject, la forme est la vôtre

foreach (var (check, penalty) in op.Deductions) Console.WriteLine($"deduction {check}: {penalty}");
```

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `Outputs` | `IReadOnlyList<string>?` | `["markdown", "structured"]` | Une valeur parmi `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `Extract` | `IReadOnlyList<string>?` | -- | Cibles heuristiques : `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `Schema` | `JsonObject?` | -- | Schéma JSON pilotant l'extraction structurée de niveau LLM. |
| `WaitFor`, `WaitTimeoutMs` | `string?`, `int?` | --, `30000` | Un sélecteur CSS (éventuellement `css:...`) ou `js:<expr>` à attendre avant la capture, borné de 0 à 60000 ms. |
| `JsCode` | `string?` | -- | JavaScript exécuté après la navigation, 20000 caractères maximum. |
| `Viewport` | `PerceiveViewport?` | 1920 x 1080 | `Width` de 320 à 3840, `Height` de 240 à 2160. |
| `Headers`, `Cookies`, `Auth` | voir [ci-dessus](#converturltopdfasync) | -- | En-têtes supplémentaires, cookies injectés, identifiants HTTP Basic. |
| `CacheMode` | `string?` | `"enabled"` | `"enabled"` réutilise un cache d'une heure, `"bypass"` le contourne, `"refresh"` refait le rendu. |
| `PdfOptions` | `PdfOptions?` | -- | Utile uniquement lorsque `Outputs` contient `pdf`. |
| `BlockResources` | `IReadOnlyList<string>?` | -- | Types de ressources à ignorer : `image`, `media`, `font`, `stylesheet`, `script`. |
| `RespectRobots`, `Mobile` | `bool?` | -- | Respecte robots.txt ; émule un appareil mobile. |
| `OnlyMainContent` | `bool?` | `true` | Supprime la navigation, l'en-tête, le pied de page et les bandeaux cookies de l'artefact markdown et de l'extrait `main_content`. |
| `DirectDownload` | `bool?` | -- | Renvoie les octets de l'artefact au lieu de l'enveloppe JSON. Préférez `PerceiveDirectAsync`. |

`ProxyUrl`, `Geolocation` et `ActionChain` existent sur `PerceiveOptions` mais ne sont pas encore disponibles côté serveur et reviennent pour l'instant en `422`.

Resignez les URL d'artefacts plus tard, traitez jusqu'à 1000 URL par lot, ou récupérez directement les octets sans l'aller-retour par URL signée :

```csharp
// Les URL d'artefacts sont resignées à chaque récupération de l'opération.
var again = await client.V2.GetPerceiveOperationAsync(op.OperationId);

// Lots : les petits s'exécutent en ligne, les plus gros renvoient "queued" et vous les interrogez.
var batch = await client.V2.PerceiveBatchAsync(
    new[] { "https://a.example.com", "https://b.example.com" },
    new PerceiveBatchOptions { Outputs = new[] { "markdown" }, OutputMode = "zip" });

var done = await client.V2.GetPerceiveBatchAsync(batch.JobId);
Console.WriteLine($"{done.Completed}/{done.Total} done, {done.Failed} failed, zip {done.Zip?.Url}");

// Téléchargement direct : exactement une sortie produisant un artefact est requise.
var direct = await client.V2.PerceiveDirectAsync("https://example.com", new PerceiveOptions { Outputs = new[] { "pdf" } });
await File.WriteAllBytesAsync(direct.Filename ?? "page.pdf", direct.Content);

// Retélécharger un artefact stocké d'une opération antérieure.
var artifact = await client.V2.DownloadPerceiveArtifactAsync(op.OperationId, "markdown");
```

`PerceiveDirectAsync` lève localement une `ArgumentException` sauf si exactement une sortie parmi `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links` ou `images` est demandée, puisque `structured` est du JSON en ligne et non un fichier stocké. Les deux méthodes directes renvoient un `PerceiveDirectResult` portant `Content`, `ContentType`, `Filename`, `OperationId`, `ObjectKey`, `CacheHit`, `RenderQuality`, `SourceStatusCode`, `ContentHash` et `WarningsCount`. `DownloadPerceiveArtifactAsync` ne prend le nom de sortie que lorsque l'opération a produit plus d'un artefact, et renvoie `410` une fois l'artefact sorti de sa fenêtre de rétention.

### Discover

Énumère les URL d'un site sans aucun rendu navigateur, ce qui la rend bien moins coûteuse qu'un crawl.

```csharp
var found = await client.V2.DiscoverAsync("https://example.com", new DiscoverOptions
{
    Mode = "hybrid",
    MaxUrls = 200,
    ExcludePatterns = new[] { "/tag/" },
});

Console.WriteLine($"{found.Total} urls, truncated: {found.Truncated}");
foreach (var url in found.Urls) Console.WriteLine(url);
```

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `Mode` | `string?` | `"hybrid"` | `"sitemap"`, `"crawl"`, ou `"hybrid"` (sitemap plus crawl HTTP). |
| `MaxUrls` | `int?` | `100` | De 1 à 1000. |
| `MaxDepth` | `int?` | `2` | Profondeur de crawl, de 1 à 5. |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Liste blanche et liste noire d'expressions régulières, 50 entrées maximum chacune. La liste noire est appliquée en second. |
| `SameDomainOnly`, `RespectRobots` | `bool?` | `true`, -- | Rester sur le domaine de départ ; respecter robots.txt pendant la découverte. |

`DiscoverResult` rapporte `Total`, `Urls`, `PagesCrawled`, `Truncated`, `RobotsRespected`, une carte de comptage par source dans `Sources`, et `Warnings`.

### Lookup

Lance une recherche web catégorisée et, si vous le souhaitez, rend les meilleurs résultats dans le même appel.

```csharp
var search = await client.V2.LookupAsync("best static site generators", new LookupOptions
{
    Category = "web",
    NumResults = 10,
    TimeFilter = "month",
    PerceiveTop = 3,
});

foreach (var hit in search.Results)
{
    Console.WriteLine($"{hit.Position}. {hit.Title} {hit.Url}");
    if (hit.Perceive is { } page)
        Console.WriteLine($"   quality {page.RenderQuality}, markdown {page.Outputs["markdown"].Url}");
}
```

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `Category` | `string?` | `"web"` | `"web"`, `"news"`, `"images"`, `"scholar"`, `"patents"`, `"maps"`. |
| `Country`, `Locale` | `string?` | -- | Code pays Google `gl` (`"us"`, `"in"`) et langue d'interface `hl` (`"en"`). |
| `TimeFilter` | `string?` | -- | `"hour"`, `"day"`, `"week"`, `"month"`, `"year"`. |
| `NumResults`, `Page` | `int?` | `10`, `1` | De 1 à 100 résultats ; page de 1 à 10. |
| `Location`, `Autocorrect` | `string?`, `bool?` | --, `true` | Localisation en texte libre comme `"Austin, Texas"` ; laisser le fournisseur corriger les fautes de frappe évidentes. |
| `PerceiveTop` | `int?` | `0` | De 0 à 10. Perçoit automatiquement les N premières URL de résultats avec un rendu navigateur complet. |

`LookupResult` porte également `AnswerBox`, `KnowledgeGraph`, `PerceiveOperationIds` et `Warnings`.

### Distill

Extraction structurée pilotée par schéma. Une passe CSS gratuite s'exécute en premier lorsque vous en fournissez une, et seuls les champs qu'elle manque escaladent vers le niveau LLM.

```csharp
using System.Text.Json.Nodes;

var extraction = await client.V2.DistillAsync(new DistillOptions
{
    Urls = new[] { "https://example.com/pricing" },
    Schema = new JsonObject { ["plans"] = "list of plan names with monthly prices" },
    CssSchema = new CssSchema
    {
        BaseSelector = ".plan-card",
        Fields = new[]
        {
            new CssField { Name = "name", Type = "text", Selector = "h3" },
            new CssField { Name = "price", Type = "text", Selector = ".price" },
        },
    },
});

var first = extraction.Results[0];
Console.WriteLine($"{first.Data} via {first.ExtractionTier}");
Console.WriteLine($"css fields {first.FieldsFromCss}, llm fields {first.FieldsFromLlm}");

// Ou découvrez d'abord un site, puis distillez chaque page trouvée.
await client.V2.DistillAsync(new DistillOptions
{
    DiscoverFrom = new DistillDiscoverFrom { Url = "https://example.com", Mode = "sitemap", MaxPages = 10 },
    Schema = new JsonObject { ["title"] = "page title" },
});
```

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `Schema` | `JsonObject` | obligatoire | Un objet JSON-Schema ou une carte plate `{field: description}`. Le `Data` de la réponse suit cette forme. |
| `Urls` | `IReadOnlyList<string>?` | -- | URL explicites, 50 maximum. Exactement l'un de `Urls` ou `DiscoverFrom`. |
| `DiscoverFrom` | `DistillDiscoverFrom?` | -- | `Url`, `Mode` (`"sitemap"`, `"crawl"`, `"hybrid"`), et `MaxPages` de 1 à 50, 10 par défaut. |
| `CssSchema` | `CssSchema?` | -- | `BaseSelector` plus `Fields`, avec `Name` et `TargetField` facultatifs. |
| `WaitFor`, `WaitTimeoutMs` | `string?`, `int?` | --, `30000` | Condition d'attente avant l'extraction. |
| `Headers`, `Cookies`, `RespectRobots` | -- | -- | Mêmes formes que les options de rendu ci-dessus. |

`CssField.Type` vaut `text`, `attribute`, `html`, `regex`, `nested`, `list` ou `nested_list`, avec une imbrication jusqu'à cinq niveaux. Passer à la fois `Urls` et `DiscoverFrom`, ou aucun des deux, lève une `ArgumentException` avant qu'aucune requête ne parte.

### Ingest

Transforme un site entier, ou une pile de documents envoyés, en JSONL découpé qu'un magasin de vecteurs peut lire. Ingest est toujours asynchrone.

```csharp
// Depuis un site.
var job = await client.V2.IngestAsync(new IngestOptions
{
    Mode = "sitemap",
    Url = "https://docs.example.com",
    MaxPages = 100,
    Chunk = new IngestChunkOptions { MaxWords = 512 },
    WebhookUrl = "https://my.app/hooks/enconvert",
});

// Depuis des fichiers envoyés : PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, bureautique hérité et ODF.
var fileJob = await client.V2.IngestFilesAsync(
    new[] { new FileInput(await File.ReadAllBytesAsync("handbook.pdf"), "handbook.pdf") },
    new IngestFilesOptions { Chunk = new IngestChunkOptions { MaxWords = 512 } });

// Interroger jusqu'à ce que le JSONL soit prêt.
var status = await client.V2.GetIngestJobAsync(job.JobId);
Console.WriteLine($"{status.Status}: {status.PagesProcessed}/{status.PagesDiscovered} pages, {status.TotalChunks} chunks");
if (status.Status == "completed") Console.WriteLine(status.OutputUrl);

var recent = await client.V2.ListIngestJobsAsync(new V2ListOptions { Limit = 20 });
await client.V2.CancelIngestJobAsync(job.JobId); // idempotent
```

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `Mode` | `string?` | `"urls"` | `"urls"`, `"sitemap"`, ou `"crawl"`. |
| `Url` | `string?` | -- | URL de départ. Obligatoire pour `sitemap` et `crawl`, rejetée pour `urls`. |
| `Urls` | `IReadOnlyList<string>?` | -- | URL explicites, 1000 maximum. Obligatoire pour `urls`, rejetée sinon. |
| `MaxPages`, `MaxDepth`, `SameDomainOnly` | `int?`, `int?`, `bool?` | `50`, `2`, `true` | Plafond de découverte de 1 à 1000 pour `sitemap` et `crawl`, profondeur de crawl de 1 à 5, rester sur le domaine de départ. |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Liste blanche et liste noire d'expressions régulières. |
| `RespectRobots`, `WaitFor`, `WaitTimeoutMs` | -- | -- | Contrôles de rendu par page. |
| `Chunk` | `IngestChunkOptions?` | -- | `MaxWords` de 32 à 4000, 512 par défaut. `SentenceOverlap` de 0 à 10, 1 par défaut. |
| `WebhookUrl` | `string?` | -- | Webhook de fin de traitement, signé en HMAC. |

Les règles de mode sont appliquées côté client : un job `urls` qui définit aussi `Url` lève donc immédiatement une `ArgumentException`. Un job traverse les états `queued`, `discovering`, `processing`, puis `completed`, `failed` ou `canceled`. La livraison des webhooks est vérifiable de bout en bout :

```csharp
var secret = await client.V2.GetWebhookSecretAsync();
Console.WriteLine($"{secret.SignatureHeader} using {secret.SignatureScheme}, replay tolerance {secret.ReplayToleranceSeconds}s");

await client.V2.RotateWebhookSecretAsync();  // les anciennes signatures cessent d'être valides
var retry = await client.V2.RetryIngestWebhookAsync(job.JobId);
Console.WriteLine($"delivered: {retry.Delivered} after {retry.Attempts} attempts");
```

### Watch

Refait le rendu d'une page selon une planification et vous prévient quand elle change.

```csharp
var watcher = await client.V2.CreateWatcherAsync("https://example.com/pricing", new WatchCreateOptions
{
    FrequencyMinutes = 60,
    DiffMode = "auto",
    WebhookUrl = "https://my.app/hooks/changes",
});

var page = await client.V2.ListWatchersAsync(new V2ListOptions { Limit = 20 });
var one = await client.V2.GetWatcherAsync(watcher.WatcherId);
var history = await client.V2.GetWatcherSnapshotsAsync(watcher.WatcherId, new SnapshotListOptions { Limit = 10 });
foreach (var snap in history.Snapshots)
    Console.WriteLine($"{snap.CheckedAt} changed: {snap.HasChanges} similarity: {snap.Similarity}");

await client.V2.UpdateWatcherAsync(watcher.WatcherId, new WatcherUpdate { Status = "paused" });
await client.V2.UpdateWatcherAsync(watcher.WatcherId, new WatcherUpdate { WebhookUrl = "" }); // l'efface
await client.V2.DeleteWatcherAsync(watcher.WatcherId); // suppression logique, idempotente
```

| Option | Type | Par défaut | Description |
|--------|------|---------|-------------|
| `FrequencyMinutes` | `int?` | `60` | De 60 à 43200. Le plancher horaire est strict. |
| `DiffMode` | `string?` | `"auto"` | `"auto"`, `"text"`, `"structured"`, `"tables"`, `"metadata"`. |
| `TrackFields` | `JsonObject?` | -- | Sous-ensemble de champs ou de sélecteurs pour le moteur de diff. |
| `WebhookUrl` | `string?` | -- | Webhook de changement, signé en HMAC. Définissez-le à `""` dans une mise à jour pour l'effacer. |
| `NotifyEmail` | `bool?` | `true` | Prévient le propriétaire du projet par e-mail en cas de changement. |

`WatcherUpdate` accepte aussi `Status` (`"active"` ou `"paused"`) et lève une `ArgumentException` si vous passez une mise à jour sans aucun champ défini.

<div class="alert alert-warning">
<strong>Les diffs de snapshots contiennent du contenu de page non fiable.</strong> <code>WatcherSnapshot.Changes</code> est du JSON brut repris de la page surveillée. Échappez-le avant de l'afficher dans un tableau de bord ou un e-mail.
</div>

---

## Options PDF

`PdfOptions` est partagé par `ConvertUrlToPdfAsync`, `ConvertDocumentAsync`, `ConvertToPdfAsync` (niveaux de gris uniquement), et `PerceiveOptions` lorsque `pdf` figure parmi les sorties.

```csharp
await client.ConvertUrlToPdfAsync("https://internal.example.com/report", new UrlToPdfOptions
{
    PdfOptions = new PdfOptions
    {
        PageSize = "A4",
        Orientation = "landscape",
        Margins = new PdfMargins { Top = 10, Bottom = 10, Left = 15, Right = 15 },
        Scale = 0.9,
        Header = new PdfHeaderFooter { Content = "Quarterly Report", Height = 15 },
        Footer = new PdfHeaderFooter { Content = "Confidential", Height = 12 },
    },
    Auth = new HttpBasicAuth { Username = "user", Password = "pass" },
    SaveTo = "report.pdf",
});
```

| Champ | Type | Description |
|-------|------|-------------|
| `PageSize` | `string?` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"`, et compagnie. |
| `PageWidth`, `PageHeight` | `double?` | Dimensions personnalisées. Définies ensemble, elles remplacent `PageSize`. |
| `Orientation` | `string?` | `"portrait"` ou `"landscape"`. |
| `Margins` | `PdfMargins?` | `Top`, `Bottom`, `Left`, `Right`, tous facultatifs. |
| `Scale` | `double?` | Échelle de rendu, par exemple `0.9` pour 90 pour cent. |
| `Grayscale` | `bool?` | Post-traite le PDF en niveaux de gris. |
| `Header`, `Footer` | `PdfHeaderFooter?` | `Content` jusqu'à 2000 caractères, plus `Height`. |

## Gestion des erreurs

Chaque échec remonte sous forme d'exception typée : les blocs `catch` se lisent donc du plus spécifique au moins spécifique.

```csharp
using Enconvert;

try
{
    await client.V2.PerceiveAsync("https://example.com");
}
catch (AuthenticationException) { Console.Error.WriteLine("Invalid or missing API key."); }
catch (QuotaException) { Console.Error.WriteLine("Request rejected with 402."); }
catch (RateLimitException) { Console.Error.WriteLine("Too many requests, back off and retry."); }
catch (ApiException e) { Console.Error.WriteLine($"API error [{e.StatusCode}]: {e.Message}"); }
```

| Classe | Déclenchée sur | Code de statut |
|-------|-----------|-------------|
| `AuthenticationException` | Clé invalide, manquante ou révoquée | `401`, `403` (rapporté comme `401`) |
| `QuotaException` | Déclenchée sur un HTTP 402 | `402` |
| `RateLimitException` | Limite de débit dépassée | `429` |
| `ApiException` | Toute autre 4xx ou 5xx | le code réel |
| `EnconvertException` | Classe de base de toutes les précédentes | -- |

La hiérarchie va d'`EnconvertException` à `ApiException` (qui porte `StatusCode`), puis aux trois classes spécifiques : un unique `catch (EnconvertException)` attrape donc tout ce que le SDK lève. Les messages sont extraits du champ `detail` ou `error` du corps de la réponse lorsqu'il est présent. La validation que le SDK effectue localement, comme une paire de conversion non prise en charge ou une requête distill malformée, lève plutôt une `ArgumentException` et n'atteint jamais le réseau. Les codes de réponse sont catalogués dans [Codes d'erreur](/fr/docs/error-codes).

## Récupération des timeouts

Les rendus d'URL longs et les conversions de documents volumineux peuvent dépasser un timeout de reverse proxy de 60 à 120 secondes, même quand la conversion elle-même réussit. Le client absorbe cela pour vous :

1. Avant chaque conversion de fichier unique ou d'URL unique, le SDK génère un identifiant de job et l'envoie avec la requête.
2. Si cette requête revient en 5xx, le SDK bascule vers le polling de `GET /v1/convert/status/{jobId}` toutes les 3 secondes au lieu d'échouer.
3. Un job enregistré comme `success` renvoie le `ConversionResult` habituel. Un job enregistré comme `failed` lève une `ApiException` avec le statut `500` et le message d'erreur du serveur.
4. Le délai maximal de polling est de 5 minutes, au-delà duquel le SDK lève `ApiException(504, "Conversion timed out")`.

Cela couvre `ConvertUrlToPdfAsync`, `ConvertUrlToScreenshotAsync`, `ConvertUrlToMarkdownAsync` et les quatre méthodes d'envoi de fichiers. Les soumissions de lots de site entier en sont volontairement exclues, parce qu'une soumission échouée n'a pas de ligne de job à interroger et doit remonter immédiatement. Quand une réponse omet `job_id`, le SDK y réinjecte l'identifiant qu'il a généré, si bien que `ConversionResult.JobId` reste toujours utilisable avec `GetJobStatusAsync`.

## Configuration

```csharp
using var client = new EnconvertClient(
    apiKey: Environment.GetEnvironmentVariable("ENCONVERT_API_KEY")!,
    baseUrl: null,       // vaut https://api.enconvert.com par défaut
    timeoutMs: 300_000);
```

| Paramètre | Type | Par défaut | Description |
|-----------|------|---------|-------------|
| `apiKey` | `string` | obligatoire | Clé API privée. Une valeur vide lève une `ArgumentException`. |
| `baseUrl` | `string?` | `https://api.enconvert.com` | Remplace l'URL de base de l'API. Les barres obliques finales sont supprimées. |
| `timeoutMs` | `int` | `300_000` | Timeout de requête en millisecondes, 5 minutes par défaut. |

La clé voyage dans un en-tête `X-API-Key` sur chaque requête. Les téléchargements d'artefacts vont directement vers des URL de stockage signées, via un second `HttpClient` qui n'envoie aucune clé et n'applique aucun timeout : un gros ZIP peut donc être diffusé aussi longtemps qu'il le faut. Libérez le client une seule fois à la fin de votre processus, ou enregistrez-le en singleton plutôt que d'en construire un par requête.

<div class="alert alert-warning">
<strong>Ne codez jamais la clé en dur.</strong> Lisez-la depuis une variable d'environnement, les user secrets ou votre gestionnaire de secrets. Quiconque détient votre clé privée peut lancer des conversions sur votre projet.
</div>

## Structure du résultat

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

```csharp
public sealed record ConversionResult
{
    public required string PresignedUrl { get; init; }  // URL de téléchargement signée
    public required string ObjectKey { get; init; }     // clé de l'objet de stockage
    public required string Filename { get; init; }      // nom de fichier côté serveur
    public int? FileSize { get; init; }                 // octets
    public double? ConversionTimeSeconds { get; init; }
    public string? JobId { get; init; }                 // utilisable avec GetJobStatusAsync
}
```

Les traitements asynchrones renvoient leurs propres records : `JobStatus` (`Status`, `PresignedUrl`, `ObjectKey`, `Error`), `BatchSubmission` (`BatchId`, `Status`, `UrlCount`, `TotalDiscovered`, `DiscoveryMethod`, `OutputFormat`), et `BatchStatus` (compteurs agrégés, `OutputMode`, `ZipDownloadUrl`, et les `Items` par URL).

Les artefacts V2 arrivent sous forme de valeurs `V2OutputArtifact` indexées par nom de sortie, chacune avec `Url` (signée pour 15 minutes), `ObjectKey`, `SizeBytes`, `ContentType` et `ExpiresIn` (900 secondes par défaut). Les URL signées expirent : pour un accès permanent, téléchargez donc les octets (passez `SaveTo`, utilisez `PerceiveDirectAsync`, ou récupérez l'URL vous-même) et stockez-les dans votre propre bucket. Rappeler `GetPerceiveOperationAsync` resigne les URL d'artefacts d'une opération encore dans sa fenêtre de rétention.

## Source et problèmes

- **NuGet :** `Enconvert`
- **GitHub :** [conversionapi/csharp-sdk](https://github.com/conversionapi/csharp-sdk)
- **Licence :** MIT
- **Autres langages :** [index des SDK](/fr/docs/sdks) · **Clés API :** [tableau de bord](/fr/dashboard) · [tarifs](/fr/pricing)

---

## Questions fréquentes

### Comment convertir des fichiers en C# avec un package NuGet ?

Exécutez `dotnet add package Enconvert`, créez un client avec votre clé (`new EnconvertClient(apiKey)`), et appelez une méthode asynchrone comme `ConvertUrlToPdfAsync`, `ConvertImageAsync` ou `ConvertDocumentAsync`. Passez `SaveTo` sur le record d'options pour écrire la sortie directement vers un chemin local, ou lisez `result.PresignedUrl` pour la télécharger vous-même.

### Comment convertir une URL en PDF en .NET ?

Appelez `await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions { SaveTo = "page.pdf" })`. Définissez `SinglePage = false` pour paginer, et passez un record `PdfOptions` pour la taille de page, l'orientation, les marges, l'échelle, les niveaux de gris, l'en-tête et le pied de page. La fenêtre d'affichage vaut 1920 x 1080 par défaut.

### Comment convertir un DOCX en PDF en C# ?

Appelez `ConvertDocumentAsync("report.docx", new ConvertDocumentOptions { SaveTo = "report.pdf" })`. Le PDF est le format de sortie par défaut, `OutputFormat` est donc facultatif ici. La même méthode gère les entrées XLSX, PPTX, ODF, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML et TOML.

### Comment récupérer une page web avec le SDK C# ?

Utilisez l'espace de noms V2 : `await client.V2.PerceiveAsync(url, new PerceiveOptions { Outputs = new[] { "markdown", "structured" } })`. Vous obtenez du Markdown, du HTML nettoyé ou brut, des captures d'écran, un PDF, des liens, des images et une extraction structurée, chacun sous forme d'artefact signé, plus un score `RenderQuality` pour la lecture. Pour énumérer d'abord les URL d'un site sans rien rendre, appelez `DiscoverAsync`.

### Que signifie la qualité de rendu et pourquoi figure-t-elle sur chaque lecture ?

`RenderQuality` est un score d'honnêteté de 0.0 à 1.0 attaché à chaque lecture V2. Un score bas signifie que la page ne s'est pas rendue proprement : challenge anti-bot, mur de cookies ou de connexion, erreur HTTP, ou coquille d'application monopage vide. Le contenu revient tout de même, accompagné d'une carte `Deductions` nommant les contrôles déclenchés et d'une liste `Warnings`, pour que votre agent puisse rejeter une mauvaise lecture au lieu de la prendre pour argent comptant.

### Le SDK gère-t-il les conversions qui dépassent le timeout du proxy ?

Oui. Il envoie un identifiant de job généré avec chaque conversion de fichier unique ou d'URL unique, et si la requête renvoie un 5xx il interroge `GET /v1/convert/status/{jobId}` toutes les 3 secondes pendant 5 minutes au maximum. Un succès renvoie le résultat habituel, un échec enregistré lève une `ApiException`, et dépasser le délai lève `ApiException(504, "Conversion timed out")`. Les soumissions de lots de site entier sont exclues volontairement.

### Puis-je utiliser ce SDK depuis Blazor WebAssembly ou une application mobile ?

Non. Le client s'authentifie avec une clé API privée qui ne doit jamais être livrée dans du code qu'un utilisateur peut lire. Exécutez-le depuis ASP.NET Core, un worker service, une Azure Function, ou n'importe quel autre hôte .NET 8 côté serveur, et faites appeler par votre front-end votre propre endpoint à la place.

### Quelles conversions d'images sont prises en charge ?

Toutes les paires parmi `jpeg`, `png`, `svg`, `heic` et `webp`, soit 20 combinaisons, plus la rastérisation `pdf` vers `jpeg`. Le format d'entrée vient de l'extension du nom de fichier, et les paires non prises en charge lèvent une `ArgumentException` avant tout appel réseau. Consultez la table vous-même avec `Formats.ValidOutputsFor("heic")`.

### Comment transformer un site de documentation en fragments prêts pour le RAG ?

Appelez `client.V2.IngestAsync(new IngestOptions { Mode = "sitemap", Url = "https://docs.example.com", MaxPages = 100 })`, puis interrogez `GetIngestJobAsync` jusqu'à ce que `Status` vaille `"completed"` et lisez `OutputUrl` pour récupérer le JSONL. Pour des documents locaux, `IngestFilesAsync` fait passer les fichiers envoyés par le même découpeur. Ajustez `Chunk.MaxWords` et `Chunk.SentenceOverlap` pour correspondre à votre modèle d'embedding.

### Combien de temps les URL de téléchargement sont-elles valides ?

Les URL d'artefacts V2 sont signées pour 15 minutes (`ExpiresIn` vaut 900 secondes) et sont resignées chaque fois que vous récupérez l'opération avec `GetPerceiveOperationAsync`. Les résultats de conversion renvoient eux aussi une URL présignée. Dans les deux cas, si vous voulez que le fichier survive à la signature, téléchargez-le et stockez-le dans votre propre bucket.
