---
seo_title: C# und .NET SDK für Dateikonvertierung: NuGet | EnConvert
meta_desc: Offizielles EnConvert SDK für C# und .NET 8+. Asynchrone Methoden für die Dateikonvertierung plus V2-Namensraum für perceive, discover, distill, ingest und watch.
keywords: c# sdk dateikonvertierung, dateien konvertieren c#, url zu pdf c#, web scraping api dotnet, docx in pdf konvertieren c#, enconvert csharp sdk, nuget client dateikonvertierung api, heic in webp konvertieren c#, url zu markdown dotnet, screenshot api für websites dotnet, rag pipeline c#
---

# C#- und .NET-SDK für Dateikonvertierung

`Enconvert` ist der offizielle C#- und .NET-Client für die EnConvert API, veröffentlicht auf NuGet und ausgelegt auf .NET 8 oder neuer. Er konvertiert Dateien aus C# heraus: URL zu PDF, DOCX zu PDF, HEIC zu WebP, JSON zu YAML, jedes Dokument zu Markdown und ganze Websites in ein einziges ZIP. Derselbe Client trägt einen `V2`-Namensraum für Web Scraping und Web-Intelligence, ein Key deckt also perceive, discover, lookup, distill, ingest und watch ab. Jeder Aufruf ist asynchron und abbrechbar, jede Antwort ist ein typisiertes Record, und das Paket zieht keine Drittanbieter-Abhängigkeiten nach.

<div class="alert alert-info">
<strong>NuGet:</strong> <code>Enconvert</code> · <strong>Quelle:</strong> <a href="https://github.com/conversionapi/csharp-sdk">conversionapi/csharp-sdk</a> · <strong>Laufzeit:</strong> .NET 8+ · <strong>Abhängigkeiten:</strong> keine über die BCL hinaus
</div>

---

## Installation

```bash
dotnet add package Enconvert
```

Die Assembly zielt auf `net8.0` mit aktivierten Nullable Reference Types und baut ausschließlich auf `System.Net.Http` und `System.Text.Json` auf.

## Schnellstart

```csharp
using Enconvert;

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

// Eine Live-Seite in ein PDF konvertieren und auf die Festplatte streamen.
var pdf = await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions { SaveTo = "page.pdf" });
Console.WriteLine(pdf.PresignedUrl);

// Lies eine Seite so, wie es dein Agent tun sollte, mit angehängtem Quality-Score.
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` implementiert `IDisposable`, deklariere ihn also mit `using` oder registriere ihn als Singleton. Er authentifiziert sich mit einem privaten API-Key, gehört also auf den Server: Liefere den Key niemals in einem Desktop-, Mobile- oder Blazor-WebAssembly-Build aus. Die Key-Einrichtung behandelt die [Authentifizierung](/de/docs/authentication).

---

## Was der Client bereitstellt

| Mitglied | Endpunkt | Rückgabe |
|--------|----------|---------|
| `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}` (mit Polling) | `BatchStatus` |
| `V2` | die `/v2/*`-Oberfläche | `EnconvertV2` |

`client.V2` bündelt 23 Methoden über sechs Fähigkeiten:

| Fähigkeit | Methoden | Referenz |
|------------|---------|-----------|
| Perceive | `PerceiveAsync`, `GetPerceiveOperationAsync`, `PerceiveBatchAsync`, `GetPerceiveBatchAsync`, `PerceiveDirectAsync`, `DownloadPerceiveArtifactAsync` | [/de/docs/v2-perceive](/de/docs/v2-perceive) |
| Discover | `DiscoverAsync` | [/de/docs/v2-discover](/de/docs/v2-discover) |
| Lookup | `LookupAsync` | [/de/docs/v2-lookup](/de/docs/v2-lookup) |
| Distill | `DistillAsync` | [/de/docs/v2-distill](/de/docs/v2-distill) |
| Ingest | `IngestAsync`, `IngestFilesAsync`, `ListIngestJobsAsync`, `GetIngestJobAsync`, `CancelIngestJobAsync`, `RetryIngestWebhookAsync`, `GetWebhookSecretAsync`, `RotateWebhookSecretAsync` | [/de/docs/v2-ingest](/de/docs/v2-ingest) |
| Watch | `CreateWatcherAsync`, `ListWatchersAsync`, `GetWatcherAsync`, `GetWatcherSnapshotsAsync`, `UpdateWatcherAsync`, `DeleteWatcherAsync` | [/de/docs/v2-watch](/de/docs/v2-watch) |

Jede Methode nimmt ein optionales abschließendes `CancellationToken`. Optionen sind Records mit `init`-Eigenschaften, baue sie also mit einem Objektinitialisierer und verwende sie mit `with` weiter. Clients für andere Sprachen findest du im [SDK-Index](/de/docs/sdks); die rohe REST-Oberfläche steht in der [Endpunkt-Übersicht](/de/docs/endpoints-overview).

---

## Datei-Konvertierung

### ConvertUrlToPdfAsync

Rendere jede erreichbare URL zu einem PDF.

```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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `SaveTo` | `string?` | -- | Lokaler Pfad, in den das PDF gestreamt wird. Fehlende übergeordnete Verzeichnisse werden angelegt. |
| `SinglePage` | `bool?` | `true` | `true` erzeugt eine einzige fortlaufende Seite. `false` paginiert anhand von `PdfOptions.PageSize`. |
| `PdfOptions` | `PdfOptions?` | -- | Seitengröße, Ausrichtung, Ränder, Skalierung, Graustufen, Kopf- und Fußzeile. Siehe [PDF-Optionen](#pdf-optionen). |
| `ViewportWidth`, `ViewportHeight` | `int?` | `1920`, `1080` | Browser-Viewport in Pixeln. |
| `LoadMedia`, `EnableScroll` | `bool?` | `true` | Vor der Erfassung auf Bilder und Videos warten; von oben nach unten scrollen, damit Lazy-Loader auslösen. |
| `OutputFilename` | `string?` | auto | Überschreibt den generierten Dateinamen. |
| `Auth` | `HttpBasicAuth?` | -- | HTTP-Basic-Zugangsdaten für eine Seite hinter einem Login. |
| `Cookies`, `Headers` | `IReadOnlyList<BrowserCookie>?`, `IReadOnlyDictionary<string, string>?` | -- | Bis zu 50 Cookies, die vor dem Rendern eingeschleust werden, und bis zu 20 zusätzliche Request-Header. Hop-by-Hop-Header werden abgelehnt. |

<div class="alert alert-warning">
<strong>Kombiniere <code>Auth</code> nicht mit deinem eigenen <code>Authorization</code>-Header.</strong> Die API lehnt diesen Konflikt ab, statt zu raten, welche Zugangsdaten gewinnen. Entscheide dich für eines.
</div>

### ConvertUrlToScreenshotAsync

Erfasse ein PNG einer beliebigen URL.

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

`UrlToScreenshotOptions` akzeptiert dieselben Optionen für Viewport, Medien, Scrollen, Dateiname und Zugriff wie `ConvertUrlToPdfAsync`, ohne `SinglePage` und `PdfOptions`.

### ConvertUrlToMarkdownAsync

Extrahiere sauberes GitHub-Flavored Markdown aus einer URL. Navigation, Fußzeilen, Werbung und Skripte werden entfernt, der Haupttext des Artikels bleibt erhalten, und YAML-Frontmatter (Titel, Beschreibung, URL, Links, Bilder) wird vorangestellt.

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

`UrlToMarkdownOptions` teilt sich dieselben Render- und Zugriffsoptionen. Für agentenorientierte Lesevorgänge, die zusätzlich einen Quality-Score und strukturierte Extraktion brauchen, nimm stattdessen [Perceive](#perceive).

### ConvertImageAsync

Konvertiere zwischen `jpeg`, `png`, `svg`, `heic` und `webp` in jede Richtung und rastere ein PDF zu JPEG.

```csharp
// Von einem Pfad.
await client.ConvertImageAsync("photo.heic", new ConvertImageOptions { OutputFormat = "webp", SaveTo = "photo.webp" });

// Aus Bytes, mit explizitem Dateinamen, damit das Eingabeformat erkannt werden kann.
var bytes = await File.ReadAllBytesAsync("scan.pdf");
await client.ConvertImageAsync(new FileInput(bytes, "scan.pdf"), new ConvertImageOptions
{
    OutputFormat = "jpeg",
    SaveTo = "scan.jpeg",
});
```

Drei Überladungen akzeptieren einen Pfad als `string`, ein rohes `byte[]` oder ein `FileInput`-Record. Das Eingabeformat ergibt sich aus der Dateiendung, bevorzuge also `FileInput`, wenn du Bytes hältst: Die reine `byte[]`-Überladung sendet `upload.bin`, was nur an den automatisch erkennenden Endpunkten funktioniert.

| Option | Typ | Erforderlich | Beschreibung |
|--------|------|----------|-------------|
| `OutputFormat` | `string` | Ja | Zielformat. `jpg`, `yml`, `htm` und `md` werden auf ihre kanonischen Namen normalisiert. |
| `SaveTo` | `string?` | -- | Lokaler Pfad, in den das Ergebnis gestreamt wird. |
| `OutputFilename` | `string?` | -- | Überschreibt den generierten Dateinamen. |

Nicht unterstützte Paare werfen eine `ArgumentException`, bevor eine HTTP-Anfrage gestellt wird, ein Tippfehler kostet also nichts.

### ConvertDocumentAsync

Konvertiere Dokumente und Datenformate. `OutputFormat` ist standardmäßig `"pdf"`.

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

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

// markdown zu pdf mit Seitengeometrie
await client.ConvertDocumentAsync("README.md", new ConvertDocumentOptions
{
    PdfOptions = new PdfOptions { PageSize = "A4", Margins = new PdfMargins { Top = 20, Bottom = 20 } },
    SaveTo = "readme.pdf",
});
```

**Unterstützte Eingaben:** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`. EPUB hat kein eigenes Dokumentpaar, schicke `.epub` also durch [`ConvertToPdfAsync`](#converttopdfasync) oder [`ConvertToMarkdownAsync`](#converttomarkdownasync).

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `OutputFormat` | `string?` | `"pdf"` | Zielformat. |
| `SaveTo` | `string?` | -- | Lokaler Pfad, in den das Ergebnis gestreamt wird. |
| `OutputFilename` | `string?` | -- | Überschreibt den generierten Dateinamen. |
| `PdfOptions` | `PdfOptions?` | -- | Seiteneinrichtung. Nur sinnvoll, wenn die Ausgabe ein PDF ist. |

Wie `ConvertImageAsync` hat diese Methode Überladungen für Pfad, `byte[]` und `FileInput`.

### ConvertToMarkdownAsync

Schicke fast jedes Dokument an den automatisch erkennenden Markdown-Endpunkt: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie ältere und ODF-Office-Formate. Bilder werden hier nicht unterstützt.

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

Das Format wird serverseitig erkannt, es gibt also keine clientseitige Prüfung der Erweiterung und keine PDF-Optionen an diesem Endpunkt. `ConvertToMarkdownOptions` trägt nur `SaveTo` und `OutputFilename`. Die Ausgabe ist eine einzelne, überschriften-bewusste `.md`-Datei, was sie zu einer soliden ersten Stufe in einer RAG-Pipeline macht: Ein semantischer Chunker kann anhand der Überschriften-Hierarchie des Dokuments trennen statt anhand willkürlicher Zeichenzahlen.

### ConvertToPdfAsync

Der andere automatisch erkennende Endpunkt: Office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, reiner Text, Rasterbilder, SVG, EPUB oder ein bestehendes PDF, das zur Normalisierung durchgereicht wird.

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

// PDF-Durchreichung, in Graustufen umgewandelt.
await client.ConvertToPdfAsync("scan.pdf", new ConvertToPdfOptions
{
    PdfOptions = new PdfOptions { Grayscale = true },
    SaveTo = "gray.pdf",
});
```

<div class="alert alert-warning">
<strong>Nur <code>Grayscale</code> wird hier berücksichtigt.</strong> Der anything-to-pdf-Endpunkt ignoriert den Rest von <code>PdfOptions</code>. Wenn du Seitengröße, Ausrichtung, Ränder oder eine Kopf- und Fußzeile brauchst, leite HTML und Markdown über <code>ConvertDocumentAsync</code>, oder rendere die Seite mit <code>ConvertUrlToPdfAsync</code>.
</div>

`ConvertToPdfOptions` trägt `SaveTo`, `OutputFilename` und `PdfOptions` und hat dieselben drei Eingabe-Überladungen wie die Methoden oben.

### Batches für ganze Websites

`ConvertWebsiteToPdfAsync` und `ConvertWebsiteToScreenshotAsync` ermitteln jede Seite einer Website, konvertieren jede einzelne im Hintergrund und bündeln die Ergebnisse in einem einzigen ZIP. Beide arbeiten ausschließlich asynchron und benötigen einen privaten API-Key.

```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}");

// Blockiert, bis das ZIP bereit ist, dann speichern.
var status = await client.WaitForBatchAsync(batch.BatchId, new WaitForBatchOptions { SaveTo = "site.zip" });
Console.WriteLine($"{status.Completed} of {status.Total} converted, {status.Failed} failed");
```

Wenn du stattdessen nach eigenem Zeitplan abfragen willst, rufe `GetBatchStatusAsync(batchId)` auf und lies `ZipDownloadUrl`, sobald `Status` den Wert `"processing"` verlässt.

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `CrawlMode` | `string?` | `"auto"` | `"auto"`, `"sitemap"` (nur sitemap.xml) oder `"full"` (Sitemap plus Breitensuche). |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Nur URLs crawlen, die zu diesen Mustern passen, oder sie überspringen. Full-Crawl-Modus. |
| `NotificationEmail`, `CallbackUrl` | `string?` | Projektinhaber, -- | Adresse, die bei Abschluss benachrichtigt wird, und ein Webhook, der bei Abschluss per POST aufgerufen wird. |
| `SinglePage`, `PdfOptions` | `bool?`, `PdfOptions?` | -- | Nur für PDF-Batches. Optionen für Viewport, Medien, Scrollen und Zugriff werden mit den Einzel-URL-Methoden geteilt. |

`WaitForBatchOptions` nimmt `IntervalMs` (Standard `5_000`), `TimeoutMs` (Standard `1_800_000`, also 30 Minuten) und `SaveTo`. Wird die Frist überschritten, wirft es eine `ApiException` mit Status `504`.

### GetJobStatusAsync

Frage einen einzelnen Konvertierungsjob per ID ab.

```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>Du brauchst das selten direkt.</strong> Der Client fragt den Job-Status bereits für dich ab, wenn eine synchrone Anfrage am Proxy stirbt. Siehe <a href="#timeout-recovery">Timeout-Recovery</a>.
</div>

### Unterstützte Konvertierungspaare

Das SDK spiegelt die Konverter-Map des Gateways und implementiert 43 typisierte `{input}-to-{output}`-Paare.

| Eingabe | Ausgaben |
|-------|---------|
| `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` | untereinander, alle 20 Paare |
| `pdf` | `jpeg` |

Die statische Klasse `Formats` stellt dieselbe Tabelle bereit, du kannst also validieren, bevor du eine Oberfläche oder eine Job-Queue baust:

```csharp
Formats.ValidOutputsFor("json");        // ["csv", "toml", "xml", "yaml"]
Formats.ValidOutputsFor("pdf");         // ["jpeg"]
Formats.ImplementedConversions;         // die vollständige Menge der 43 Endpunktnamen
Formats.NormalizeOutputFormat(".JPG");  // "jpeg"
```

Alles außerhalb dieser Tabelle läuft über `ConvertToPdfAsync` oder `ConvertToMarkdownAsync`, die das Format serverseitig erkennen. Die vollständige Parameter-Referenz steht unter [Parameter und Optionen](/de/docs/parameters-options).

---

## Web-Intelligence (V2)

Jeder V2-Lesevorgang trägt `RenderQuality`, einen Wert von 0.0 bis 1.0, der angibt, wie ehrlich die Seite gerendert wurde. Eine Bot-Challenge, eine Cookie- oder Login-Wall, ein HTTP-Fehler oder eine leere Single-Page-App-Hülle kommt mit niedrigem Wert, einer gefüllten `Deductions`-Map, die benennt, welche Prüfungen angeschlagen haben, und einer `Warnings`-Liste zurück, während der Inhalt trotzdem geliefert wird. Genau das ist der Punkt: Ein schlechter Lesevorgang wird markiert, statt still in den Kontext deines Agenten zu gelangen. Derselbe Wert erscheint an Perceive-Ergebnissen, Distill-Einträgen, automatisch geperceiveten Lookup-Treffern und Watcher-Snapshots. Die Konzepte behandelt die [V2-Übersicht](/de/docs/v2-overview). V2-Endpunkte verlangen einen privaten API-Key; öffentliche Keys werden abgelehnt.

### Perceive

Rendere eine URL in agentenfertige Artefakte.

```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);            // 0.0 bis 1.0
Console.WriteLine(op.StatusCode);               // HTTP-Status der Ursprungsseite
Console.WriteLine(op.Outputs["markdown"].Url);  // 15 Minuten lang signiert
Console.WriteLine(op.Structured);               // JsonObject, die Struktur bestimmst du

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

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `Outputs` | `IReadOnlyList<string>?` | `["markdown", "structured"]` | Beliebige aus `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `Extract` | `IReadOnlyList<string>?` | -- | Heuristische Ziele: `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `Schema` | `JsonObject?` | -- | JSON-Schema, das die strukturierte Extraktion der LLM-Stufe steuert. |
| `WaitFor`, `WaitTimeoutMs` | `string?`, `int?` | --, `30000` | Ein CSS-Selektor (optional als `css:...`) oder `js:<expr>`, auf den vor der Erfassung gewartet wird, begrenzt auf 0 bis 60000 ms. |
| `JsCode` | `string?` | -- | JavaScript, das nach der Navigation ausgeführt wird, maximal 20000 Zeichen. |
| `Viewport` | `PerceiveViewport?` | 1920 x 1080 | `Width` 320 bis 3840, `Height` 240 bis 2160. |
| `Headers`, `Cookies`, `Auth` | siehe [oben](#converturltopdfasync) | -- | Zusätzliche Header, eingeschleuste Cookies, HTTP-Basic-Zugangsdaten. |
| `CacheMode` | `string?` | `"enabled"` | `"enabled"` nutzt einen Cache von 1 Stunde erneut, `"bypass"` überspringt ihn, `"refresh"` rendert neu. |
| `PdfOptions` | `PdfOptions?` | -- | Nur sinnvoll, wenn `Outputs` den Wert `pdf` enthält. |
| `BlockResources` | `IReadOnlyList<string>?` | -- | Ressourcentypen, die übersprungen werden: `image`, `media`, `font`, `stylesheet`, `script`. |
| `RespectRobots`, `Mobile` | `bool?` | -- | robots.txt berücksichtigen; ein Mobilgerät emulieren. |
| `OnlyMainContent` | `bool?` | `true` | Entfernt Navigation, Header, Footer und Cookie-Banner aus dem Markdown-Artefakt und dem `main_content`-Extrakt. |
| `DirectDownload` | `bool?` | -- | Streamt Artefakt-Bytes statt des JSON-Envelopes. Bevorzuge `PerceiveDirectAsync`. |

`ProxyUrl`, `Geolocation` und `ActionChain` existieren in `PerceiveOptions`, sind serverseitig aber noch nicht verfügbar und kommen derzeit als `422` zurück.

Signiere Artefakt-URLs später neu, bündle bis zu 1000 URLs oder streame Bytes ohne den Umweg über die signierte URL:

```csharp
// Artefakt-URLs werden bei jedem Abruf der Operation neu signiert.
var again = await client.V2.GetPerceiveOperationAsync(op.OperationId);

// Batches: Kleine laufen inline, größere geben "queued" zurück, also abfragen.
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}");

// Direkter Download: genau eine artefakterzeugende Ausgabe erforderlich.
var direct = await client.V2.PerceiveDirectAsync("https://example.com", new PerceiveOptions { Outputs = new[] { "pdf" } });
await File.WriteAllBytesAsync(direct.Filename ?? "page.pdf", direct.Content);

// Ein gespeichertes Artefakt einer früheren Operation erneut herunterladen.
var artifact = await client.V2.DownloadPerceiveArtifactAsync(op.OperationId, "markdown");
```

`PerceiveDirectAsync` wirft lokal eine `ArgumentException`, sofern nicht genau eines von `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links` oder `images` angefordert wird, da `structured` inline-JSON und keine gespeicherte Datei ist. Beide direkten Methoden geben ein `PerceiveDirectResult` zurück, das `Content`, `ContentType`, `Filename`, `OperationId`, `ObjectKey`, `CacheHit`, `RenderQuality`, `SourceStatusCode`, `ContentHash` und `WarningsCount` trägt. `DownloadPerceiveArtifactAsync` nimmt den Ausgabenamen nur, wenn die Operation mehr als ein Artefakt erzeugt hat, und gibt `410` zurück, sobald das Artefakt seine Aufbewahrungsfrist überschritten hat.

### Discover

Zähle die URLs einer Website ohne Browser-Rendering auf, was deutlich günstiger ist als ein 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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `Mode` | `string?` | `"hybrid"` | `"sitemap"`, `"crawl"` oder `"hybrid"` (Sitemap plus HTTP-Crawl). |
| `MaxUrls` | `int?` | `100` | 1 bis 1000. |
| `MaxDepth` | `int?` | `2` | Crawl-Tiefe, 1 bis 5. |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Regex-Positivliste und Sperrliste, jeweils maximal 50 Einträge. Die Sperrliste wird als zweites angewendet. |
| `SameDomainOnly`, `RespectRobots` | `bool?` | `true`, -- | Auf der Ausgangsdomain bleiben; robots.txt während der Ermittlung berücksichtigen. |

`DiscoverResult` meldet `Total`, `Urls`, `PagesCrawled`, `Truncated`, `RobotsRespected`, eine Zählung pro Quelle in `Sources` sowie `Warnings`.

### Lookup

Führe eine kategorisierte Websuche aus und rendere die besten Treffer auf Wunsch im selben Aufruf.

```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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `Category` | `string?` | `"web"` | `"web"`, `"news"`, `"images"`, `"scholar"`, `"patents"`, `"maps"`. |
| `Country`, `Locale` | `string?` | -- | Google-`gl`-Ländercode (`"us"`, `"in"`) und `hl`-Oberflächensprache (`"en"`). |
| `TimeFilter` | `string?` | -- | `"hour"`, `"day"`, `"week"`, `"month"`, `"year"`. |
| `NumResults`, `Page` | `int?` | `10`, `1` | 1 bis 100 Ergebnisse; Seite 1 bis 10. |
| `Location`, `Autocorrect` | `string?`, `bool?` | --, `true` | Freitext-Ort wie `"Austin, Texas"`; offensichtliche Tippfehler vom Anbieter korrigieren lassen. |
| `PerceiveTop` | `int?` | `0` | 0 bis 10. Perceive die URLs der ersten N Treffer automatisch mit vollem Browser-Rendering. |

`LookupResult` trägt außerdem `AnswerBox`, `KnowledgeGraph`, `PerceiveOperationIds` und `Warnings`.

### Distill

Schemagesteuerte strukturierte Extraktion. Ein kostenloser CSS-Durchlauf läuft zuerst, wenn du einen angibst, und nur die Felder, die er verfehlt, steigen in die LLM-Stufe auf.

```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}");

// Oder ermittle zuerst eine Website und destilliere dann jede gefundene Seite.
await client.V2.DistillAsync(new DistillOptions
{
    DiscoverFrom = new DistillDiscoverFrom { Url = "https://example.com", Mode = "sitemap", MaxPages = 10 },
    Schema = new JsonObject { ["title"] = "page title" },
});
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `Schema` | `JsonObject` | erforderlich | Ein JSON-Schema-Objekt oder eine flache `{field: description}`-Map. Das `Data` der Antwort entspricht dieser Struktur. |
| `Urls` | `IReadOnlyList<string>?` | -- | Explizite URLs, maximal 50. Genau eines von `Urls` oder `DiscoverFrom`. |
| `DiscoverFrom` | `DistillDiscoverFrom?` | -- | `Url`, `Mode` (`"sitemap"`, `"crawl"`, `"hybrid"`) und `MaxPages` 1 bis 50, Standard 10. |
| `CssSchema` | `CssSchema?` | -- | `BaseSelector` plus `Fields`, mit optionalem `Name` und `TargetField`. |
| `WaitFor`, `WaitTimeoutMs` | `string?`, `int?` | --, `30000` | Wartebedingung vor der Extraktion. |
| `Headers`, `Cookies`, `RespectRobots` | -- | -- | Gleiche Strukturen wie bei den Render-Optionen oben. |

`CssField.Type` ist eines von `text`, `attribute`, `html`, `regex`, `nested`, `list` oder `nested_list`, mit einer Verschachtelung von bis zu fünf Ebenen. `Urls` und `DiscoverFrom` gemeinsam oder keines von beiden zu übergeben, wirft eine `ArgumentException`, bevor eine Anfrage rausgeht.

### Ingest

Verwandle eine ganze Website oder einen Stapel hochgeladener Dokumente in gechunktes JSONL, das ein Vektorspeicher lesen kann. Ingest arbeitet immer asynchron.

```csharp
// Von einer Website.
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",
});

// Aus hochgeladenen Dateien: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD, ältere und ODF-Office.
var fileJob = await client.V2.IngestFilesAsync(
    new[] { new FileInput(await File.ReadAllBytesAsync("handbook.pdf"), "handbook.pdf") },
    new IngestFilesOptions { Chunk = new IngestChunkOptions { MaxWords = 512 } });

// Abfragen, bis das JSONL bereit ist.
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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `Mode` | `string?` | `"urls"` | `"urls"`, `"sitemap"` oder `"crawl"`. |
| `Url` | `string?` | -- | Start-URL. Erforderlich für `sitemap` und `crawl`, abgelehnt bei `urls`. |
| `Urls` | `IReadOnlyList<string>?` | -- | Explizite URLs, maximal 1000. Erforderlich für `urls`, sonst abgelehnt. |
| `MaxPages`, `MaxDepth`, `SameDomainOnly` | `int?`, `int?`, `bool?` | `50`, `2`, `true` | Ermittlungsgrenze 1 bis 1000 für `sitemap` und `crawl`, Crawl-Tiefe 1 bis 5, auf der Ausgangsdomain bleiben. |
| `IncludePatterns`, `ExcludePatterns` | `IReadOnlyList<string>?` | -- | Regex-Positivliste und Sperrliste. |
| `RespectRobots`, `WaitFor`, `WaitTimeoutMs` | -- | -- | Render-Steuerung pro Seite. |
| `Chunk` | `IngestChunkOptions?` | -- | `MaxWords` 32 bis 4000, Standard 512. `SentenceOverlap` 0 bis 10, Standard 1. |
| `WebhookUrl` | `string?` | -- | Abschluss-Webhook, HMAC-signiert. |

Die Modusregeln werden clientseitig erzwungen, ein `urls`-Job, der zusätzlich `Url` setzt, wirft also sofort eine `ArgumentException`. Ein Job durchläuft `queued`, `discovering`, `processing` und dann `completed`, `failed` oder `canceled`. Die Webhook-Zustellung ist Ende zu Ende überprüfbar:

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

await client.V2.RotateWebhookSecretAsync();  // alte Signaturen verifizieren nicht mehr
var retry = await client.V2.RetryIngestWebhookAsync(job.JobId);
Console.WriteLine($"delivered: {retry.Delivered} after {retry.Attempts} attempts");
```

### Watch

Rendere eine Seite nach Zeitplan erneut und lass dich benachrichtigen, wenn sie sich ändert.

```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öscht ihn
await client.V2.DeleteWatcherAsync(watcher.WatcherId); // Soft Delete, idempotent
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `FrequencyMinutes` | `int?` | `60` | 60 bis 43200. Die Untergrenze von einer Stunde ist fest. |
| `DiffMode` | `string?` | `"auto"` | `"auto"`, `"text"`, `"structured"`, `"tables"`, `"metadata"`. |
| `TrackFields` | `JsonObject?` | -- | Feld- oder Selektor-Teilmenge für die Diff-Engine. |
| `WebhookUrl` | `string?` | -- | Änderungs-Webhook, HMAC-signiert. Setze ihn in einem Update auf `""`, um ihn zu löschen. |
| `NotifyEmail` | `bool?` | `true` | Benachrichtigt den Projektinhaber per E-Mail über Änderungen. |

`WatcherUpdate` akzeptiert außerdem `Status` (`"active"` oder `"paused"`) und wirft eine `ArgumentException`, wenn du ein Update ohne gesetzte Felder übergibst.

<div class="alert alert-warning">
<strong>Snapshot-Diffs enthalten nicht vertrauenswürdige Seiteninhalte.</strong> <code>WatcherSnapshot.Changes</code> ist rohes JSON aus der überwachten Seite. Escape es, bevor du es in einem Dashboard oder einer E-Mail darstellst.
</div>

---

## PDF-Optionen

`PdfOptions` wird von `ConvertUrlToPdfAsync`, `ConvertDocumentAsync`, `ConvertToPdfAsync` (nur Graustufen) und `PerceiveOptions` gemeinsam genutzt, wenn `pdf` unter den Ausgaben ist.

```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",
});
```

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `PageSize` | `string?` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` und Verwandte. |
| `PageWidth`, `PageHeight` | `double?` | Eigene Maße. Gemeinsam gesetzt überschreiben sie `PageSize`. |
| `Orientation` | `string?` | `"portrait"` oder `"landscape"`. |
| `Margins` | `PdfMargins?` | `Top`, `Bottom`, `Left`, `Right`, alle optional. |
| `Scale` | `double?` | Render-Skalierung, zum Beispiel `0.9` für 90 Prozent. |
| `Grayscale` | `bool?` | Wandelt das PDF nachträglich in Graustufen um. |
| `Header`, `Footer` | `PdfHeaderFooter?` | `Content` bis zu 2000 Zeichen, plus `Height`. |

## Fehlerbehandlung

Jeder Fehler erscheint als typisierte Exception, `catch`-Blöcke laufen also vom Spezifischsten zum Allgemeinsten.

```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}"); }
```

| Klasse | Ausgelöst bei | Statuscode |
|-------|-----------|-------------|
| `AuthenticationException` | Ungültiger, fehlender oder widerrufener Key | `401`, `403` (gemeldet als `401`) |
| `QuotaException` | Ausgelöst bei HTTP 402 | `402` |
| `RateLimitException` | Rate-Limit überschritten | `429` |
| `ApiException` | Jede andere 4xx oder 5xx | der tatsächliche Code |
| `EnconvertException` | Basisklasse für alle obigen | -- |

Die Hierarchie verläuft von `EnconvertException` über `ApiException` (das `StatusCode` trägt) zu den drei spezifischen Klassen, ein einzelnes `catch (EnconvertException)` fängt also alles ab, was das SDK auslöst. Meldungen werden aus dem Feld `detail` oder `error` des Response-Body übernommen, sofern vorhanden. Validierung, die das SDK lokal durchführt, etwa ein nicht unterstütztes Konvertierungspaar oder eine fehlerhafte distill-Anfrage, wirft stattdessen eine `ArgumentException` und erreicht nie das Netzwerk. Die Antwortcodes sind unter [Fehlercodes](/de/docs/error-codes) katalogisiert.

## Timeout-Recovery

Lange URL-Renders und große Dokumentkonvertierungen können ein Reverse-Proxy-Timeout von 60 bis 120 Sekunden überdauern, selbst wenn die Konvertierung selbst gelingt. Der Client fängt das für dich auf:

1. Vor jeder Konvertierung einer einzelnen Datei oder einer einzelnen URL erzeugt das SDK eine Job-ID und sendet sie mit der Anfrage.
2. Kommt diese Anfrage mit 5xx zurück, wechselt das SDK dazu, `GET /v1/convert/status/{jobId}` alle 3 Sekunden abzufragen, statt zu scheitern.
3. Ein als `success` erfasster Job gibt das normale `ConversionResult` zurück. Ein als `failed` erfasster Job wirft eine `ApiException` mit Status `500` und der Fehlermeldung des Servers.
4. Die Polling-Frist beträgt 5 Minuten, danach wirft das SDK `ApiException(504, "Conversion timed out")`.

Das gilt für `ConvertUrlToPdfAsync`, `ConvertUrlToScreenshotAsync`, `ConvertUrlToMarkdownAsync` und alle vier Datei-Upload-Methoden. Website-Batch-Übermittlungen nehmen bewusst nicht teil, weil eine fehlgeschlagene Übermittlung keine Job-Zeile zum Abfragen hat und sofort gemeldet werden muss. Wenn eine Antwort `job_id` weglässt, trägt das SDK die selbst erzeugte ID nach, `ConversionResult.JobId` ist also immer mit `GetJobStatusAsync` verwendbar.

## Konfiguration

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

| Parameter | Typ | Standard | Beschreibung |
|-----------|------|---------|-------------|
| `apiKey` | `string` | erforderlich | Privater API-Key. Ein leerer Wert wirft eine `ArgumentException`. |
| `baseUrl` | `string?` | `https://api.enconvert.com` | Überschreibt die Basis-URL der API. Abschließende Schrägstriche werden entfernt. |
| `timeoutMs` | `int` | `300_000` | Anfrage-Timeout in Millisekunden, standardmäßig 5 Minuten. |

Der Key reist bei jeder Anfrage in einem `X-API-Key`-Header. Artefakt-Downloads gehen direkt an signierte Speicher-URLs über einen zweiten `HttpClient`, der keinen Key sendet und kein Timeout anwendet, ein großes ZIP kann also so lange streamen, wie es braucht. Entsorge den Client einmal am Ende deines Prozesses oder registriere ihn als Singleton, statt pro Anfrage einen neuen zu bauen.

<div class="alert alert-warning">
<strong>Schreibe den Key niemals fest in den Code.</strong> Lies ihn aus einer Umgebungsvariablen, aus User Secrets oder aus deinem Secret-Manager. Wer deinen privaten Key hat, kann Konvertierungen in deinem Projekt ausführen.
</div>

## Ergebnisform

Jede Konvertierungsmethode gibt ein `ConversionResult` zurück:

```csharp
public sealed record ConversionResult
{
    public required string PresignedUrl { get; init; }  // signierte Download-URL
    public required string ObjectKey { get; init; }     // Objekt-Key im Speicher
    public required string Filename { get; init; }      // serverseitiger Dateiname
    public int? FileSize { get; init; }                 // Bytes
    public double? ConversionTimeSeconds { get; init; }
    public string? JobId { get; init; }                 // mit GetJobStatusAsync verwendbar
}
```

Asynchrone Arbeit gibt eigene Records zurück: `JobStatus` (`Status`, `PresignedUrl`, `ObjectKey`, `Error`), `BatchSubmission` (`BatchId`, `Status`, `UrlCount`, `TotalDiscovered`, `DiscoveryMethod`, `OutputFormat`) und `BatchStatus` (Summenzähler, `OutputMode`, `ZipDownloadUrl` und die `Items` pro URL).

V2-Artefakte kommen als `V2OutputArtifact`-Werte an, nach Ausgabenamen indexiert, jeweils mit `Url` (15 Minuten lang signiert), `ObjectKey`, `SizeBytes`, `ContentType` und `ExpiresIn` (standardmäßig 900 Sekunden). Signierte URLs laufen ab: Lade für dauerhaften Zugriff die Bytes herunter (übergib `SaveTo`, nutze `PerceiveDirectAsync` oder rufe die URL selbst ab) und lege sie in deinem eigenen Bucket ab. Ein erneuter Aufruf von `GetPerceiveOperationAsync` signiert die Artefakt-URLs einer Operation neu, solange sie noch innerhalb ihrer Aufbewahrungsfrist ist.

## Quelle und Issues

- **NuGet:** `Enconvert`
- **GitHub:** [conversionapi/csharp-sdk](https://github.com/conversionapi/csharp-sdk)
- **Lizenz:** MIT
- **Weitere Sprachen:** [SDK-Index](/de/docs/sdks) · **API-Keys:** [Dashboard](/de/dashboard) · [Preise](/de/pricing)

---

## Häufig gestellte Fragen

### Wie konvertiere ich Dateien in C# mit einem NuGet-Paket?

Führe `dotnet add package Enconvert` aus, erzeuge einen Client mit deinem Key (`new EnconvertClient(apiKey)`) und rufe eine asynchrone Methode wie `ConvertUrlToPdfAsync`, `ConvertImageAsync` oder `ConvertDocumentAsync` auf. Übergib `SaveTo` im Options-Record, um die Ausgabe direkt an einen lokalen Pfad zu streamen, oder lies `result.PresignedUrl`, um sie selbst herunterzuladen.

### Wie konvertiere ich eine URL in .NET in ein PDF?

Rufe `await client.ConvertUrlToPdfAsync("https://example.com", new UrlToPdfOptions { SaveTo = "page.pdf" })` auf. Setze `SinglePage = false`, um zu paginieren, und übergib ein `PdfOptions`-Record für Seitengröße, Ausrichtung, Ränder, Skalierung, Graustufen, Kopf- und Fußzeile. Der Viewport ist standardmäßig 1920 x 1080.

### Wie konvertiere ich DOCX in C# in ein PDF?

Rufe `ConvertDocumentAsync("report.docx", new ConvertDocumentOptions { SaveTo = "report.pdf" })` auf. PDF ist das Standard-Ausgabeformat, `OutputFormat` ist hier also optional. Dieselbe Methode verarbeitet XLSX, PPTX, ODF, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML und TOML als Eingabe.

### Wie scrape ich eine Webseite mit dem C# SDK?

Nutze den V2-Namensraum: `await client.V2.PerceiveAsync(url, new PerceiveOptions { Outputs = new[] { "markdown", "structured" } })`. Du bekommst Markdown, bereinigtes oder rohes HTML, Screenshots, PDF, Links, Bilder und strukturierte Extraktion, jeweils als signiertes Artefakt, plus einen `RenderQuality`-Wert für den Lesevorgang. Um die URLs einer Website zuerst ohne Rendering aufzuzählen, rufe `DiscoverAsync` auf.

### Was bedeutet Render Quality und warum steht sie an jedem Lesevorgang?

`RenderQuality` ist ein Ehrlichkeitswert von 0.0 bis 1.0, der an jedem V2-Lesevorgang hängt. Ein niedriger Wert bedeutet, dass die Seite nicht sauber gerendert wurde: eine Bot-Challenge, eine Cookie- oder Login-Wall, ein HTTP-Fehler oder eine leere Single-Page-App-Hülle. Der Inhalt kommt trotzdem zurück, zusammen mit einer `Deductions`-Map, die benennt, welche Prüfungen angeschlagen haben, und einer `Warnings`-Liste, sodass dein Agent einen schlechten Lesevorgang ablehnen kann, statt ihn als Tatsache zu behandeln.

### Behandelt das SDK Konvertierungen, die das Proxy-Timeout überdauern?

Ja. Es sendet mit jeder Konvertierung einer einzelnen Datei oder einer einzelnen URL eine erzeugte Job-ID, und wenn die Anfrage 5xx zurückgibt, fragt es `GET /v1/convert/status/{jobId}` alle 3 Sekunden für bis zu 5 Minuten ab. Erfolg gibt das normale Ergebnis zurück, ein erfasster Fehlschlag wirft eine `ApiException`, und das Überschreiten der Frist wirft `ApiException(504, "Conversion timed out")`. Website-Batch-Übermittlungen sind bewusst ausgenommen.

### Kann ich dieses SDK aus Blazor WebAssembly oder einer Mobile-App nutzen?

Nein. Der Client authentifiziert sich mit einem privaten API-Key, der niemals in Code ausgeliefert werden darf, den ein Nutzer lesen kann. Betreibe ihn aus ASP.NET Core, einem Worker Service, einer Azure Function oder einem beliebigen anderen serverseitigen .NET-8-Host, und lass dein Frontend stattdessen deinen eigenen Endpunkt aufrufen.

### Welche Bildkonvertierungen werden unterstützt?

Jedes Paar aus `jpeg`, `png`, `svg`, `heic` und `webp`, also 20 Kombinationen, plus die Rasterung von `pdf` zu `jpeg`. Das Eingabeformat ergibt sich aus der Dateiendung, und nicht unterstützte Paare werfen eine `ArgumentException`, bevor ein Netzwerkaufruf stattfindet. Prüfe die Tabelle selbst mit `Formats.ValidOutputsFor("heic")`.

### Wie verwandle ich eine Doku-Website in RAG-fertige Chunks?

Rufe `client.V2.IngestAsync(new IngestOptions { Mode = "sitemap", Url = "https://docs.example.com", MaxPages = 100 })` auf, frage dann `GetIngestJobAsync` ab, bis `Status` den Wert `"completed"` hat, und lies `OutputUrl` für das JSONL. Für lokale Dokumente schickt `IngestFilesAsync` Uploads durch denselben Chunker. Stimme `Chunk.MaxWords` und `Chunk.SentenceOverlap` auf dein Embedding-Modell ab.

### Wie lange sind die Download-URLs gültig?

V2-Artefakt-URLs sind 15 Minuten lang signiert (`ExpiresIn` beträgt 900 Sekunden) und werden bei jedem Abruf der Operation mit `GetPerceiveOperationAsync` neu signiert. Konvertierungsergebnisse liefern ebenfalls eine vorsignierte URL. In beiden Fällen gilt: Wenn die Datei die Signatur überdauern soll, lade sie herunter und lege sie in deinem eigenen Bucket ab.
