---
seo_title: SDK Swift de conversion de fichiers en async await | EnConvert
meta_desc: SDK Swift officiel d'EnConvert pour macOS, iOS, tvOS et watchOS. Méthodes async await pour convertir des fichiers et pour percevoir, découvrir et distiller des pages web.
keywords: sdk swift conversion de fichiers, convertir des fichiers en swift, url vers pdf en swift, api de scraping web swift, docx vers pdf en swift, enconvert sdk swift, heic vers webp en swift, client api swift async await, api de conversion de fichiers ios, bibliothèque pdf swift package manager, capture d'écran de site web en swift, extraction de données structurées en swift
---

# SDK Swift de conversion de fichiers

`Enconvert` est le client EnConvert officiel pour Swift, distribué via Swift Package Manager. Il est bâti sur `URLSession` avec `async`/`await`, ne porte aucune dépendance externe, et cible Swift 5.9 et plus récent sur macOS 12, iOS 15, tvOS 15 et watchOS 8. Douze méthodes du client couvrent la conversion de fichiers et le rendu d'URL (DOCX vers PDF, HEIC vers WebP, URL vers PDF, URL vers Markdown, lots à l'échelle d'un site entier), et l'espace de noms `client.v2` ajoute vingt-trois méthodes d'intelligence web pour percevoir, découvrir, rechercher, distiller, ingérer et surveiller des pages.

<div class="alert alert-info">
<strong>Paquet :</strong> <code>Enconvert</code> · <strong>Source :</strong> <a href="https://github.com/conversionapi/swift-sdk">conversionapi/swift-sdk</a> · <strong>Swift :</strong> 5.9+ · <strong>Plateformes :</strong> macOS 12+, iOS 15+, tvOS 15+, watchOS 8+ · <strong>Dépendances :</strong> aucune
</div>

---

## Installation

Ajoutez le paquet, puis listez le produit dans la cible qui l'utilise :

```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")
    ])
]
```

Dans Xcode, utilisez **File > Add Package Dependencies** et collez `https://github.com/conversionapi/swift-sdk.git`. Sous Linux, le SDK importe `FoundationNetworking` de manière conditionnelle : rien de plus n'est nécessaire de votre côté.

---

## Démarrage rapide

```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` est throwing, pas failable : un `apiKey` vide lève `EnconvertError.invalidArgument` avant que quoi que ce soit ne touche au réseau. Chaque méthode de requête est `async throws`, et les méthodes de conversion sont marquées `@discardableResult` pour qu'un appel effectué uniquement pour son effet de bord `saveTo` ne produise pas d'avertissement.

---

## Ce que le client expose

Douze méthodes sont accrochées à `Enconvert` et correspondent 1:1 à des endpoints REST :

| Méthode | Endpoint | Renvoie |
|--------|----------|---------|
| `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}` (interrogé en boucle) | `BatchStatus` |

`client.v2` est un espace de noms `EnconvertV2` qui contient vingt-trois méthodes supplémentaires réparties sur six groupes de capacités :

| Groupe | Méthodes | Chemin de base |
|-------|---------|-----------|
| 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` |

Les options sont passées sous forme de struct dont les paramètres d'initialisation ont des valeurs par défaut : `UrlToPdfOptions()` signifie donc « tout par défaut » et vous ne nommez que les champs qui vous intéressent. Swift exige des arguments étiquetés dans l'ordre de déclaration : gardez donc `saveTo:` avant `singlePage:` et `pdfOptions:` lorsque vous en définissez plusieurs à la fois.

---

## Conversion de fichiers

Les envois acceptent un `FileInput` :

| Cas | À utiliser pour |
|------|-----------|
| `.path("report.docx")` | Un fichier sur le disque. Le nom de base détermine le format d'entrée et le type MIME. |
| `.data(bytes)` | Des octets bruts sans nom. Envoyés en tant que `upload.bin`, `application/octet-stream`. |
| `.wrapped(data: bytes, filename: "report.docx", contentType: nil)` | Des octets bruts plus un nom de fichier explicite. Un `contentType` à `nil` est déduit de l'extension. |

### convertUrlToPdf

Effectue le rendu de n'importe quelle URL publique en 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 | Type | Défaut | Description |
|--------|------|---------|-------------|
| `viewportWidth`, `viewportHeight` | `Int?` | `1920`, `1080` | Taille de la fenêtre d'affichage du navigateur, en pixels. |
| `loadMedia`, `enableScroll` | `Bool?` | `true` | Attend les images et les vidéos ; fait défiler la page de haut en bas pour déclencher le chargement paresseux. |
| `outputFilename` | `String?` | auto | Remplace le nom de fichier généré. |
| `auth`, `cookies`, `headers` | `HttpBasicAuth?`, `[BrowserCookie]?`, `[String: String]?` | aucun | Identifiants HTTP Basic, cookies injectés (50 au maximum), en-têtes de requête supplémentaires (20 au maximum, hop-by-hop rejetés). |
| `saveTo` | `String?` | aucun | Chemin local où écrire le PDF. Les répertoires parents sont créés. |
| `singlePage` | `Bool?` | `true` | `true` produit une seule page continue. `false` pagine en utilisant `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions?` | aucun | Géométrie de page. Voir [Options PDF](#options-pdf). |

Les pages derrière une authentification acceptent des identifiants, des cookies ou des en-têtes :

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

Ne combinez pas `auth` avec une entrée `Authorization` dans `headers`. L'API rejette le conflit.

### convertUrlToScreenshot

Capture un PNG de n'importe quelle URL.

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

`UrlToScreenshotOptions` accepte les mêmes champs de fenêtre d'affichage, de médias, de défilement, de nom de fichier et d'accès navigateur que `UrlToPdfOptions`, sans `singlePage` ni `pdfOptions`.

### convertUrlToMarkdown

Extrait un Markdown GitHub-Flavored propre à partir d'une URL. Le convertisseur supprime la navigation, les pieds de page, les publicités et les scripts, conserve le corps principal de l'article, et ajoute en tête un frontmatter YAML avec le titre, la description, l'url, les liens et les images. Utile pour les pipelines RAG, pour importer du contenu tiers dans un CMS, ou pour générer des données d'entraînement.

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

### convertImage

Convertit entre `jpeg`, `png`, `svg`, `heic` et `webp`, ou rastérise un PDF en JPEG.

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

Le format d'entrée provient de l'extension du nom de fichier (`.jpg`, `.jpeg`, `.png`, `.svg`, `.heic`, `.webp`, et `.pdf` pour la rastérisation). `outputFormat` est obligatoire et accepte les alias `jpg`, `yml`, `htm` et `md`.

| Option | Type | Obligatoire | Description |
|--------|------|----------|-------------|
| `outputFormat` | `String` | Oui | Format cible, par exemple `"webp"`. |
| `saveTo` | `String?` | non | Chemin local où écrire le résultat. |
| `outputFilename` | `String?` | non | Remplace le nom de fichier généré. |

### convertDocument

Convertit des documents et des formats texte structurés. `outputFormat` vaut `"pdf"` par défaut.

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

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

// markdown vers pdf avec une mise en page personnalisée
_ = try await client.convertDocument(.path("README.md"), options: ConvertDocumentOptions(
    saveTo: "readme.pdf",
    pdfOptions: PdfOptions(pageSize: "A4", margins: PdfMargins(top: 20, bottom: 20))
))
```

**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`. EPUB n'a pas de paire documentaire dédiée : faites plutôt passer les fichiers `.epub` par `convertToPdf` ou `convertToMarkdown`.

Le SDK embarque la table de conversion complète de la passerelle et valide chaque paire `{input}-to-{output}` en local : une paire non prise en charge lève donc `EnconvertError.invalidArgument` avec la liste des sorties valides, plutôt que de payer un aller-retour voué à l'échec. Il y a 43 paires implémenté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` | chacun vers les quatre autres (20 paires ordonnées) |
| `pdf` | `jpeg` |

Vous pouvez interroger cette table vous-même sans effectuer de requête :

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

### convertToMarkdown

Convertit un document envoyé, de presque n'importe quel format, en Markdown propre, le format étant détecté automatiquement côté serveur. Une bonne première étape pour un pipeline RAG.

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

PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, ainsi que les fichiers bureautiques hérités ou ODF sont acceptés. Les images ne le sont pas. Cet endpoint n'accepte aucune option PDF.

### convertToPdf

Convertit un fichier envoyé, de presque n'importe quel format, en PDF : bureautique, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, texte brut, images matricielles, SVG, EPUB, ou un PDF existant transmis tel quel et normalisé.

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

// PDF en passthrough, converti en niveaux de gris
_ = try await client.convertToPdf(
    .path("scan.pdf"),
    options: ConvertToPdfOptions(saveTo: "scan-gray.pdf", pdfOptions: PdfOptions(grayscale: true))
)
```

<div class="alert alert-warning">
<strong>Seul <code>grayscale</code> est pris en compte ici.</strong> <code>convertToPdf</code> transmet <code>pdfOptions</code>, mais l'endpoint anything-to-pdf lit <code>grayscale</code> et ignore le reste. Utilisez <code>convertDocument</code> ou <code>convertUrlToPdf</code> lorsque vous avez besoin de la taille de page, de l'orientation, des marges, de l'échelle, des en-têtes ou des pieds de page.
</div>

### convertWebsiteToPdf et convertWebsiteToScreenshot

Découvrez toutes les pages d'un site, convertissez-les en arrière-plan et récupérez une archive ZIP unique. Les deux méthodes sont exclusivement asynchrones et exigent une clé API privée disposant de l'accès à l'exploration.

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

// Bloque jusqu'à ce que le lot se stabilise, puis enregistre le ZIP
let status = try await client.waitForBatch(batch.batchId, options: WaitForBatchOptions(saveTo: "site.zip"))
print("\(status.completed) of \(status.total) pages converted")

// Ou interrogez-le vous-même
let snapshot = try await client.getBatchStatus(batch.batchId)
if snapshot.status != .processing {
    print(snapshot.zipDownloadUrl ?? "")
}
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `crawlMode` | `CrawlMode?` | `.auto` | `.auto`, `.sitemap` (sitemap.xml uniquement) ou `.full` (sitemap plus exploration en largeur). |
| `includePatterns`, `excludePatterns` | `[String]?` | aucun | Liste d'autorisation puis liste d'exclusion pour les URL découvertes. Mode d'exploration complète. |
| `notificationEmail` | `String?` | propriétaire du projet | Adresse notifiée à la fin du lot. |
| `callbackUrl` | `String?` | aucun | Webhook appelé en POST à la fin du lot. |
| `singlePage`, `pdfOptions` | `Bool?`, `PdfOptions?` | voir ci-dessus | Lots PDF uniquement. |

Les deux méthodes acceptent également les champs de fenêtre d'affichage, de médias, de défilement et d'accès navigateur listés sous `convertUrlToPdf`, appliqués à chaque page. `waitForBatch` interroge toutes les 5 secondes avec un délai de 30 minutes par défaut ; remplacez-les via `WaitForBatchOptions(intervalMs:timeoutMs:saveTo:)`. Dépasser le délai lève `EnconvertError.api(statusCode: 504, ...)`. `convertWebsiteToScreenshot` se comporte à l'identique et produit un ZIP de PNG.

---

## Intelligence web (V2)

Chaque lecture V2 porte un score `renderQuality` de 0.0 à 1.0, exposé sous forme de `Double?` sur `PerceiveResult`, `PerceiveDirectResult`, `DistillItem` et `WatcherSnapshot`. Un score faible signifie que la page n'a pas été rendue honnêtement : un défi anti-bot, un mur de connexion, un bandeau de cookies posé sur une coquille SPA vide, un statut d'erreur HTTP. Le contenu revient quand même, signalé, à côté d'un dictionnaire `deductions` qui nomme chaque pénalité déclenchée et d'un tableau `warnings` : une mauvaise lecture n'entre donc jamais discrètement dans le contexte de votre agent. Conditionnez-y votre confiance avant de vous fier à quoi que ce soit :

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

### Perceive

Effectue le rendu d'une URL dans les artefacts que vous demandez. Synchrone, avec des URL d'artefacts signées valables 15 minutes. Chaque méthode V2 nécessite une clé API privée ; les clés publiques sont rejetées.

```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)

// Resigner les URL d'artefacts plus tard
let again = try await client.v2.getPerceiveOperation(op.operationId)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `outputs` | `[PerceiveOutputName]?` | `[.markdown, .structured]` | `.markdown`, `.htmlCleaned`, `.htmlRaw`, `.screenshot`, `.screenshotFullPage`, `.pdf`, `.links`, `.images`, `.structured`. |
| `extract` | `[PerceiveExtractName]?` | aucun | `.tables`, `.prices`, `.contacts`, `.metadata`, `.mainContent`, `.headings`, `.structuredData`, `.technologies`, `.all`. |
| `schema` | `JSONObject?` | aucun | Schéma JSON pour l'extraction structurée via le niveau LLM. |
| `waitFor`, `waitTimeoutMs` | `String?`, `Int?` | aucun, `30000` | Un sélecteur CSS (éventuellement préfixé par `css:`) ou `js:<expr>` à attendre, et son budget en ms (0 à 60000). |
| `jsCode` | `String?` | aucun | JavaScript exécuté après la navigation, 20000 caractères maximum. |
| `viewport` | `PerceiveViewport?` | 1920 par 1080 | `width` de 320 à 3840, `height` de 240 à 2160. |
| `headers`, `cookies`, `auth` | `[String: String]?`, `[BrowserCookie]?`, `HttpBasicAuth?` | aucun | En-têtes de requête, cookies injectés, identifiants HTTP Basic. |
| `cacheMode` | `PerceiveCacheMode?` | `.enabled` | `.enabled` (cache d'1 heure), `.bypass`, `.refresh`. |
| `pdfOptions` | `PdfOptions?` | aucun | N'a de sens que lorsque `outputs` inclut `.pdf`. |
| `blockResources` | `[PerceiveResourceType]?` | aucun | `.image`, `.media`, `.font`, `.stylesheet`, `.script`, `.xhr`, `.fetch`, `.websocket`, `.manifest`, `.other`. |
| `respectRobots`, `mobile` | `Bool?` | valeur serveur par défaut | Respecter `robots.txt` ; émuler un appareil mobile. |
| `onlyMainContent` | `Bool?` | `true` | Supprime la navigation, l'en-tête, le pied de page et les bandeaux de cookies de l'artefact markdown et de l'extrait `main_content`. Mettez `false` pour la page entière. |
| `directDownload` | `Bool?` | `false` | Renvoie les octets bruts au lieu d'une enveloppe JSON. Préférez `perceiveDirect`. |

<div class="alert alert-warning">
<strong>Trois options sont déclarées mais pas encore actives.</strong> <code>proxyUrl</code>, <code>geolocation</code> et <code>actionChain</code> existent sur <code>PerceiveOptions</code> et sont sérialisées sur le réseau, mais le serveur répond actuellement <code>422</code> pour les trois. Laissez-les à <code>nil</code>.
</div>

Diffuser un artefact unique directement sur le disque évite l'enveloppe JSON et l'aller-retour par URL signée. `perceiveDirect` vérifie en local que vous avez demandé exactement une sortie produisant un artefact : une erreur ne coûte donc rien.

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

// Retélécharge un artefact stocké plus tard. Passez nil si l'opération n'en a produit qu'un.
let saved = try await client.v2.downloadPerceiveArtifact(direct.operationId, output: .pdf)
```

`PerceiveDirectResult` porte `content`, `contentType`, `filename`, `operationId`, `objectKey`, `cacheHit`, `renderQuality`, `sourceStatusCode`, `contentHash` et `warningsCount`, tous lus depuis les en-têtes de réponse. Un `410` renvoyé par `downloadPerceiveArtifact` signifie que l'artefact est sorti de sa fenêtre de rétention.

Les lots acceptent jusqu'à 1000 URL avec un bloc d'options partagé. Les petits lots se terminent en ligne ; les plus gros reviennent en file d'attente et vous les interrogez :

```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` vaut `.manifest` (par défaut) ou `.zip`. `directDownload` est rejeté avec un `422` sur les lots.

### Discover

Énumère les URL d'un site sans rendu navigateur. Rapide, et il n'effectue jamais de rendu.

```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 | Type | Défaut | Description |
|--------|------|---------|-------------|
| `mode` | `DiscoverMode?` | `.hybrid` | `.sitemap`, `.crawl` ou `.hybrid` (sitemap plus exploration HTTP). |
| `maxUrls`, `maxDepth` | `Int?` | `100`, `2` | De 1 à 1000 URL ; profondeur d'exploration de 1 à 5. |
| `includePatterns`, `excludePatterns` | `[String]?` | aucun | Liste d'autorisation en expressions régulières, puis liste d'exclusion appliquée ensuite. 50 motifs maximum chacune. |
| `sameDomainOnly` | `Bool?` | `true` | Rester sur le domaine de l'URL de départ. |
| `respectRobots` | `Bool?` | valeur serveur par défaut | Respecter `robots.txt`. |

`DiscoverResult` indique également `pagesCrawled`, `robotsRespected` et `warnings`, et `sources` contient les comptages bruts par source relevés avant déduplication.

### Lookup

Lance une recherche web catégorisée, en effectuant éventuellement le rendu des premiers résultats dans le même appel.

```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 | Type | Défaut | Description |
|--------|------|---------|-------------|
| `category` | `LookupCategory?` | `.web` | `.web`, `.news`, `.images`, `.scholar`, `.patents`, `.maps`. |
| `country`, `locale` | `String?` | aucun | Code pays Google `gl` (`"us"`, `"in"`) et langue d'interface `hl` (`"en"`). |
| `timeFilter` | `LookupTimeFilter?` | aucun | `.hour`, `.day`, `.week`, `.month`, `.year`. |
| `numResults`, `page` | `Int?` | `10`, `1` | De 1 à 100 résultats ; page de 1 à 10. |
| `location`, `autocorrect` | `String?`, `Bool?` | aucun, `true` | Localisation en texte libre comme `"Austin, Texas"` ; laisser le fournisseur corriger la requête. |
| `perceiveTop` | `Int?` | `0` | Effectuer automatiquement le rendu des N premières URL de résultat, de 0 à 10. Chacune déclenche un rendu navigateur complet. |

`LookupResult` expose aussi `answerBox`, `knowledgeGraph`, `perceiveOperationIds` et `credits`.

### Distill

Extrayez des données structurées de pages web selon un schéma que vous définissez.

```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` est un `JSONObject`, c'est-à-dire un `[String: JSONValue]` : une table plate `{field: description}` comme un objet JSON Schema complet fonctionnent donc tous les deux. Le `cssSchema` facultatif s'exécute en premier et répond à tout ce que de simples sélecteurs peuvent atteindre ; seuls les champs qu'il manque sont escaladés vers le niveau LLM, et `extractionTier` indique quels niveaux ont réellement répondu (`.css`, `.llm`, `.mixed` ou `.none`). `CssField.type` vaut l'un de `.text`, `.attribute`, `.html`, `.regex`, `.nested`, `.list` ou `.nestedList`, avec une imbrication jusqu'à 5 niveaux.

Remplacez `urls` par `discoverFrom` pour découvrir puis distiller en un seul appel. `DistillDiscoverFrom` prend `url`, `mode` (`.hybrid` par défaut) et `maxPages` (de 1 à 50, 10 par défaut, plafonnant à la fois la découverte et la distillation) :

```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")]
))
```

Passer à la fois `urls` et `discoverFrom`, ou aucun des deux, lève `EnconvertError.invalidArgument` avant l'envoi de toute requête.

### Ingest

Transforme un site, une liste d'URL ou une pile de documents envoyés en JSONL découpé et prêt pour le RAG. Toujours asynchrone.

```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 | Type | Défaut | Description |
|--------|------|---------|-------------|
| `mode` | `IngestMode?` | `.urls` | `.urls`, `.sitemap` ou `.crawl`. Le quatrième cas, `.files`, est celui que `ingestFiles` rapporte sur son job ; ne le passez pas ici. |
| `url` | `String?` | aucun | URL de départ. Obligatoire pour `.sitemap` et `.crawl`, interdite pour `.urls`. |
| `urls` | `[String]?` | aucun | URL explicites, 1000 au maximum. Obligatoires pour `.urls`, interdites sinon. |
| `maxPages`, `maxDepth` | `Int?` | `50`, `2` | Plafond de découverte pour `.sitemap` et `.crawl`, de 1 à 1000 ; profondeur de 1 à 5. |
| `sameDomainOnly` | `Bool?` | `true` | Rester sur le domaine de l'URL de départ. |
| `includePatterns`, `excludePatterns` | `[String]?` | aucun | Liste d'autorisation en expressions régulières, puis liste d'exclusion. |
| `respectRobots` | `Bool?` | valeur serveur par défaut | Respecter `robots.txt`. |
| `waitFor`, `waitTimeoutMs` | `String?`, `Int?` | `30000` ms | Sélecteur ou expression `js:` attendu sur chaque page, et son budget (0 à 60000). |
| `chunk` | `IngestChunkOptions?` | aucun | `maxWords` de 32 à 4000, 512 par défaut. `sentenceOverlap` de 0 à 10, 1 par défaut. |
| `webhookUrl` | `String?` | aucun | Webhook de fin, signé en HMAC. |

Les règles de mode et d'URL ci-dessus sont appliquées côté client : `ingest` lève `EnconvertError.invalidArgument` plutôt que d'émettre une requête vouée à l'échec si vous passez `urls` avec `mode: .sitemap`. Les fichiers envoyés passent par le même pipeline et le même cycle de vie de job :

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

PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, ainsi que les fichiers bureautiques hérités ou ODF sont acceptés, et au moins un fichier est requis. Gestion des jobs et plomberie des webhooks :

```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()                   // les anciennes signatures cessent aussitôt d'être valides

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

`retryIngestWebhook` répond `409` lorsque le job n'est pas terminé et `400` lorsqu'aucun webhook n'est configuré. `V2ListOptions` prend `skip` et `limit` (de 1 à 100, 20 par défaut).

### Watch

Refait le rendu d'une page à intervalle fixe et vous notifie quand elle change.

```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 | Type | Défaut | Description |
|--------|------|---------|-------------|
| `frequencyMinutes` | `Int?` | `60` | De 60 à 43200. Le plancher horaire est strict. |
| `diffMode` | `WatchDiffMode?` | `.auto` | `.auto`, `.text`, `.structured`, `.tables`, `.metadata`. |
| `trackFields` | `JSONObject?` | aucun | Sous-ensemble de champs ou de sélecteurs transmis au moteur de diff. |
| `webhookUrl` | `String?` | aucun | Webhook de notification de changement, signé en HMAC. |
| `notifyEmail` | `Bool?` | `true` | Notifier le propriétaire du projet par e-mail en cas de changement. |

```swift
// Une chaîne vide efface le webhook ; nil le laisse tel quel.
_ = 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)   // suppression logique, idempotente
```

`updateWatcher` exige au moins un champ et lève `EnconvertError.invalidArgument` sur un `WatcherUpdate` vide. `WatchUpdateStatus` n'accepte que `.active` ou `.paused` ; la suppression passe par `deleteWatcher`, qui renvoie le watcher marqué supprimé avec le statut `.deleted`.

<div class="alert alert-warning">
<strong>Les diffs de snapshots contiennent du contenu de page non fiable.</strong> <code>WatcherSnapshot.changes</code> est un tableau d'objets JSON bruts extraits de la page surveillée. Échappez les valeurs avant de les afficher où que ce soit.
</div>

---

## Options PDF

`PdfOptions` est partagé par `convertUrlToPdf`, `convertDocument`, `convertWebsiteToPdf`, `PerceiveOptions` et (pour `grayscale` uniquement) `convertToPdf`. Seuls les champs que vous définissez sont envoyés.

```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)
)
```

| Champ | Type | Description |
|-------|------|-------------|
| `pageSize` | `String?` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"`, et compagnie. |
| `pageWidth`, `pageHeight` | `Double?` | Remplacent `pageSize` lorsque les deux sont définis ensemble. |
| `orientation` | `PdfOrientation?` | `.portrait` ou `.landscape`. Portrait par défaut. |
| `margins` | `PdfMargins?` | `top`, `bottom`, `left`, `right`, chacun un `Double?`. Les quatre sont facultatifs. |
| `scale` | `Double?` | Échelle de rendu, par exemple `0.9` pour 90 %. |
| `grayscale` | `Bool?` | Post-traite le PDF en niveaux de gris. |
| `header` | `PdfHeaderFooter?` | `content` (2000 caractères maximum) et `height`. |
| `footer` | `PdfHeaderFooter?` | Même forme que `header`. |

---

## Gestion des erreurs

Swift reçoit un seul type d'erreur, `EnconvertError`, modélisé comme une énumération plutôt que comme une hiérarchie de classes. Filtrez-le avec des motifs `catch` :

```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)")   // s'affiche sous la forme "[<status>] <message>"
}
```

| Cas | Levé sur | Code de statut |
|------|-----------|-------------|
| `.authentication(message:)` | Clé invalide, absente ou révoquée | `401`, `403` (les deux rapportent `401`) |
| `.quota(message:)` | Toute réponse à laquelle l'API répond `402` | `402` |
| `.rateLimit(message:)` | Limite de débit dépassée | `429` |
| `.api(statusCode:message:)` | Toute autre 4xx ou 5xx | le code réel |
| `.invalidArgument(_:)` | Validation côté client, avant toute requête | aucun |

`EnconvertError` se conforme à `CustomStringConvertible` et `LocalizedError` : `String(describing:)`, `localizedDescription` et l'interpolation de chaînes s'affichent donc tous sous la forme `"[<status>] <message>"`. Deux propriétés pratiques lisent les mêmes valeurs sans filtrage de motif : `error.statusCode` (`Int?`, `nil` pour `.invalidArgument`) et `error.message` (le texte sans le préfixe entre crochets). Une réponse 2xx bien formée à laquelle manque un champ requis par le SDK remonte sous la forme `.api(statusCode: 0, ...)`, ce qui distingue une charge utile mal formée d'un véritable échec HTTP.

Les paires de conversion non prises en charge, un appel `distill` avec à la fois `urls` et `discoverFrom`, un appel `perceiveDirect` demandant deux artefacts, et un `WatcherUpdate` vide lèvent tous `.invalidArgument` avant que le réseau ne soit sollicité. Les codes de réponse sont catalogués dans la [référence des codes d'erreur](/fr/docs/error-codes).

---

## Récupération après timeout

Les rendus d'URL longs et les conversions de gros documents peuvent dépasser le plafond de 60 à 120 secondes d'un reverse proxy même lorsque le job se termine correctement côté serveur. Le SDK s'en sort par interrogation, sans une ligne de code de votre part :

1. Avant chaque conversion de fichier unique et d'URL unique, le client génère un UUIDv4, retire les tirets et l'envoie comme `job_id`.
2. Si cette requête revient avec un statut de 500 ou plus, le client bascule silencieusement sur `GET /v1/convert/status/{job_id}`, interrogé toutes les 3 secondes. Un `404` y signifie « pas encore enregistré » et maintient la boucle.
3. Sur `success`, il renvoie le résultat. Sur `failed`, il lève `.api(statusCode: 500, message:)` portant le message du serveur. Le délai d'interrogation est de 5 minutes, après quoi vous obtenez `.api(statusCode: 504, message: "Conversion timed out")`.

`ConversionResult.jobId` est renseigné par le client même lorsque le chemin synchrone a réussi et que la réponse l'a omis : vous pouvez donc le passer vous-même à `getJobStatus` :

```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>Les lots de sites s'en excluent volontairement.</strong> <code>convertWebsiteToPdf</code> et <code>convertWebsiteToScreenshot</code> n'ont pas de ligne de job par unité à interroger : une 5xx y remonte donc immédiatement au lieu d'être réessayée. Les méthodes V2 n'utilisent pas non plus le repli par job.
</div>

---

## Configuration

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

| Paramètre | Type | Défaut | Description |
|-----------|------|---------|-------------|
| `apiKey` | `String` | obligatoire | Clé API privée. Une chaîne vide lève `EnconvertError.invalidArgument`. |
| `baseURL` | `String` | `https://api.enconvert.com` | À remplacer pour une passerelle auto-hébergée. Les barres obliques finales sont supprimées. |
| `timeout` | `TimeInterval` | `300` | En secondes. Définit à la fois `timeoutIntervalForRequest` et `timeoutIntervalForResource` sur l'`URLSession` interne. |

La clé voyage dans un en-tête `X-API-Key` à chaque appel d'API. Les téléchargements présignés partent délibérément sans elle, puisqu'une URL de stockage signée s'authentifie d'elle-même et que transmettre la clé à un hôte de stockage la ferait fuiter. Pour annuler un appel individuel, enveloppez-le dans une `Task` et annulez-la : chaque méthode est une simple fonction `async throws`. `Enconvert` ne stocke que des propriétés `let` sur une seule `URLSession` : construisez donc un client unique au démarrage et réutilisez-le ; `client.v2` est un mince espace de noms au-dessus du même transport.

<div class="alert alert-warning">
<strong>Ne codez jamais la clé API en dur, et ne la livrez jamais dans un bundle applicatif.</strong> Lisez-la depuis l'environnement ou votre gestionnaire de secrets et gardez le client sur un serveur que vous contrôlez. Le paquet se compile pour iOS, tvOS et watchOS afin que vous puissiez partager du code de modèle entre cibles, mais un binaire d'application est un artefact public : quiconque extrait votre clé privée peut lancer des conversions sur votre projet. Faites appeler votre propre backend par l'application, et laissez le backend appeler EnConvert. Voir l'<a href="/fr/docs/authentication">authentification</a> pour les types de clés et leur rotation.
</div>

---

## Forme du résultat

Les conversions de fichier unique et d'URL unique renvoient un `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?
}
```

Les URL présignées sont de courte durée. Passez `saveTo` pour que le SDK écrive les octets sur le disque à votre place, en créant les répertoires parents au besoin, ou récupérez l'URL vous-même et rangez le fichier dans votre propre bucket pour un accès à long terme.

Les artefacts V2 arrivent sous forme de valeurs `V2OutputArtifact` indexées par nom de sortie, chacune portant `url` (`String?`, présignée pour 15 minutes et resignée à chaque GET de statut), `objectKey`, `sizeBytes`, `contentType` et `expiresIn` (en secondes, 900 par défaut). `PerceiveResult` les enveloppe avec les métadonnées de fiabilité : `renderQuality`, `statusCode`, `deductions`, `cacheHit`, `warnings`, `contentHash`, `urlFinal`, `structured`, `extractionTier`, `tokens`, `costCents`, `durationMs` et `optionsEcho`, qui renvoie en écho les options que le serveur a réellement appliquées, les secrets étant réduits à des booléens. Les charges utiles définies par l'appelant (schémas d'extraction, `data` distillées, `trackFields` de watcher, `changes` de diff, `extra` de recherche) font l'aller-retour via `JSONValue`, une énumération avec les cas `.null`, `.bool`, `.number`, `.string`, `.array` et `.object`, plus l'alias `JSONObject` pour `[String: JSONValue]`. Chaque type de résultat est `Codable`, `Equatable` et `Sendable` : mettre en cache un résultat parsé sur le disque et le recharger plus tard fonctionne sans rien de plus.

---

## Sources et signalements

- **Paquet :** `Enconvert`, via Swift Package Manager. Version exposée à l'exécution par la constante de module `VERSION`
- **GitHub :** [conversionapi/swift-sdk](https://github.com/conversionapi/swift-sdk)
- **Licence :** MIT. Dépendances : aucune, uniquement `URLSession` et Foundation

À lire également : [tous les SDK](/fr/docs/sdks), [vue d'ensemble V2](/fr/docs/v2-overview), [perceive](/fr/docs/v2-perceive), [discover](/fr/docs/v2-discover), [lookup](/fr/docs/v2-lookup), [distill](/fr/docs/v2-distill), [ingest](/fr/docs/v2-ingest), [watch](/fr/docs/v2-watch), [vue d'ensemble des endpoints](/fr/docs/endpoints-overview), [paramètres et options](/fr/docs/parameters-options), et votre [tableau de bord](/fr/dashboard) pour les clés.

---

## Questions fréquentes

### Comment convertir des fichiers en Swift ?

Ajoutez `https://github.com/conversionapi/swift-sdk.git` aux dépendances de votre `Package.swift`, construisez un client avec `try Enconvert(apiKey:)`, puis appelez une méthode typée comme `convertDocument`, `convertImage` ou `convertUrlToPdf`. Passez `saveTo` dans la struct d'options et le SDK écrit le fichier terminé directement à ce chemin, en créant les répertoires parents au besoin.

### Comment convertir une URL en PDF en Swift ?

Appelez `try await client.convertUrlToPdf("https://example.com", options: UrlToPdfOptions(saveTo: "page.pdf"))`. Mettez `singlePage: false` pour paginer au lieu de produire une seule page continue, et passez `pdfOptions:` pour la taille de page, l'orientation, les marges, l'échelle, les niveaux de gris, les en-têtes et les pieds de page. N'oubliez pas que Swift veut les étiquettes dans l'ordre de déclaration : `saveTo:` vient donc avant `singlePage:` et `pdfOptions:`.

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

`try await client.convertDocument(.path("report.docx"), options: ConvertDocumentOptions(saveTo: "report.pdf"))`. Le format de sortie vaut `"pdf"` par défaut, `outputFormat` peut donc être omis. La même méthode gère les entrées XLSX, PPTX, ODT, ODS, ODP, OTS, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML et TOML.

### Comment convertir du HEIC en WebP en Swift ?

`try await client.convertImage(.path("photo.heic"), options: ConvertImageOptions(outputFormat: "webp", saveTo: "photo.webp"))`. Le format d'entrée est lu depuis l'extension du nom de fichier, et les 20 paires ordonnées entre `jpeg`, `png`, `svg`, `heic` et `webp` fonctionnent de la même manière. Les paires non prises en charge lèvent `EnconvertError.invalidArgument` en local, avant l'envoi de toute requête.

### Le SDK Swift embarque-t-il des dépendances tierces ?

Non. `Package.swift` déclare un tableau `dependencies` vide. Tout repose sur `URLSession`, `JSONSerialization` et `Foundation`, avec `FoundationNetworking` importé de manière conditionnelle pour que le paquet se compile sous Linux comme sur les plateformes Apple.

### Comment récupérer une page web en Markdown propre en Swift ?

Deux possibilités. `client.convertUrlToMarkdown` renvoie du Markdown GitHub-Flavored avec un frontmatter YAML et constitue le chemin le plus simple. `client.v2.perceive` avec `outputs: [.markdown]` vous donne le même Markdown plus un score `renderQuality`, une table `deductions`, des `warnings`, et la possibilité d'ajouter des captures d'écran, des liens ou une extraction structurée issus du même rendu.

### Que signifie la qualité de rendu et pourquoi faut-il la vérifier ?

`renderQuality` est un `Double?` de 0.0 à 1.0 attaché à chaque lecture V2. Il baisse lorsque la page n'a pas été rendue honnêtement : défi anti-bot, mur de connexion, bandeau de cookies posé sur une coquille vide, ou statut d'erreur HTTP. Le contenu est quand même renvoyé plutôt qu'avalé : vérifiez donc le score et le dictionnaire `deductions` qui nomme chaque pénalité avant de transmettre le texte à un modèle.

### Puis-je utiliser le SDK Swift dans une application iOS ou macOS ?

Uniquement derrière votre propre backend. Le paquet se compile pour iOS 15, tvOS 15, watchOS 8 et macOS 12 afin que vous puissiez partager du code de modèle entre cibles, mais il s'authentifie avec une clé API privée et les endpoints V2 rejettent purement et simplement les clés publiques. Livrer cette clé dans un binaire d'application revient à la donner à quiconque décompresse le bundle. Faites appeler votre propre serveur depuis l'application, et appelez EnConvert depuis le serveur.

### Que se passe-t-il quand une conversion longue atteint le timeout du proxy ?

Le SDK envoie un `job_id` généré par le client avec chaque conversion de fichier unique et d'URL unique. Si la requête renvoie 500 ou plus, il interroge `GET /v1/convert/status/{job_id}` toutes les 3 secondes pendant 5 minutes au maximum, renvoyant le résultat sur `success` et levant `.api(statusCode: 500, ...)` sur `failed`. Dépasser le délai donne `.api(statusCode: 504, message: "Conversion timed out")`. Les soumissions de lots de sites sautent délibérément ce repli.

### Comment savoir quelles conversions sont prises en charge avant d'envoyer une requête ?

Appelez `validOutputsFor("json")` pour connaître les sorties acceptées par un format d'entrée donné, ou vérifiez l'appartenance à `IMPLEMENTED_CONVERSIONS`, l'ensemble des 43 endpoints `{input}-to-{output}` implémentés. `convertImage` et `convertDocument` effectuent la même vérification en interne et lèvent `EnconvertError.invalidArgument` avec la liste des sorties valides pour cette entrée, avant l'envoi de toute requête.
