---
seo_title: Swift SDK für Dateikonvertierung: async/await-Client | EnConvert
meta_desc: Offizielles EnConvert Swift SDK für macOS, iOS, tvOS und watchOS. async/await-Methoden für Dateikonvertierung und zum Perceive, Discover und Distill von Webseiten.
keywords: swift sdk dateikonvertierung, dateien konvertieren swift, url zu pdf swift, swift web scraping api, docx in pdf konvertieren swift, enconvert swift sdk, heic in webp konvertieren swift, swift async await api client, ios api dateikonvertierung, swift package manager pdf bibliothek, website screenshot swift, strukturierte daten extrahieren swift
---

# Swift SDK für Dateikonvertierung

`Enconvert` ist der offizielle EnConvert-Client für Swift, verteilt über den Swift Package Manager. Er basiert auf `URLSession` mit `async`/`await`, hat null externe Abhängigkeiten und zielt auf Swift 5.9 und neuer unter macOS 12, iOS 15, tvOS 15 und watchOS 8. Zwölf Methoden am Client decken Datei-Konvertierung und URL-Rendering ab (DOCX zu PDF, HEIC zu WebP, URL zu PDF, URL zu Markdown, Batches für ganze Websites), und der Namespace `client.v2` ergänzt dreiundzwanzig Web-Intelligence-Methoden für Perceive, Discover, Lookup, Distill, Ingest und Watch.

<div class="alert alert-info">
<strong>Paket:</strong> <code>Enconvert</code> · <strong>Quelle:</strong> <a href="https://github.com/conversionapi/swift-sdk">conversionapi/swift-sdk</a> · <strong>Swift:</strong> 5.9+ · <strong>Plattformen:</strong> macOS 12+, iOS 15+, tvOS 15+, watchOS 8+ · <strong>Abhängigkeiten:</strong> keine
</div>

---

## Installation

Füge das Paket hinzu und liste danach das Produkt in dem Target auf, das es nutzt:

```swift
dependencies: [
    .package(url: "https://github.com/conversionapi/swift-sdk.git", from: "0.0.1")
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "Enconvert", package: "swift-sdk")
    ])
]
```

In Xcode nutzt du **File > Add Package Dependencies** und fügst `https://github.com/conversionapi/swift-sdk.git` ein. Unter Linux importiert das SDK `FoundationNetworking` bedingt, du musst also nichts zusätzlich tun.

---

## Schnellstart

```swift
import Enconvert

let apiKey = ProcessInfo.processInfo.environment["ENCONVERT_API_KEY"] ?? ""
let client = try Enconvert(apiKey: apiKey)

let result = try await client.convertUrlToPdf("https://example.com", options: UrlToPdfOptions(saveTo: "page.pdf"))
print(result.filename, result.presignedUrl)
```

`Enconvert.init` wirft, es ist kein Failable Initializer: Ein leerer `apiKey` löst `EnconvertError.invalidArgument` aus, bevor irgendetwas das Netzwerk berührt. Jede Anfragemethode ist `async throws`, und die Konvertierungsmethoden sind mit `@discardableResult` markiert, sodass ein Aufruf, der nur wegen des `saveTo`-Nebeneffekts erfolgt, keine Warnung erzeugt.

---

## Was der Client bereitstellt

Zwölf Methoden hängen an `Enconvert` und bilden REST-Endpunkte 1:1 ab:

| Methode | Endpunkt | Rückgabe |
|--------|----------|---------|
| `convertUrlToPdf(_:options:)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `convertUrlToScreenshot(_:options:)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `convertUrlToMarkdown(_:options:)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `convertImage(_:options:)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `convertDocument(_:options:)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `convertToMarkdown(_:options:)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `convertToPdf(_:options:)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `convertWebsiteToPdf(_:options:)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `convertWebsiteToScreenshot(_:options:)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `getJobStatus(_:)` | `GET /v1/convert/status/{jobId}` | `JobStatus` |
| `getBatchStatus(_:)` | `GET /v1/convert/batch/{batchId}` | `BatchStatus` |
| `waitForBatch(_:options:)` | `GET /v1/convert/batch/{batchId}` (mit Polling) | `BatchStatus` |

`client.v2` ist ein `EnconvertV2`-Namespace mit dreiundzwanzig weiteren Methoden in sechs Fähigkeitsgruppen:

| Gruppe | Methoden | Basispfad |
|-------|---------|-----------|
| Perceive | `perceive`, `perceiveDirect`, `getPerceiveOperation`, `downloadPerceiveArtifact`, `perceiveBatch`, `getPerceiveBatch` | `/v2/perceive` |
| Discover | `discover` | `/v2/discover` |
| Lookup | `lookup` | `/v2/lookup` |
| Distill | `distill` | `/v2/distill` |
| Ingest | `ingest`, `ingestFiles`, `getIngestJob`, `listIngestJobs`, `cancelIngestJob`, `retryIngestWebhook`, `getWebhookSecret`, `rotateWebhookSecret` | `/v2/ingest` |
| Watch | `createWatcher`, `listWatchers`, `getWatcher`, `getWatcherSnapshots`, `updateWatcher`, `deleteWatcher` | `/v2/watch` |

Optionen werden als Struct mit vorbelegten Initializer-Parametern übergeben, `UrlToPdfOptions()` heißt also "alle Standardwerte", und du benennst nur die Felder, die dich interessieren. Swift verlangt benannte Argumente in Deklarationsreihenfolge, halte `saveTo:` also vor `singlePage:` und `pdfOptions:`, wenn du mehrere auf einmal setzt.

---

## Datei-Konvertierung

Uploads nehmen ein `FileInput` entgegen:

| Case | Wofür du ihn nutzt |
|------|-----------|
| `.path("report.docx")` | Eine Datei auf der Platte. Der Basisname bestimmt Eingabeformat und MIME-Typ. |
| `.data(bytes)` | Rohbytes ohne Namen. Werden als `upload.bin`, `application/octet-stream` hochgeladen. |
| `.wrapped(data: bytes, filename: "report.docx", contentType: nil)` | Rohbytes plus einen expliziten Dateinamen. Ein `contentType` von `nil` wird aus der Endung abgeleitet. |

### convertUrlToPdf

Rendere jede öffentliche URL zu einem PDF.

```swift
let result = try await client.convertUrlToPdf("https://example.com", options: UrlToPdfOptions(
    viewportWidth: 1440,
    saveTo: "report.pdf",
    singlePage: false,
    pdfOptions: PdfOptions(pageSize: "A4", orientation: .landscape)
))
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `viewportWidth`, `viewportHeight` | `Int?` | `1920`, `1080` | Größe des Browser-Viewports in Pixeln. |
| `loadMedia`, `enableScroll` | `Bool?` | `true` | Auf Bilder und Videos warten; von oben nach unten scrollen, damit Lazy Loader auslösen. |
| `outputFilename` | `String?` | automatisch | Überschreibt den generierten Dateinamen. |
| `auth`, `cookies`, `headers` | `HttpBasicAuth?`, `[BrowserCookie]?`, `[String: String]?` | keine | HTTP-Basic-Zugangsdaten, eingeschleuste Cookies (max. 50), zusätzliche Request-Header (max. 20, Hop-by-Hop wird abgelehnt). |
| `saveTo` | `String?` | keiner | Lokaler Pfad, unter dem das PDF geschrieben wird. Übergeordnete Verzeichnisse werden angelegt. |
| `singlePage` | `Bool?` | `true` | `true` erzeugt eine einzige durchgehende Seite. `false` paginiert anhand von `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions?` | keine | Seitengeometrie. Siehe [PDF-Optionen](#pdf-optionen). |

Seiten hinter einem Login nehmen Zugangsdaten, Cookies oder Header entgegen:

```swift
_ = try await client.convertUrlToPdf("https://internal.example.com/report", options: UrlToPdfOptions(
    auth: HttpBasicAuth(username: "user", password: "pass"),
    cookies: [BrowserCookie(name: "session", value: "abc123", domain: "internal.example.com")],
    headers: ["X-Tenant": "acme"],
    saveTo: "report.pdf"
))
```

Kombiniere `auth` nicht mit einem `Authorization`-Eintrag in `headers`. Die API weist den Konflikt zurück.

### convertUrlToScreenshot

Nimm ein PNG von einer beliebigen URL auf.

```swift
let shot = try await client.convertUrlToScreenshot(
    "https://example.com",
    options: UrlToScreenshotOptions(viewportWidth: 1440, saveTo: "shot.png")
)
```

`UrlToScreenshotOptions` akzeptiert dieselben Felder für Viewport, Medien, Scrollen, Dateiname und Browser-Zugriff wie `UrlToPdfOptions`, nur ohne `singlePage` und `pdfOptions`.

### convertUrlToMarkdown

Extrahiere sauberes GitHub-Flavored Markdown aus einer URL. Der Konverter entfernt Navigation, Fußzeilen, Werbung und Skripte, behält den eigentlichen Artikeltext und stellt ein YAML-Frontmatter mit Titel, Beschreibung, URL, Links und Bildern voran. Nützlich für RAG-Pipelines, den Import fremder Inhalte in ein CMS oder das Erzeugen von Trainingsdaten.

```swift
_ = try await client.convertUrlToMarkdown("https://example.com/article", options: UrlToMarkdownOptions(saveTo: "article.md"))
```

### convertImage

Konvertiere zwischen `jpeg`, `png`, `svg`, `heic` und `webp`, oder rastere ein PDF nach JPEG.

```swift
let result = try await client.convertImage(.path("photo.heic"), options: ConvertImageOptions(outputFormat: "webp", saveTo: "photo.webp"))
```

Das Eingabeformat ergibt sich aus der Dateiendung (`.jpg`, `.jpeg`, `.png`, `.svg`, `.heic`, `.webp` sowie `.pdf` für die Rasterung). `outputFormat` ist erforderlich und akzeptiert die Aliasse `jpg`, `yml`, `htm` und `md`.

| Option | Typ | Erforderlich | Beschreibung |
|--------|------|----------|-------------|
| `outputFormat` | `String` | Ja | Zielformat, zum Beispiel `"webp"`. |
| `saveTo` | `String?` | nein | Lokaler Pfad, unter dem das Ergebnis geschrieben wird. |
| `outputFilename` | `String?` | nein | Überschreibt den generierten Dateinamen. |

### convertDocument

Konvertiere Dokumente und strukturierte Textformate. `outputFormat` steht standardmäßig auf `"pdf"`.

```swift
// docx zu pdf
_ = try await client.convertDocument(.path("report.docx"), options: ConvertDocumentOptions(saveTo: "report.pdf"))

// json zu yaml
_ = try await client.convertDocument(.path("data.json"), options: ConvertDocumentOptions(outputFormat: "yaml", saveTo: "data.yaml"))

// markdown zu pdf mit eigener Seiteneinrichtung
_ = try await client.convertDocument(.path("README.md"), options: ConvertDocumentOptions(
    saveTo: "readme.pdf",
    pdfOptions: PdfOptions(pageSize: "A4", margins: PdfMargins(top: 20, bottom: 20))
))
```

**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`-Dateien also stattdessen durch `convertToPdf` oder `convertToMarkdown`.

Das SDK bringt die vollständige Konvertierungstabelle des Gateways mit und prüft jedes `{input}-to-{output}`-Paar lokal. Ein nicht unterstütztes Paar wirft also `EnconvertError.invalidArgument` samt Liste der gültigen Ausgaben, statt für einen Roundtrip zu zahlen, der garantiert scheitert. Es gibt 43 implementierte 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` | jeweils in die anderen vier (20 geordnete Paare) |
| `pdf` | `jpeg` |

Diese Tabelle kannst du auch selbst abfragen, ohne eine Anfrage zu stellen:

```swift
validOutputsFor("json")                           // ["csv", "toml", "xml", "yaml"]
IMPLEMENTED_CONVERSIONS.contains("heic-to-webp")  // true
```

### convertToMarkdown

Konvertiere ein hochgeladenes Dokument nahezu beliebigen Formats in sauberes Markdown, wobei das Format serverseitig automatisch erkannt wird. Eine gute erste Stufe für eine RAG-Pipeline.

```swift
_ = try await client.convertToMarkdown(.path("handbook.docx"), options: ConvertToMarkdownOptions(saveTo: "handbook.md"))
```

Akzeptiert werden PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie ältere oder ODF-Office-Dateien. Bilder nicht. Dieser Endpunkt kennt keine PDF-Optionen.

### convertToPdf

Konvertiere eine hochgeladene Datei nahezu beliebigen Formats nach PDF: Office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, reiner Text, Rasterbilder, SVG, EPUB oder ein bestehendes PDF, das durchgereicht und normalisiert wird.

```swift
_ = try await client.convertToPdf(.path("slides.pptx"), options: ConvertToPdfOptions(saveTo: "slides.pdf"))

// PDF durchgereicht und in Graustufen umgewandelt
_ = try await client.convertToPdf(
    .path("scan.pdf"),
    options: ConvertToPdfOptions(saveTo: "scan-gray.pdf", pdfOptions: PdfOptions(grayscale: true))
)
```

<div class="alert alert-warning">
<strong>Hier wird ausschließlich <code>grayscale</code> berücksichtigt.</strong> <code>convertToPdf</code> reicht <code>pdfOptions</code> weiter, aber der Anything-to-PDF-Endpunkt liest nur <code>grayscale</code> und ignoriert den Rest. Nutze <code>convertDocument</code> oder <code>convertUrlToPdf</code>, wenn du Seitengröße, Ausrichtung, Ränder, Skalierung, Kopf- oder Fußzeilen brauchst.
</div>

### convertWebsiteToPdf und convertWebsiteToScreenshot

Ermittle jede Seite einer Website, konvertiere jede einzelne im Hintergrund und sammle alles in einem ZIP. Beide Methoden arbeiten ausschließlich asynchron und verlangen einen privaten API-Key mit Crawl-Zugriff.

```swift
let batch = try await client.convertWebsiteToPdf("https://example.com", options: WebsiteToPdfOptions(
    crawlMode: .sitemap,
    excludePatterns: ["/blog/tag/"]
))
print(batch.batchId, batch.urlCount, batch.discoveryMethod ?? "")

// Blockieren, bis der Batch durch ist, und das ZIP speichern
let status = try await client.waitForBatch(batch.batchId, options: WaitForBatchOptions(saveTo: "site.zip"))
print("\(status.completed) of \(status.total) pages converted")

// Oder selbst abfragen
let snapshot = try await client.getBatchStatus(batch.batchId)
if snapshot.status != .processing {
    print(snapshot.zipDownloadUrl ?? "")
}
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `crawlMode` | `CrawlMode?` | `.auto` | `.auto`, `.sitemap` (nur sitemap.xml) oder `.full` (Sitemap plus BFS-Crawl). |
| `includePatterns`, `excludePatterns` | `[String]?` | keine | Positivliste und danach Sperrliste für gefundene URLs. Nur im Full-Crawl-Modus. |
| `notificationEmail` | `String?` | Projektinhaber | Adresse, die benachrichtigt wird, wenn der Batch fertig ist. |
| `callbackUrl` | `String?` | keine | Webhook, der per POST aufgerufen wird, wenn der Batch fertig ist. |
| `singlePage`, `pdfOptions` | `Bool?`, `PdfOptions?` | siehe oben | Nur für PDF-Batches. |

Beide Methoden nehmen außerdem die unter `convertUrlToPdf` gelisteten Felder für Viewport, Medien, Scrollen und Browser-Zugriff entgegen und wenden sie auf jede Seite an. `waitForBatch` fragt standardmäßig alle 5 Sekunden mit einer Frist von 30 Minuten ab; überschreiben kannst du das mit `WaitForBatchOptions(intervalMs:timeoutMs:saveTo:)`. Wird die Frist überschritten, wirft es `EnconvertError.api(statusCode: 504, ...)`. `convertWebsiteToScreenshot` verhält sich identisch und erzeugt ein ZIP mit PNGs.

---

## Web-Intelligence (V2)

Jeder V2-Lesevorgang trägt einen `renderQuality`-Wert von 0.0 bis 1.0, bereitgestellt als `Double?` auf `PerceiveResult`, `PerceiveDirectResult`, `DistillItem` und `WatcherSnapshot`. Ein niedriger Wert bedeutet, dass die Seite nicht ehrlich gerendert hat: eine Bot-Challenge, eine Login-Wall, ein Cookie-Banner über einer leeren SPA-Hülle, ein HTTP-Fehlerstatus. Der Inhalt kommt trotzdem zurück, markiert, neben einem `deductions`-Dictionary, das jeden ausgelösten Abzug benennt, und einem `warnings`-Array, sodass ein schlechter Lesevorgang nie unbemerkt in den Kontext deines Agenten rutscht. Prüfe den Wert, bevor du irgendetwas vertraust:

```swift
if let quality = op.renderQuality, quality < 0.6 {
    print("low quality read of \(op.url): \(op.deductions)")
}
```

### Perceive

Rendere eine URL in genau die Artefakte, die du anforderst. Synchron, mit signierten Artefakt-URLs, die 15 Minuten gültig sind. Jede V2-Methode braucht einen privaten API-Key; öffentliche Keys werden abgelehnt.

```swift
let op = try await client.v2.perceive("https://example.com", options: PerceiveOptions(
    outputs: [.markdown, .screenshot, .structured],
    extract: [.tables, .metadata],
    viewport: PerceiveViewport(width: 1440)
))
print(op.operationId, op.renderQuality ?? 0, op.outputs["markdown"]?.url ?? "")
print(op.structured ?? [:], op.extractionTier ?? .heuristic)

// Die Artefakt-URLs später neu signieren
let again = try await client.v2.getPerceiveOperation(op.operationId)
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `outputs` | `[PerceiveOutputName]?` | `[.markdown, .structured]` | `.markdown`, `.htmlCleaned`, `.htmlRaw`, `.screenshot`, `.screenshotFullPage`, `.pdf`, `.links`, `.images`, `.structured`. |
| `extract` | `[PerceiveExtractName]?` | keine | `.tables`, `.prices`, `.contacts`, `.metadata`, `.mainContent`, `.headings`, `.structuredData`, `.technologies`, `.all`. |
| `schema` | `JSONObject?` | keines | JSON-Schema für die strukturierte Extraktion über die LLM-Stufe. |
| `waitFor`, `waitTimeoutMs` | `String?`, `Int?` | keiner, `30000` | Ein CSS-Selektor (optional mit Präfix `css:`) oder `js:<expr>`, auf den gewartet wird, und dessen Budget in ms (0 bis 60000). |
| `jsCode` | `String?` | keiner | JavaScript, das nach der Navigation ausgeführt wird, max. 20000 Zeichen. |
| `viewport` | `PerceiveViewport?` | 1920 mal 1080 | `width` 320 bis 3840, `height` 240 bis 2160. |
| `headers`, `cookies`, `auth` | `[String: String]?`, `[BrowserCookie]?`, `HttpBasicAuth?` | keine | Request-Header, eingeschleuste Cookies, HTTP-Basic-Zugangsdaten. |
| `cacheMode` | `PerceiveCacheMode?` | `.enabled` | `.enabled` (1 Stunde Cache), `.bypass`, `.refresh`. |
| `pdfOptions` | `PdfOptions?` | keine | Nur relevant, wenn `outputs` `.pdf` enthält. |
| `blockResources` | `[PerceiveResourceType]?` | keine | `.image`, `.media`, `.font`, `.stylesheet`, `.script`, `.xhr`, `.fetch`, `.websocket`, `.manifest`, `.other`. |
| `respectRobots`, `mobile` | `Bool?` | Server-Standard | `robots.txt` beachten; ein Mobilgerät emulieren. |
| `onlyMainContent` | `Bool?` | `true` | Entfernt Navigation, Header, Footer und Cookie-Banner aus dem Markdown-Artefakt und dem `main_content`-Extrakt. Setze `false` für die ganze Seite. |
| `directDownload` | `Bool?` | `false` | Streamt Rohbytes statt eines JSON-Envelopes. Bevorzuge `perceiveDirect`. |

<div class="alert alert-warning">
<strong>Drei Optionen sind deklariert, aber noch nicht aktiv.</strong> <code>proxyUrl</code>, <code>geolocation</code> und <code>actionChain</code> existieren auf <code>PerceiveOptions</code> und werden auch übertragen, der Server antwortet aber derzeit für alle drei mit <code>422</code>. Lass sie auf <code>nil</code>.
</div>

Ein einzelnes Artefakt direkt auf die Platte zu streamen, spart den JSON-Envelope und den Umweg über die signierte URL. `perceiveDirect` prüft lokal, dass du genau eine artefakterzeugende Ausgabe angefordert hast, ein Fehler kostet also nichts:

```swift
let direct = try await client.v2.perceiveDirect("https://example.com", options: PerceiveOptions(outputs: [.pdf]))
try direct.content.write(to: URL(fileURLWithPath: direct.filename ?? "page.pdf"))

// Ein gespeichertes Artefakt später erneut laden. Übergib nil, wenn die Operation nur eines erzeugt hat.
let saved = try await client.v2.downloadPerceiveArtifact(direct.operationId, output: .pdf)
```

`PerceiveDirectResult` trägt `content`, `contentType`, `filename`, `operationId`, `objectKey`, `cacheHit`, `renderQuality`, `sourceStatusCode`, `contentHash` und `warningsCount`, alle aus den Response-Headern gelesen. Ein `410` von `downloadPerceiveArtifact` bedeutet, dass das Artefakt aus seiner Aufbewahrungsfrist herausgefallen ist.

Batches nehmen bis zu 1000 URLs mit einem gemeinsamen Optionsblock. Kleine Batches laufen inline durch; größere kommen in der Warteschlange zurück, und du fragst sie ab:

```swift
let batch = try await client.v2.perceiveBatch(
    ["https://a.example", "https://b.example"],
    options: PerceiveBatchOptions(outputs: [.markdown], outputMode: .zip)
)
let done = try await client.v2.getPerceiveBatch(batch.jobId)
if done.status == .completed, let zip = done.zip {
    print(zip.url ?? "")
}
```

`outputMode` ist `.manifest` (Standard) oder `.zip`. `directDownload` wird bei Batches mit `422` abgelehnt.

### Discover

Zähle die URLs einer Website ohne Browser-Rendering auf. Schnell, und es läuft nie ein Rendering.

```swift
let opts = DiscoverOptions(mode: .hybrid, maxUrls: 200, excludePatterns: ["/tag/"])
let found = try await client.v2.discover("https://example.com", options: opts)
print(found.total, found.truncated, found.sources, found.urls)
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `mode` | `DiscoverMode?` | `.hybrid` | `.sitemap`, `.crawl` oder `.hybrid` (Sitemap plus HTTP-Crawl). |
| `maxUrls`, `maxDepth` | `Int?` | `100`, `2` | 1 bis 1000 URLs; Crawl-Tiefe 1 bis 5. |
| `includePatterns`, `excludePatterns` | `[String]?` | keine | Regex-Positivliste, danach die Sperrliste. Jeweils max. 50 Muster. |
| `sameDomainOnly` | `Bool?` | `true` | Auf der Domain der Start-URL bleiben. |
| `respectRobots` | `Bool?` | Server-Standard | `robots.txt` beachten. |

`DiscoverResult` meldet außerdem `pagesCrawled`, `robotsRespected` und `warnings`, und `sources` enthält die rohen Zähler je Quelle, erfasst vor der Deduplizierung.

### Lookup

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

```swift
let search = try await client.v2.lookup(
    "best static site generators",
    options: LookupOptions(category: .web, numResults: 10, perceiveTop: 3)
)
for hit in search.results {
    print(hit.position ?? 0, hit.title ?? "", hit.url ?? "", hit.perceive?.renderQuality ?? 0)
}
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `category` | `LookupCategory?` | `.web` | `.web`, `.news`, `.images`, `.scholar`, `.patents`, `.maps`. |
| `country`, `locale` | `String?` | keine | Google-Ländercode `gl` (`"us"`, `"in"`) und Oberflächensprache `hl` (`"en"`). |
| `timeFilter` | `LookupTimeFilter?` | keiner | `.hour`, `.day`, `.week`, `.month`, `.year`. |
| `numResults`, `page` | `Int?` | `10`, `1` | 1 bis 100 Ergebnisse; Seite 1 bis 10. |
| `location`, `autocorrect` | `String?`, `Bool?` | keiner, `true` | Ort als freier Text wie `"Austin, Texas"`; den Anbieter die Anfrage korrigieren lassen. |
| `perceiveTop` | `Int?` | `0` | Rendert die obersten N Ergebnis-URLs automatisch, 0 bis 10. Jede davon startet ein vollständiges Browser-Rendering. |

`LookupResult` stellt außerdem `answerBox`, `knowledgeGraph`, `perceiveOperationIds` und `credits` bereit.

### Distill

Hole strukturierte Daten aus Seiten heraus, gegen ein Schema, das du selbst definierst.

```swift
let extraction = try await client.v2.distill(DistillOptions(
    urls: ["https://example.com/pricing"],
    schema: ["plans": .string("list of plan names with monthly prices")],
    cssSchema: CssSchema(baseSelector: ".plan-card", fields: [
        CssField(name: "name", type: .text, selector: "h3"),
        CssField(name: "price", type: .text, selector: ".price")
    ])
))

let first = extraction.results[0]
print(first.data ?? [:], first.extractionTier, first.fieldsFromCss, first.fieldsFromLlm)
```

`schema` ist ein `JSONObject`, also ein `[String: JSONValue]`, es funktioniert also sowohl eine flache `{field: description}`-Map als auch ein vollständiges JSON-Schema-Objekt. Das optionale `cssSchema` läuft zuerst und beantwortet alles, was einfache Selektoren erreichen; nur die Felder, die es verfehlt, eskalieren an die LLM-Stufe, und `extractionTier` meldet, welche Stufen tatsächlich geantwortet haben (`.css`, `.llm`, `.mixed` oder `.none`). `CssField.type` ist eines von `.text`, `.attribute`, `.html`, `.regex`, `.nested`, `.list` oder `.nestedList`, verschachtelt bis zu 5 Ebenen tief.

Tausche `urls` gegen `discoverFrom`, um in einem Aufruf erst zu ermitteln und dann zu destillieren. `DistillDiscoverFrom` nimmt `url`, `mode` (Standard `.hybrid`) und `maxPages` (1 bis 50, Standard 10, begrenzt Discovery und Distillation zugleich):

```swift
_ = try await client.v2.distill(DistillOptions(
    discoverFrom: DistillDiscoverFrom(url: "https://example.com", mode: .sitemap, maxPages: 10),
    schema: ["title": .string("page title"), "summary": .string("one-line summary")]
))
```

Gibst du sowohl `urls` als auch `discoverFrom` an oder keines von beidem, wirft es `EnconvertError.invalidArgument`, bevor eine Anfrage rausgeht.

### Ingest

Verwandle eine Website, eine URL-Liste oder einen Stapel hochgeladener Dokumente in gechunktes, RAG-fertiges JSONL. Immer asynchron.

```swift
let job = try await client.v2.ingest(IngestOptions(
    mode: .sitemap,
    url: "https://docs.example.com",
    maxPages: 100,
    chunk: IngestChunkOptions(maxWords: 512, sentenceOverlap: 1),
    webhookUrl: "https://my.app/hooks/enconvert"
))

let status = try await client.v2.getIngestJob(job.jobId)
if status.status == .completed {
    print(status.totalChunks, status.outputUrl ?? "")
}
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `mode` | `IngestMode?` | `.urls` | `.urls`, `.sitemap` oder `.crawl`. Der vierte Fall, `.files`, ist das, was `ingestFiles` an seinem Job zurückmeldet; übergib ihn hier nicht. |
| `url` | `String?` | keine | Start-URL. Erforderlich für `.sitemap` und `.crawl`, verboten für `.urls`. |
| `urls` | `[String]?` | keine | Explizite URLs, max. 1000. Erforderlich für `.urls`, sonst verboten. |
| `maxPages`, `maxDepth` | `Int?` | `50`, `2` | Discovery-Obergrenze für `.sitemap` und `.crawl`, 1 bis 1000; Tiefe 1 bis 5. |
| `sameDomainOnly` | `Bool?` | `true` | Auf der Domain der Start-URL bleiben. |
| `includePatterns`, `excludePatterns` | `[String]?` | keine | Regex-Positivliste, danach die Sperrliste. |
| `respectRobots` | `Bool?` | Server-Standard | `robots.txt` beachten. |
| `waitFor`, `waitTimeoutMs` | `String?`, `Int?` | `30000` ms | Selektor oder `js:`-Ausdruck, auf den pro Seite gewartet wird, und dessen Budget (0 bis 60000). |
| `chunk` | `IngestChunkOptions?` | keine | `maxWords` 32 bis 4000, Standard 512. `sentenceOverlap` 0 bis 10, Standard 1. |
| `webhookUrl` | `String?` | keine | Abschluss-Webhook, HMAC-signiert. |

Die obigen Regeln zu Modus und URL werden clientseitig erzwungen: `ingest` wirft `EnconvertError.invalidArgument`, statt eine aussichtslose Anfrage zu stellen, wenn du `urls` mit `mode: .sitemap` übergibst. Hochgeladene Dateien laufen durch dieselbe Pipeline und denselben Job-Lebenszyklus:

```swift
let files: [FileInput] = [.path("handbook.pdf"), .path("notes.docx")]
let fileJob = try await client.v2.ingestFiles(files, options: IngestFilesOptions(chunk: IngestChunkOptions(maxWords: 512)))
```

Akzeptiert werden PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie ältere oder ODF-Office-Dateien, und mindestens eine Datei ist erforderlich. Job-Verwaltung und Webhook-Infrastruktur:

```swift
let list = try await client.v2.listIngestJobs(V2ListOptions(limit: 20))
let canceled = try await client.v2.cancelIngestJob(job.jobId)   // idempotent

let secret = try await client.v2.getWebhookSecret()
print(secret.signatureHeader, secret.signatureScheme, secret.replayToleranceSeconds)
_ = try await client.v2.rotateWebhookSecret()                   // alte Signaturen gelten sofort nicht mehr

let retry = try await client.v2.retryIngestWebhook(job.jobId)
print(retry.delivered, retry.attempts, retry.detail)
```

`retryIngestWebhook` antwortet mit `409`, wenn der Job nicht abgeschlossen ist, und mit `400`, wenn kein Webhook konfiguriert ist. `V2ListOptions` nimmt `skip` und `limit` (1 bis 100, Standard 20).

### Watch

Rendere eine Seite in festem Takt erneut und lass dich benachrichtigen, wenn sie sich ändert.

```swift
let watcher = try await client.v2.createWatcher("https://example.com/pricing", options: WatchCreateOptions(
    frequencyMinutes: 60,
    diffMode: .auto,
    webhookUrl: "https://my.app/hooks/changes",
    notifyEmail: true
))

let history = try await client.v2.getWatcherSnapshots(watcher.watcherId, options: SnapshotListOptions(limit: 10))
for snapshot in history.snapshots where snapshot.hasChanges {
    print(snapshot.checkedAt, snapshot.changeCount, snapshot.similarity ?? 0)
}
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `frequencyMinutes` | `Int?` | `60` | 60 bis 43200. Die stündliche Untergrenze ist hart. |
| `diffMode` | `WatchDiffMode?` | `.auto` | `.auto`, `.text`, `.structured`, `.tables`, `.metadata`. |
| `trackFields` | `JSONObject?` | keine | Teilmenge von Feldern oder Selektoren, die an die Diff-Engine übergeben wird. |
| `webhookUrl` | `String?` | keiner | Webhook für Änderungsbenachrichtigungen, HMAC-signiert. |
| `notifyEmail` | `Bool?` | `true` | Schickt dem Projektinhaber bei Änderungen eine E-Mail. |

```swift
// Ein leerer String löscht den Webhook; nil lässt ihn unangetastet.
_ = try await client.v2.updateWatcher(watcher.watcherId, updates: WatcherUpdate(webhookUrl: "", status: .paused))

_ = try await client.v2.listWatchers()
_ = try await client.v2.getWatcher(watcher.watcherId)
_ = try await client.v2.deleteWatcher(watcher.watcherId)   // Soft Delete, idempotent
```

`updateWatcher` verlangt mindestens ein Feld und wirft `EnconvertError.invalidArgument` bei einem leeren `WatcherUpdate`. `WatchUpdateStatus` akzeptiert nur `.active` oder `.paused`; das Löschen läuft über `deleteWatcher`, das den auf tot gesetzten Watcher mit Status `.deleted` zurückgibt.

<div class="alert alert-warning">
<strong>Snapshot-Diffs enthalten nicht vertrauenswürdige Seiteninhalte.</strong> <code>WatcherSnapshot.changes</code> ist ein Array roher JSON-Objekte, die von der beobachteten Seite stammen. Escape die Werte, bevor du sie irgendwo renderst.
</div>

---

## PDF-Optionen

`PdfOptions` wird von `convertUrlToPdf`, `convertDocument`, `convertWebsiteToPdf`, `PerceiveOptions` und (nur für `grayscale`) `convertToPdf` gemeinsam genutzt. Es werden nur die Felder gesendet, die du setzt.

```swift
let pdf = PdfOptions(
    pageSize: "A4",
    orientation: .landscape,
    margins: PdfMargins(top: 10, bottom: 10, left: 15, right: 15),
    scale: 0.9,
    grayscale: false,
    header: PdfHeaderFooter(content: "Quarterly Report", height: 15),
    footer: PdfHeaderFooter(content: "Confidential", height: 12)
)
```

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `pageSize` | `String?` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` und Verwandte. |
| `pageWidth`, `pageHeight` | `Double?` | Überschreiben `pageSize`, wenn beide gemeinsam gesetzt sind. |
| `orientation` | `PdfOrientation?` | `.portrait` oder `.landscape`. Standard ist Hochformat. |
| `margins` | `PdfMargins?` | `top`, `bottom`, `left`, `right`, jeweils ein `Double?`. Alle vier optional. |
| `scale` | `Double?` | Render-Skalierung, zum Beispiel `0.9` für 90%. |
| `grayscale` | `Bool?` | Wandelt das PDF nachträglich in Graustufen um. |
| `header` | `PdfHeaderFooter?` | `content` (max. 2000 Zeichen) und `height`. |
| `footer` | `PdfHeaderFooter?` | Dieselbe Form wie `header`. |

---

## Fehlerbehandlung

Swift bekommt genau einen Fehlertyp, `EnconvertError`, modelliert als Enum statt als Klassenhierarchie. Fange ihn mit `catch`-Mustern ab:

```swift
do {
    _ = try await client.v2.perceive("https://example.com")
} catch EnconvertError.authentication(let message) {
    print("invalid or missing API key: \(message)")
} catch EnconvertError.rateLimit(let message) {
    print("too many requests, back off and retry: \(message)")
} catch let error as EnconvertError {
    print("api error: \(error)")   // wird als "[<status>] <message>" dargestellt
}
```

| Case | Ausgelöst bei | Statuscode |
|------|-----------|-------------|
| `.authentication(message:)` | Ungültiger, fehlender oder widerrufener Key | `401`, `403` (beide melden `401`) |
| `.quota(message:)` | Jede Antwort, die die API mit `402` beantwortet | `402` |
| `.rateLimit(message:)` | Rate-Limit überschritten | `429` |
| `.api(statusCode:message:)` | Jede andere 4xx oder 5xx | der tatsächliche Code |
| `.invalidArgument(_:)` | Clientseitige Validierung, vor jeder Anfrage | keiner |

`EnconvertError` erfüllt `CustomStringConvertible` und `LocalizedError`, `String(describing:)`, `localizedDescription` und String-Interpolation stellen also alle `"[<status>] <message>"` dar. Zwei Convenience-Eigenschaften lesen dieselben Werte ohne Pattern Matching: `error.statusCode` (`Int?`, `nil` bei `.invalidArgument`) und `error.message` (der Text ohne das Präfix in eckigen Klammern). Eine wohlgeformte 2xx-Antwort, in der ein vom SDK benötigtes Feld fehlt, erscheint als `.api(statusCode: 0, ...)`, was fehlerhafte Nutzdaten von einem echten HTTP-Fehler trennt.

Nicht unterstützte Konvertierungspaare, ein `distill`-Aufruf mit sowohl `urls` als auch `discoverFrom`, ein `perceiveDirect`-Aufruf, der zwei Artefakte anfordert, und ein leeres `WatcherUpdate` werfen alle `.invalidArgument`, bevor das Netzwerk berührt wird. Die Antwortcodes sind in der [Fehlercode-Referenz](/de/docs/error-codes) katalogisiert.

---

## Timeout-Recovery

Lange URL-Renderings und große Dokumentkonvertierungen können die Obergrenze von 60 bis 120 Sekunden eines Reverse Proxy überdauern, selbst wenn der Job auf dem Server sauber durchläuft. Das SDK fragt sich da wieder heraus, ganz ohne Code von dir:

1. Vor jeder Einzeldatei- und Einzel-URL-Konvertierung erzeugt der Client eine UUIDv4, entfernt die Bindestriche und sendet sie als `job_id`.
2. Kommt diese Anfrage mit einem Status ab 500 zurück, wechselt der Client still auf `GET /v1/convert/status/{job_id}` und fragt alle 3 Sekunden ab. Ein `404` heißt dort "noch nicht erfasst" und hält die Schleife am Laufen.
3. Bei `success` gibt er das Ergebnis zurück. Bei `failed` wirft er `.api(statusCode: 500, message:)` mit der Meldung des Servers. Die Abfragefrist beträgt 5 Minuten, danach bekommst du `.api(statusCode: 504, message: "Conversion timed out")`.

`ConversionResult.jobId` wird vom Client auch dann nachgetragen, wenn der synchrone Pfad erfolgreich war und die Antwort sie weggelassen hat, du kannst sie also selbst an `getJobStatus` übergeben:

```swift
let status = try await client.getJobStatus(result.jobId ?? "")
if status.status == .success {
    print(status.presignedUrl ?? "")
} else if status.status == .failed {
    print(status.error ?? "conversion failed")
}
```

<div class="alert alert-info">
<strong>Website-Batches klinken sich absichtlich aus.</strong> <code>convertWebsiteToPdf</code> und <code>convertWebsiteToScreenshot</code> haben keine Job-Zeile zum Abfragen, ein 5xx kommt dort also sofort durch, statt wiederholt zu werden. V2-Methoden nutzen den Job-Fallback ebenfalls nicht.
</div>

---

## Konfiguration

```swift
let client = try Enconvert(
    apiKey: ProcessInfo.processInfo.environment["ENCONVERT_API_KEY"] ?? "",
    baseURL: "https://api.enconvert.com",
    timeout: 300
)
```

| Parameter | Typ | Standard | Beschreibung |
|-----------|------|---------|-------------|
| `apiKey` | `String` | erforderlich | Privater API-Key. Ein leerer String wirft `EnconvertError.invalidArgument`. |
| `baseURL` | `String` | `https://api.enconvert.com` | Überschreibung für ein selbst gehostetes Gateway. Abschließende Schrägstriche werden entfernt. |
| `timeout` | `TimeInterval` | `300` | Sekunden. Setzt sowohl `timeoutIntervalForRequest` als auch `timeoutIntervalForResource` an der internen `URLSession`. |

Der Key reist bei jedem API-Aufruf als Header `X-API-Key` mit. Vorsignierte Downloads gehen bewusst ohne ihn raus, denn eine signierte Storage-URL authentifiziert sich selbst, und den Key an einen Storage-Host weiterzureichen würde ihn preisgeben. Für das Abbrechen einzelner Aufrufe verpackst du den Aufruf in einen `Task` und brichst diesen ab: Jede Methode ist eine ganz normale `async throws`-Funktion. `Enconvert` speichert nur `let`-Eigenschaften über einer `URLSession`, baue also beim Start einen einzigen Client und nutze ihn wieder; `client.v2` ist ein dünner Namespace über demselben Transport.

<div class="alert alert-warning">
<strong>Schreibe den API-Key niemals fest in den Code und liefere ihn nie in einem App-Bundle aus.</strong> Lies ihn aus der Umgebung oder deinem Secret-Manager und halte den Client auf einem Server, den du kontrollierst. Das Paket baut für iOS, tvOS und watchOS, damit du Modellcode über Targets hinweg teilen kannst, aber ein App-Binary ist ein öffentliches Artefakt: Wer deinen privaten Key extrahiert, kann Konvertierungen gegen dein Projekt ausführen. Lass die App dein eigenes Backend aufrufen und das Backend EnConvert. Siehe <a href="/de/docs/authentication">Authentifizierung</a> für Key-Typen und Rotation.
</div>

---

## Ergebnisform

Einzeldatei- und Einzel-URL-Konvertierungen liefern ein `ConversionResult`:

```swift
public struct ConversionResult: Codable, Equatable, Sendable {
    public let presignedUrl: String
    public let objectKey: String
    public let filename: String
    public let fileSize: Int?
    public let conversionTimeSeconds: Double?
    public let jobId: String?
}
```

Vorsignierte URLs sind kurzlebig. Übergib `saveTo`, damit das SDK die Bytes für dich auf die Platte streamt und dabei übergeordnete Verzeichnisse anlegt, oder hole die URL selbst und speichere die Datei für den langfristigen Zugriff in deinem eigenen Bucket.

V2-Artefakte kommen als `V2OutputArtifact`-Werte an, geschlüsselt nach Ausgabename, jeder mit `url` (`String?`, 15 Minuten lang vorsigniert und bei jedem Status-GET neu signiert), `objectKey`, `sizeBytes`, `contentType` und `expiresIn` (Sekunden, standardmäßig 900). `PerceiveResult` umschließt sie mit den Ehrlichkeitsmetadaten: `renderQuality`, `statusCode`, `deductions`, `cacheHit`, `warnings`, `contentHash`, `urlFinal`, `structured`, `extractionTier`, `tokens`, `costCents`, `durationMs` und `optionsEcho`, das die vom Server tatsächlich berücksichtigten Optionen zurückspiegelt, wobei Geheimnisse auf Booleans reduziert sind. Vom Aufrufer definierte Nutzdaten (Extraktionsschemata, destillierte `data`, `trackFields` eines Watchers, `changes` eines Diffs, `extra` einer Suche) laufen über `JSONValue` hin und zurück, ein Enum mit den Fällen `.null`, `.bool`, `.number`, `.string`, `.array` und `.object`, dazu der Alias `JSONObject` für `[String: JSONValue]`. Jeder Ergebnistyp ist `Codable`, `Equatable` und `Sendable`, ein geparstes Ergebnis auf die Platte zu cachen und später neu zu laden funktioniert also sofort.

---

## Quelle und Issues

- **Paket:** `Enconvert`, über den Swift Package Manager. Die Version steht zur Laufzeit als modulweite Konstante `VERSION` bereit
- **GitHub:** [conversionapi/swift-sdk](https://github.com/conversionapi/swift-sdk)
- **Lizenz:** MIT. Abhängigkeiten: keine, nur `URLSession` und Foundation

Weiterführend: [alle SDKs](/de/docs/sdks), [V2-Übersicht](/de/docs/v2-overview), [Perceive](/de/docs/v2-perceive), [Discover](/de/docs/v2-discover), [Lookup](/de/docs/v2-lookup), [Distill](/de/docs/v2-distill), [Ingest](/de/docs/v2-ingest), [Watch](/de/docs/v2-watch), [Endpunkt-Übersicht](/de/docs/endpoints-overview), [Parameter und Optionen](/de/docs/parameters-options) und dein [Dashboard](/de/dashboard) für die Keys.

---

## Häufig gestellte Fragen

### Wie konvertiere ich Dateien in Swift?

Füge `https://github.com/conversionapi/swift-sdk.git` den Abhängigkeiten in deiner `Package.swift` hinzu, baue einen Client mit `try Enconvert(apiKey:)` und rufe dann eine typisierte Methode wie `convertDocument`, `convertImage` oder `convertUrlToPdf` auf. Übergib `saveTo` im Options-Struct, und das SDK streamt die fertige Datei direkt an diesen Pfad und legt dabei übergeordnete Verzeichnisse an.

### Wie konvertiere ich eine URL in Swift in ein PDF?

Rufe `try await client.convertUrlToPdf("https://example.com", options: UrlToPdfOptions(saveTo: "page.pdf"))` auf. Setze `singlePage: false`, um zu paginieren statt eine einzige durchgehende Seite zu erzeugen, und übergib `pdfOptions:` für Seitengröße, Ausrichtung, Ränder, Skalierung, Graustufen, Kopf- und Fußzeilen. Denk daran, dass Swift die Labels in Deklarationsreihenfolge erwartet, `saveTo:` kommt also vor `singlePage:` und `pdfOptions:`.

### Wie konvertiere ich DOCX in Swift nach PDF?

`try await client.convertDocument(.path("report.docx"), options: ConvertDocumentOptions(saveTo: "report.pdf"))`. Das Ausgabeformat ist standardmäßig `"pdf"`, `outputFormat` kann also entfallen. Dieselbe Methode verarbeitet Eingaben in XLSX, PPTX, ODT, ODS, ODP, OTS, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML und TOML.

### Wie konvertiere ich HEIC in Swift nach WebP?

`try await client.convertImage(.path("photo.heic"), options: ConvertImageOptions(outputFormat: "webp", saveTo: "photo.webp"))`. Das Eingabeformat wird aus der Dateiendung gelesen, und alle 20 geordneten Paare unter `jpeg`, `png`, `svg`, `heic` und `webp` funktionieren genauso. Nicht unterstützte Paare werfen lokal `EnconvertError.invalidArgument`, bevor eine Anfrage rausgeht.

### Zieht das Swift SDK irgendwelche Fremdabhängigkeiten mit?

Nein. `Package.swift` deklariert ein leeres `dependencies`-Array. Alles läuft über `URLSession`, `JSONSerialization` und `Foundation`, wobei `FoundationNetworking` bedingt importiert wird, damit das Paket unter Linux ebenso baut wie auf Apple-Plattformen.

### Wie hole ich eine Webseite in Swift als sauberes Markdown?

Zwei Möglichkeiten. `client.convertUrlToMarkdown` liefert GitHub-Flavored Markdown mit YAML-Frontmatter und ist der einfachste Weg. `client.v2.perceive` mit `outputs: [.markdown]` liefert dasselbe Markdown plus einen `renderQuality`-Wert, eine `deductions`-Map, `warnings` und die Möglichkeit, Screenshots, Links oder strukturierte Extraktion im selben Rendering zu ergänzen.

### Was bedeutet Render-Qualität, und warum sollte ich sie prüfen?

`renderQuality` ist ein `Double?` von 0.0 bis 1.0, das an jedem V2-Lesevorgang hängt. Es sinkt, wenn die Seite nicht ehrlich gerendert hat: eine Bot-Challenge, eine Login-Wall, ein Cookie-Banner über einer leeren Hülle oder ein HTTP-Fehlerstatus. Der Inhalt wird trotzdem zurückgegeben statt verschluckt, prüfe also den Wert und das `deductions`-Dictionary, das jeden Abzug benennt, bevor du den Text an ein Modell gibst.

### Kann ich das Swift SDK innerhalb einer iOS- oder macOS-App nutzen?

Nur hinter deinem eigenen Backend. Das Paket baut für iOS 15, tvOS 15, watchOS 8 und macOS 12, damit du Modellcode über Targets hinweg teilen kannst, aber es authentifiziert sich mit einem privaten API-Key, und V2-Endpunkte lehnen öffentliche Keys rundweg ab. Diesen Key in einem App-Binary auszuliefern, gibt ihn jedem in die Hand, der das Bundle entpackt. Rufe aus der App deinen eigenen Server auf und EnConvert vom Server aus.

### Was passiert, wenn eine lange Konvertierung das Proxy-Timeout erreicht?

Das SDK sendet mit jeder Einzeldatei- und Einzel-URL-Konvertierung eine clientseitig erzeugte `job_id`. Antwortet die Anfrage mit 500 oder höher, fragt es `GET /v1/convert/status/{job_id}` alle 3 Sekunden für bis zu 5 Minuten ab, gibt bei `success` das Ergebnis zurück und wirft bei `failed` `.api(statusCode: 500, ...)`. Wird die Frist überschritten, bekommst du `.api(statusCode: 504, message: "Conversion timed out")`. Batch-Einreichungen für ganze Websites überspringen diesen Fallback absichtlich.

### Wie erfahre ich vor dem Senden einer Anfrage, welche Konvertierungen unterstützt werden?

Rufe `validOutputsFor("json")` für die Ausgaben auf, die ein bestimmtes Eingabeformat unterstützt, oder prüfe die Mitgliedschaft in `IMPLEMENTED_CONVERSIONS`, der Menge aller 43 implementierten `{input}-to-{output}`-Endpunkte. `convertImage` und `convertDocument` führen intern dieselbe Prüfung aus und werfen `EnconvertError.invalidArgument` samt Liste der für diese Eingabe gültigen Ausgaben, bevor eine Anfrage rausgeht.
