---
seo_title: SDK Python de conversion de fichiers : pip install enconvert
meta_desc: Le SDK Python officiel d'EnConvert. Installez-le avec pip, convertissez plus de 40 formats, et pilotez l'espace de noms V2 pour lire, extraire et surveiller le web.
keywords: sdk python conversion de fichiers, convertir des fichiers en python, url vers pdf python, api scraping web python, docx vers pdf python, enconvert python sdk, pip install enconvert, heic vers webp python, html vers markdown python, pipeline ingestion rag python, surveillance de changements de site python
---

# SDK Python de conversion de fichiers

`enconvert` est le client Python officiel de l'API EnConvert : un seul `pip install`, une seule clé API, et des méthodes typées pour convertir des fichiers et pour lire le web en direct. Il tourne sur Python 3.9 ou plus récent avec une seule dépendance runtime, `requests`, et embarque des annotations de type en ligne ainsi qu'un marqueur `py.typed` pour que mypy et Pyright voient tout. Le client a deux surfaces. Douze méthodes de conversion couvrent 43 paires `{input}-to-{output}`, plus URL vers PDF, capture d'écran et Markdown. L'espace de noms `client.v2` ajoute la web intelligence : perceive, discover, lookup, distill, ingest et watch.

<div class="alert alert-info">
<strong>PyPI :</strong> <code>enconvert</code> &middot; <strong>Source :</strong> <a href="https://github.com/conversionapi/python-sdk">conversionapi/python-sdk</a> &middot; <strong>Python :</strong> 3.9+ &middot; <strong>Dépendance runtime :</strong> <code>requests&gt;=2.28</code> &middot; <strong>Licence :</strong> MIT
</div>

---

## Installation

```bash
pip install enconvert
```

`uv add enconvert` et `poetry add enconvert` fonctionnent de la même façon. `requests` est la seule dépendance runtime, et les stubs de types sont livrés dans la wheel : il n'y a donc aucun package `types-` à aller chercher.

---

## Démarrage rapide

Lisez une page comme votre agent devrait le faire, avec un score de qualité attaché, puis convertissez un fichier local via le même client.

```python
import os

from enconvert import Enconvert

client = Enconvert(api_key=os.environ["ENCONVERT_API_KEY"])

op = client.v2.perceive("https://example.com", outputs=["markdown", "structured"])
print(op.outputs["markdown"].url, op.render_quality)

print(client.convert_to_pdf("report.docx", save_to="report.pdf").presigned_url)
```

Toutes les méthodes sont synchrones et bloquantes. Le SDK est **exclusivement côté serveur** : il s'authentifie avec une clé API privée, alors ne le livrez jamais à l'intérieur d'un client desktop, mobile ou navigateur. Récupérez une clé depuis votre [tableau de bord](/fr/dashboard), et consultez [Authentification](/fr/docs/authentication) pour comprendre la portée des clés.

---

## Ce que le client expose

`Enconvert` constitue toute la surface publique. Les méthodes de conversion sont accrochées directement au client ; tout ce qui touche au web vit sous `client.v2`.

| Méthode de conversion | Endpoint | Renvoie |
|--------|----------|---------|
| `convert_url_to_pdf(url, ...)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `convert_url_to_screenshot(url, ...)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `convert_url_to_markdown(url, ...)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `convert_image(file, output_format=...)` | `POST /v1/convert/{input}-to-{output}` | `ConversionResult` |
| `convert_document(file, ...)` | `POST /v1/convert/{input}-to-{output}` | `ConversionResult` |
| `convert_to_markdown(file, ...)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `convert_to_pdf(file, ...)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `convert_website_to_pdf(url, ...)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `convert_website_to_screenshot(url, ...)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `get_job_status(job_id)` | `GET /v1/convert/status/{job_id}` | `JobStatus` |
| `get_batch_status(batch_id)` | `GET /v1/convert/batch/{batch_id}` | `BatchStatus` |
| `wait_for_batch(batch_id, ...)` | `GET /v1/convert/batch/{batch_id}` (interrogé) | `BatchStatus` |

| Capacité `client.v2` | Méthodes | Renvoie |
|------------|---------|---------|
| Perceive | `perceive`, `perceive_direct`, `get_perceive_operation`, `download_perceive_artifact`, `perceive_batch`, `get_perceive_batch` | `PerceiveResult`, `PerceiveDirectResult`, `PerceiveBatchResult` |
| Discover, Lookup, Distill | `discover`, `lookup`, `distill` | `DiscoverResult`, `LookupResult`, `DistillResult` |
| Ingest | `ingest`, `ingest_files`, `list_ingest_jobs`, `get_ingest_job`, `cancel_ingest_job`, `retry_ingest_webhook`, `get_webhook_secret`, `rotate_webhook_secret` | `IngestJob`, `IngestJobList`, `WebhookSecret`, `WebhookRetryResult` |
| Watch | `create_watcher`, `list_watchers`, `get_watcher`, `get_watcher_snapshots`, `update_watcher`, `delete_watcher` | `Watcher`, `WatcherList`, `WatcherSnapshotList` |

Tous les arguments qui suivent le premier argument positionnel sont nommés uniquement, en snake_case, et facultatifs sauf indication contraire dans un tableau. Les résultats sont des dataclasses figées : construisez donc une nouvelle instance plutôt que d'en muter une. Les formes REST se trouvent dans la [vue d'ensemble des endpoints](/fr/docs/endpoints-overview).

---

## Conversion de fichiers

Chaque méthode de conversion accepte `save_to` (un `str` ou un `os.PathLike` ; le résultat y est écrit en flux et les répertoires parents sont créés) et `output_filename` (remplace le nom généré). Les deux sont omis des tableaux ci-dessous.

### convert_url_to_pdf

Rendez en PDF n'importe quelle URL accessible.

```python
result = client.convert_url_to_pdf(
    "https://example.com", single_page=False, viewport_width=1440, save_to="report.pdf"
)
print(result.presigned_url, result.file_size)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `single_page` | `bool` | `True` | `True` donne une seule page continue. `False` pagine en utilisant `pdf_options.page_size`. |
| `pdf_options` | `PdfOptions` | -- | Format de page, orientation, marges, échelle, niveaux de gris, en-tête, pied de page. Voir [Options PDF](#options-pdf). |
| `viewport_width` / `viewport_height` | `int` | `1920` / `1080` | Taille de la fenêtre du navigateur, en pixels. |

`load_media` et `enable_scroll` valent tous deux `True` par défaut : le premier attend les images et les vidéos, le second fait défiler de haut en bas pour que les chargements différés se déclenchent. Trois options supplémentaires permettent d'atteindre des pages derrière une porte : `auth` (`HttpBasicAuth`), `cookies` (`list[BrowserCookie]`) et `headers` (`dict[str, str]`).

<div class="alert alert-warning">
<strong>Ne combinez pas <code>auth</code> avec un en-tête <code>Authorization</code>.</strong> L'API rejette le conflit plutôt que de deviner quelle authentification l'emporte. Choisissez-en une.
</div>

---

### convert_url_to_screenshot

Capturez un PNG de n'importe quelle URL.

```python
client.convert_url_to_screenshot("https://example.com", viewport_width=1440, save_to="shot.png")
```

Accepte les mêmes options de fenêtre, de médias, de défilement, de nom de fichier et d'accès navigateur que `convert_url_to_pdf`, sans `single_page` ni `pdf_options`.

---

### convert_url_to_markdown

Extrayez du Markdown GitHub-Flavored propre depuis une URL. La navigation, les pieds de page, les publicités et les scripts sont supprimés, le corps principal de l'article est conservé, et un frontmatter YAML avec titre, description, url, liens et images est ajouté en tête.

```python
client.convert_url_to_markdown("https://example.com/article", save_to="article.md")
```

Même jeu d'options que `convert_url_to_screenshot`. Quand vous voulez en plus un score de qualité, les métadonnées de la page ou une extraction structurée sur la même lecture, utilisez plutôt [`v2.perceive`](#perceive).

---

### convert_image

Convertissez entre `jpeg`, `png`, `svg`, `heic` et `webp`, ou rastérisez un PDF en JPEG. Le format d'entrée vient de l'extension du nom de fichier.

```python
client.convert_image("photo.heic", output_format="webp", save_to="photo.webp")
client.convert_image("scan.pdf", output_format="jpeg", save_to="scan.jpeg")
```

`output_format` est obligatoire et doit valoir `jpeg`, `png`, `svg`, `heic` ou `webp` ; les alias `jpg`, `yml`, `htm` et `md` sont normalisés pour vous. `file` accepte une chaîne de chemin, un `os.PathLike`, des `bytes` bruts, ou un wrapper `FileData(data, filename)`. Les `bytes` bruts ne portent aucun nom de fichier et sont téléversés sous `upload.bin` : préférez donc `FileData` chaque fois que l'extension compte.

```python
from pathlib import Path

from enconvert import FileData

blob = FileData(data=Path("photo.heic").read_bytes(), filename="photo.heic")
client.convert_image(blob, output_format="webp", save_to="photo.webp")
```

---

### convert_document

Convertissez des documents et des formats de données. `output_format` vaut `"pdf"` par défaut, et `pdf_options` est pris en compte lorsque la sortie est un PDF.

```python
from enconvert import PdfMargins, PdfOptions

client.convert_document("report.docx", save_to="report.pdf")
client.convert_document("data.json", output_format="yaml", save_to="data.yaml")
client.convert_document(
    "README.md", pdf_options=PdfOptions(page_size="A4", margins=PdfMargins(top=20)), save_to="r.pdf"
)
```

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

EPUB n'a pas de paire de document dédiée. Faites passer les `.epub` par `convert_to_pdf` ou `convert_to_markdown`.

---

### convert_to_markdown

Détectez automatiquement un document téléversé côté serveur et récupérez du Markdown propre. C'est la brique d'ingestion RAG pour un fichier unique.

```python
client.convert_to_markdown("handbook.docx", save_to="handbook.md")
```

**Entrées acceptées :** PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, ainsi que les formats bureautiques anciens et ODF. Les images ne sont pas prises en charge ici, et l'endpoint n'accepte aucune option PDF. Pour un site entier plutôt qu'un seul fichier, utilisez [`v2.ingest`](#ingest).

---

### convert_to_pdf

Détectez automatiquement un fichier téléversé côté serveur et récupérez un PDF.

```python
from enconvert import PdfOptions

client.convert_to_pdf("slides.pptx", save_to="slides.pdf")
client.convert_to_pdf("scan.pdf", pdf_options=PdfOptions(grayscale=True), save_to="gray.pdf")
```

**Entrées acceptées :** bureautique, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, texte brut, images matricielles, SVG, EPUB, et un PDF existant transmis tel quel. Comme une entrée `.pdf` est transmise telle quelle, cette méthode fait aussi office de normaliseur en niveaux de gris.

<div class="alert alert-warning">
<strong>Seul <code>pdf_options.grayscale</code> est pris en compte sur cet endpoint.</strong> Les autres champs de géométrie de page sont ignorés ici. Quand vous avez besoin d'une vraie mise en page, faites passer le fichier par <code>convert_document</code> ou <code>convert_url_to_pdf</code>.
</div>

---

### convert_website_to_pdf et convert_website_to_screenshot

Découvrez chaque page d'un site, convertissez-les toutes en arrière-plan, et récupérez un seul ZIP. Les deux méthodes sont asynchrones par conception et renvoient immédiatement un `BatchSubmission`.

```python
batch = client.convert_website_to_pdf(
    "https://example.com", crawl_mode="sitemap", exclude_patterns=["/blog/tag/"]
)
print(batch.batch_id, batch.url_count, batch.discovery_method)

status = client.wait_for_batch(batch.batch_id, save_to="site.zip")
print(status.completed, "of", status.total, "pages converted")

for item in client.get_batch_status(batch.batch_id).items:
    print(item.source_url, item.status, item.download_url)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `crawl_mode` | `"auto" \| "sitemap" \| "full"` | défaut serveur | `sitemap` lit uniquement `sitemap.xml`. `full` ajoute un crawl en largeur d'abord. `auto` choisit le mode le plus élevé disponible pour la clé. |
| `include_patterns` / `exclude_patterns` | `list[str]` | -- | Conserve ou écarte les URL par fragment de chemin. Les exclusions ne s'appliquent qu'en mode crawl complet. |
| `notification_email` / `callback_url` | `str` | -- | Adresse à prévenir par e-mail, et webhook à appeler, quand le lot se termine. |
| `single_page` | `bool` | défaut serveur | PDF uniquement. |
| `pdf_options` | `PdfOptions` | -- | PDF uniquement. Voir [Options PDF](#options-pdf). |

Les options de rendu par page s'appliquent aussi à chaque URL découverte : `viewport_width`, `viewport_height`, `load_media`, `enable_scroll`, `auth`, `cookies` et `headers`. Tout ce qui reste non défini conserve la valeur par défaut de la passerelle.

`wait_for_batch` accepte `interval_seconds` (défaut `5.0`), `timeout_seconds` (défaut `1800.0`) et `save_to`. Il lève `APIError(504, ...)` si le lot est toujours en traitement une fois l'échéance passée.

---

### Paires de conversion prises en charge

Le SDK embarque une copie de la carte des conversions de la passerelle et rejette localement une paire non implémentée, avant tout aller-retour réseau, en nommant dans le message les sorties valides pour cette entrée.

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

Cela fait 43 paires implémentées. Vérifiez-les par programme :

```python
from enconvert import IMPLEMENTED_CONVERSIONS, valid_outputs_for

valid_outputs_for("json")                  # ['csv', 'toml', 'xml', 'yaml']
"heic-to-webp" in IMPLEMENTED_CONVERSIONS  # True
```

La référence complète des paramètres vit dans [Paramètres et options](/fr/docs/parameters-options).

---

## Web intelligence (V2)

Chaque lecture V2 porte `render_quality`, un flottant de 0.0 à 1.0 qui dit avec quelle honnêteté la page s'est rendue. Un écran de défi, un mur de cookies, une porte de connexion, une page d'erreur HTTP ou une coquille vide d'application monopage revient avec un score bas, une table `deductions` nommant ce qui s'est déclenché, et une liste `warnings`. Le contenu est tout de même renvoyé, simplement signalé : une mauvaise lecture n'entre donc jamais discrètement dans le contexte de votre agent. Traitez le score comme une porte et vérifiez-le avant d'utiliser le texte. `PerceiveResult`, `PerceiveDirectResult`, `DistillItem`, `LookupItem.perceive` et `WatcherSnapshot` l'exposent tous. Les concepts se trouvent dans la [vue d'ensemble V2](/fr/docs/v2-overview).

### Perceive

Rendez une URL sous forme d'artefacts prêts pour un agent. Synchrone : l'appel renvoie l'opération terminée avec des URL d'artefacts signées.

```python
op = client.v2.perceive(
    "https://example.com",
    outputs=["markdown", "screenshot", "structured"],
    extract=["tables", "metadata"],
    only_main_content=True,
)

print(op.render_quality)            # de 0.0 à 1.0
print(op.status_code)               # statut HTTP de la page elle-même
print(op.deductions)                # par ex. {"login_wall": 0.65}
print(op.outputs["markdown"].url)   # URL signée, 15 minutes
print(op.structured)

if (op.render_quality or 0) < 0.6:
    print("Low-confidence read:", op.warnings)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `outputs` | `list[str]` | `["markdown", "structured"]` | Au choix parmi `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `extract` | `list[str]` | -- | Au choix parmi `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `wait_for` | `str` | -- | Sélecteur CSS à attendre avant la capture. |
| `viewport` | `PerceiveViewport` | 1920 x 1080 | `width` de 320 à 3840, `height` de 240 à 2160. |
| `cache_mode` | `"enabled" \| "bypass" \| "refresh"` | défaut serveur | Réutilise, ignore ou réécrit le rendu mis en cache. |
| `block_resources` | `list[str]` | -- | Au choix parmi `image`, `media`, `font`, `stylesheet`, `script`, `xhr`, `fetch`, `websocket`, `manifest`, `other`. |
| `only_main_content` | `bool` | défaut serveur | Supprime la navigation, les en-têtes, les pieds de page et les autres habillages de la page dans le contenu extrait. |
| `direct_download` | `bool` | `False` | Renvoie les octets bruts au lieu d'une URL signée. Accepté par `perceive` uniquement ; l'endpoint de lot le rejette. |

Également acceptés : `schema` (`dict`, un schéma d'extraction libre transmis intact), `js_code` (`str`, exécuté dans la page avant la capture), `wait_timeout_ms` (`int`), `headers` (`dict[str, str]`), `cookies` (`list[BrowserCookie]`), `auth` (`HttpBasicAuth`), `proxy_url` (`str`), `geolocation` (`dict`), `action_chain` (`list[dict]` d'étapes scriptées de clic, de saisie et de défilement), `pdf_options` (`PdfOptions`, appliqué à la sortie `pdf`), `mobile` (`bool`) et `respect_robots` (`bool`). `perceive_batch` prend le jeu identique, sauf `direct_download`.

Les URL d'artefacts sont resignées à chaque lecture : appelez donc `client.v2.get_perceive_operation(op.operation_id)` pour en obtenir une fraîche, plutôt que de mettre la chaîne en cache.

**Octets bruts, sans aller-retour par URL signée.** `perceive_direct` force `direct_download` et vous remet l'artefact lui-même, avec les métadonnées lues dans les en-têtes de la réponse.

```python
direct = client.v2.perceive_direct("https://example.com", outputs=["markdown"])
print(direct.filename, direct.content_type, len(direct.content), direct.render_quality)

raw = client.v2.download_perceive_artifact(op.operation_id, output="markdown")
```

`perceive_direct` exige exactement une sortie produisant un artefact parmi `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links` et `images`. `structured` peut accompagner la demande mais reste en ligne côté serveur ; passez autre chose et le SDK lève `EnconvertError` avant d'envoyer la requête. Sur `download_perceive_artifact`, `output` peut être omis lorsque l'opération n'a produit qu'un seul artefact, et un artefact sorti de sa fenêtre de rétention répond `410`.

**Lots.** Jusqu'à 1000 URL partagent un seul bloc d'options. Les petits lots se terminent en ligne ; les plus gros reviennent avec le statut `"queued"`, alors interrogez l'identifiant de tâche.

```python
batch = client.v2.perceive_batch(
    ["https://a.example.com", "https://b.example.com"], outputs=["markdown"], output_mode="zip"
)

done = client.v2.get_perceive_batch(batch.job_id)
print(done.status, done.completed, done.failed, done.pending)
for item in done.items:
    print(item.url, item.render_quality)
```

`output_mode` vaut `"manifest"` (défaut) ou `"zip"` ; quand il vaut `"zip"`, le bundle terminé se trouve sur `done.zip.url`. Détails dans [Perceive](/fr/docs/v2-perceive).

### Discover

Énumérez les URL d'un site sans aucun rendu navigateur, ce qui en fait la première étape peu coûteuse avant de percevoir quoi que ce soit.

```python
found = client.v2.discover(
    "https://example.com", mode="hybrid", max_urls=200, exclude_patterns=["/tag/"]
)
print(found.total, found.truncated, found.sources)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `mode` | `"sitemap" \| "crawl" \| "hybrid"` | défaut serveur | Analyse du sitemap, crawl HTTP, ou les deux fusionnés et dédupliqués. |
| `max_urls` / `max_depth` | `int` | défaut serveur | Plafond d'URL renvoyées (`truncated` vaut `True` s'il en existait davantage), et profondeur de crawl depuis l'URL de départ. |
| `same_domain_only` | `bool` | défaut serveur | Reste sur l'hôte de départ. |
| `respect_robots` | `bool` | défaut serveur | Respecte `robots.txt`. |

`include_patterns` et `exclude_patterns` (tous deux `list[str]`) filtrent l'ensemble de résultats. `DiscoverResult.sources` contient les décomptes bruts par source avant déduplication, par exemple `{"sitemap": 42, "crawl": 30}`. Pour aller plus loin : [Discover](/fr/docs/v2-discover).

### Lookup

Lancez une recherche web catégorisée, en rendant éventuellement les meilleurs résultats dans le même appel.

```python
search = client.v2.lookup(
    "best static site generators", category="web", num_results=10, perceive_top=3
)

for hit in search.results:
    quality = hit.perceive.render_quality if hit.perceive else None
    print(hit.position, hit.title, hit.url, quality)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `category` | `"web" \| "news" \| "images" \| "scholar" \| "patents" \| "maps"` | défaut serveur | Verticale de recherche. |
| `time_filter` | `"hour" \| "day" \| "week" \| "month" \| "year"` | -- | Fenêtre de fraîcheur. |
| `num_results` / `page` | `int` | défaut serveur | Résultats par page, et numéro de page en base 1. |
| `perceive_top` | `int` | `0` | Rend automatiquement les N premières URL de résultats. Chacune arrive avec son `PerceiveResult` complet sur `hit.perceive`. |

Également acceptés : `country` (`str`), `locale` (`str`), `location` (`str`) pour les requêtes sensibles à la géographie, et `autocorrect` (`bool`). `LookupResult` porte `answer_box`, `knowledge_graph`, `perceive_operation_ids` et `perceive_top`, ce dernier indiquant combien de résultats ont réellement été rendus, ce qui peut être inférieur à ce que vous avez demandé. Référence dans [Lookup](/fr/docs/v2-lookup).

### Distill

Extraction structurée pilotée par schéma sur une ou plusieurs pages. Fournissez exactement l'un de `urls` ou `discover_from` ; `schema` est toujours obligatoire. Les deux règles sont appliquées côté client et lèvent `EnconvertError` avant qu'une requête ne parte.

```python
from enconvert import CssField, CssSchema

extraction = client.v2.distill(
    urls=["https://example.com/pricing"],
    schema={"plans": "list of plan names with monthly prices"},
    css_schema=CssSchema(
        base_selector=".plan-card",
        fields=[
            CssField(name="name", type="text", selector="h3"),
            CssField(name="price", type="text", selector=".price"),
        ],
        target_field="plans",
    ),
)

for item in extraction.results:
    print(item.url, item.extraction_tier, item.fields_from_css, item.fields_from_llm, item.data)
```

Le `css_schema` facultatif s'exécute en premier et répond à tout ce que les sélecteurs peuvent atteindre. Seuls les champs qu'il manque escaladent vers le niveau modèle de langage, et `extraction_tier` sur chaque élément indique quel chemin a été emprunté : `css`, `llm`, `mixed` ou `none`.

Découvrir et extraire en un seul appel :

```python
from enconvert import DistillDiscoverFrom

client.v2.distill(
    discover_from=DistillDiscoverFrom(url="https://example.com", mode="sitemap", max_pages=10),
    schema={"title": "page title"},
)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `schema` | `dict` | -- (obligatoire) | Forme de sortie que vous voulez récupérer, transmise intacte. |
| `urls` | `list[str]` | -- | Liste de pages explicite. Mutuellement exclusive avec `discover_from`. |
| `discover_from` | `DistillDiscoverFrom` | -- | `url`, `mode` facultatif, `max_pages` facultatif (de 1 à 50, défaut 10). |
| `css_schema` | `CssSchema` | -- | Passe de sélecteurs exécutée avant tout appel au modèle. |

Également acceptés : `wait_for` (`str`), `wait_timeout_ms` (`int`), `headers` (`dict[str, str]`), `cookies` (`list[BrowserCookie]`) et `respect_robots` (`bool`). `CssField` prend en charge les types `text`, `attribute`, `html`, `regex`, `nested`, `list` et `nested_list`, avec les options `attribute`, `pattern`, `default`, `transform` (`lowercase`, `uppercase`, `strip`) et des `fields` imbriqués jusqu'à cinq niveaux de profondeur. Voir [Distill](/fr/docs/v2-distill).

### Ingest

Transformez un site entier, une liste d'URL explicite, ou un lot de documents téléversés en JSONL découpé et prêt pour le RAG. Ingest est toujours asynchrone.

```python
import time

from enconvert import IngestChunkOptions

job = client.v2.ingest(
    mode="sitemap",
    url="https://docs.example.com",
    max_pages=100,
    chunk=IngestChunkOptions(max_words=512, sentence_overlap=1),
    webhook_url="https://my.app/hooks/enconvert",
)

status = client.v2.get_ingest_job(job.job_id)
while status.status in ("queued", "discovering", "processing"):
    time.sleep(10)
    status = client.v2.get_ingest_job(job.job_id)

print(status.status, status.pages_processed, status.total_chunks, status.output_url)
```

Les fichiers téléversés suivent le même cycle de vie de tâche sous le mode `files`. PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, ainsi que les formats bureautiques anciens et ODF sont acceptés, et chaque entrée peut être un chemin, un `os.PathLike`, des `bytes` bruts ou un `FileData`.

```python
file_job = client.v2.ingest_files(["handbook.pdf", "notes.docx"])
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `mode` | `"urls" \| "sitemap" \| "crawl" \| "files"` | `"urls"` | `urls` a besoin d'une liste `urls` non vide et rejette `url`. Tous les autres modes ont besoin d'une `url` de départ et rejettent `urls`. |
| `url` / `urls` | `str` / `list[str]` | -- | URL de départ pour `sitemap` et `crawl`, ou la liste de pages explicite pour le mode `urls`. |
| `max_pages` | `int` | défaut serveur | Plafond de pages ingérées. |
| `chunk` | `IngestChunkOptions` | -- | `max_words` de 32 à 4000, défaut 512. `sentence_overlap` de 0 à 10, défaut 1. Associez-le à `webhook_url` (`str`), appelé dès que la tâche atteint un état terminal. |

Les options de forme de crawl et de rendu sont acceptées elles aussi : `max_depth`, `same_domain_only`, `include_patterns`, `exclude_patterns`, `respect_robots`, `wait_for` et `wait_timeout_ms`. `ingest_files` ne prend que `chunk` et `webhook_url`.

```python
for summary in client.v2.list_ingest_jobs(limit=20).jobs:
    print(summary.job_id, summary.status, summary.total_chunks)

client.v2.cancel_ingest_job(job.job_id)
client.v2.retry_ingest_webhook(job.job_id)

secret = client.v2.get_webhook_secret()
print(secret.signature_header, secret.signature_scheme, secret.replay_tolerance_seconds)
client.v2.rotate_webhook_secret()
```

`cancel_ingest_job` est idempotent ; annuler une tâche déjà terminale la renvoie inchangée. `retry_ingest_webhook` répond `409` quand la tâche n'est pas terminée et `400` quand aucun webhook n'était configuré. `rotate_webhook_secret` invalide immédiatement le secret précédent. Couverture plus approfondie dans [Ingest](/fr/docs/v2-ingest).

### Watch

Refaites le rendu d'une URL à une cadence fixe et faites-vous prévenir quand elle change.

```python
watcher = client.v2.create_watcher(
    "https://example.com/pricing",
    frequency_minutes=60,
    diff_mode="auto",
    webhook_url="https://my.app/hooks/changes",
    notify_email=True,
)

for snap in client.v2.get_watcher_snapshots(watcher.watcher_id, limit=10).snapshots:
    print(snap.checked_at, snap.has_changes, snap.similarity, snap.change_count)

client.v2.list_watchers(limit=20)
client.v2.update_watcher(watcher.watcher_id, status="paused")
client.v2.update_watcher(watcher.watcher_id, webhook_url="")
client.v2.delete_watcher(watcher.watcher_id)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `frequency_minutes` | `int` | défaut serveur | Intervalle entre deux vérifications. L'heure est le plancher. |
| `diff_mode` | `"auto" \| "text" \| "structured" \| "tables" \| "metadata"` | défaut serveur | Quelle couche de la page le moteur de diff compare. |
| `track_fields` | `dict` | -- | Champs nommés à suivre, transmis intacts. |
| `webhook_url` | `str` | -- | Appelé à chaque changement détecté. |
| `notify_email` | `bool` | défaut serveur | Envoie les notifications de changement par e-mail. |

`update_watcher` a besoin d'au moins un champ et lève `EnconvertError` sinon ; `status` accepte `"active"` ou `"paused"`, et un `webhook_url` vide efface le webhook. `delete_watcher` est une suppression douce et idempotente qui renvoie l'observateur marqué supprimé avec le statut `"deleted"`, après quoi l'observateur répond `404`.

<div class="alert alert-warning">
<strong>Les diffs d'instantanés portent du contenu de page non fiable.</strong> Les dicts de <code>WatcherSnapshot.changes</code> viennent directement du site surveillé. Échappez-les avant de les afficher en HTML, dans un corps d'e-mail ou dans un message de chat.
</div>

Le moteur de diff et les charges utiles de notification sont documentés dans [Watch](/fr/docs/v2-watch).

---

## Options PDF

`PdfOptions` est une dataclass figée partagée par `convert_url_to_pdf`, `convert_website_to_pdf`, `convert_document`, `convert_to_pdf` et la famille `v2.perceive`.

```python
from enconvert import BrowserCookie, HttpBasicAuth, PdfHeaderFooter, PdfMargins, PdfOptions

client.convert_url_to_pdf(
    "https://internal.example.com/report",
    pdf_options=PdfOptions(
        page_size="A4",
        orientation="landscape",
        margins=PdfMargins(top=10, bottom=10, left=15, right=15),
        scale=0.9,
        header=PdfHeaderFooter(content="Quarterly Report", height=15),
        footer=PdfHeaderFooter(content="Confidential", height=12),
    ),
    auth=HttpBasicAuth(username="user", password="pass"),
    cookies=[BrowserCookie(name="session", value="abc123", domain="internal.example.com")],
    save_to="report.pdf",
)
```

| Champ | Type | Description |
|-------|------|-------------|
| `page_size` | `str` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"`, et compagnie. |
| `page_width` / `page_height` | `float` | Géométrie de page personnalisée. Ensemble, ils remplacent `page_size`. |
| `orientation` | `"portrait" \| "landscape"` | Portrait par défaut. |
| `margins` | `PdfMargins` | `top`, `bottom`, `left`, `right`, tous facultatifs. |
| `scale` | `float` | Échelle de rendu, par exemple `0.9` pour 90 pour cent. |
| `grayscale` | `bool` | Post-traite le PDF en niveaux de gris. |
| `header` | `PdfHeaderFooter` | `content` (jusqu'à 2000 caractères) et `height`. |
| `footer` | `PdfHeaderFooter` | `content` (jusqu'à 2000 caractères) et `height`. |

`BrowserCookie` prend `name`, `value`, et soit `domain`, soit `url`, plus les champs facultatifs `path`, `expires`, `http_only`, `secure` et `same_site` (`"Strict"`, `"Lax"`, `"None"`). Le SDK mappe `http_only` et `same_site` sur leurs clés camelCase de transport pour vous.

---

## Gestion des erreurs

Les erreurs sont des classes d'exception : attrapez-les donc avec `except`. Ordonnez les gestionnaires du spécifique au général : `AuthenticationError`, `QuotaError` et `RateLimitError` héritent tous de `APIError`, qui hérite de `EnconvertError`.

```python
from enconvert import APIError, AuthenticationError, EnconvertError, QuotaError, RateLimitError

try:
    op = client.v2.perceive("https://example.com")
except AuthenticationError:
    print("Invalid or missing API key. Check ENCONVERT_API_KEY.")
except QuotaError as e:
    print(f"402 from the API: {e.message}")
except RateLimitError:
    print("Too many requests. Back off and retry.")
except APIError as e:
    print(f"API error [{e.status_code}]: {e.message}")
except EnconvertError as e:
    print(f"Rejected before the request was sent: {e}")
```

| Classe | Levée sur | Code de statut |
|-------|-----------|-------------|
| `AuthenticationError` | Clé invalide, manquante ou révoquée | `401`, `403` |
| `QuotaError` | HTTP 402 | `402` |
| `RateLimitError` | Trop de requêtes | `429` |
| `APIError` | Toute autre 4xx ou 5xx | le code réel |
| `EnconvertError` | Classe de base, et validation côté client qui n'atteint jamais le réseau | -- |

`APIError` porte `status_code` et `message`, et son `str()` se lit `[404] Not found`. Les levées côté client que vous pouvez rencontrer : une `api_key` vide, une extension de fichier non prise en charge, une paire de conversion non implémentée, un appel `distill` avec les deux ou aucun de `urls` et `discover_from`, une incohérence entre le mode et les arguments d'`ingest`, une charge utile `update_watcher` vide, et une liste de sorties `perceive_direct` qui ne se résout pas à exactement un artefact. La table complète des messages se trouve dans [Codes d'erreur](/fr/docs/error-codes).

---

## Récupération après timeout

Les conversions longues d'URL et de documents peuvent survivre à un timeout du reverse proxy même quand le serveur termine la tâche. Le SDK Python récupère de façon transparente :

1. Avant chaque conversion de fichier unique ou d'URL unique, le SDK génère une chaîne hexadécimale UUID4 et l'envoie comme `job_id`.
2. Si cette requête revient en 5xx, le SDK bascule sur l'interrogation de `GET /v1/convert/status/{job_id}` toutes les 3 secondes. Un `404` à cet endroit signifie que la ligne de la tâche n'est pas encore écrite, donc l'interrogation continue.
3. Sur `success` le SDK renvoie le résultat comme si de rien n'était ; sur `failed` il lève `APIError(500, ...)` avec le message d'erreur du serveur ; et passé l'échéance d'interrogation de 300 secondes, il lève `APIError(504, "Conversion timed out")`.

Vous n'écrivez aucun code pour cela. Quand une réponse arrive par le chemin de récupération, `ConversionResult.job_id` est renseigné pour que vous puissiez la corréler dans vos logs.

Deux exceptions. `convert_website_to_pdf` et `convert_website_to_screenshot` se retirent de ce mécanisme, parce qu'une soumission de site n'a pas de ligne de tâche propre : une 5xx signifie donc que la soumission elle-même a échoué et remonte directement. Les endpoints V2 ne sont pas interrogés non plus ; utilisez leurs propres identifiants de tâche avec `get_perceive_batch` ou `get_ingest_job`.

---

## Configuration

```python
client = Enconvert(
    api_key=os.environ["ENCONVERT_API_KEY"], timeout=300.0, base_url="https://api.enconvert.com"
)
```

| Option | Type | Défaut | Description |
|--------|------|---------|-------------|
| `api_key` | `str` | -- (obligatoire) | Clé API privée. Une valeur vide lève `EnconvertError` à la construction. |
| `timeout` | `float` | `300.0` | Timeout par requête en secondes, appliqué à chaque appel, téléversements compris. |
| `base_url` | `str` | `https://api.enconvert.com` | URL de base de l'API. Les barres obliques finales sont supprimées. |

Le client conserve une `requests.Session` : les connexions sont donc mutualisées entre les appels ; réutilisez un seul client au lieu d'en construire un par requête. Votre clé voyage dans l'en-tête `X-API-Key`, et les téléchargements `save_to` attaquent l'URL de stockage signée par un simple GET non authentifié écrit sur le disque par blocs de 64 Ko : la clé ne quitte donc jamais l'hôte de l'API.

<div class="alert alert-warning">
<strong>N'écrivez jamais la clé API en dur.</strong> Lisez-la depuis une variable d'environnement ou votre gestionnaire de secrets, et gardez-la côté serveur. Quiconque détient votre clé privée peut lancer des traitements sur votre projet.
</div>

---

## Forme du résultat

Les méthodes de conversion renvoient un `ConversionResult` figé :

```python
@dataclass(frozen=True)
class ConversionResult:
    presigned_url: str
    object_key: str
    filename: str
    file_size: int | None = None
    conversion_time_seconds: float | None = None
    job_id: str | None = None
```

L'URL pré-signée est de courte durée de vie. Passez `save_to`, ou récupérez l'URL vous-même, et stockez les octets dans votre propre bucket quand vous avez besoin d'un accès durable.

| Type | Renvoyé par | Champs clés |
|------|-------------|------------|
| `JobStatus` | `get_job_status` | `status` (`processing`, `success`, `failed`), `presigned_url`, `object_key`, `error` |
| `BatchSubmission`, `BatchStatus` | `convert_website_to_*`, `get_batch_status`, `wait_for_batch` | `batch_id`, `status`, `url_count`, `total`, `completed`, `failed`, `discovery_method`, `zip_download_url`, `items` |
| `PerceiveResult` | `v2.perceive`, `v2.get_perceive_operation` | `operation_id`, `render_quality`, `status_code`, `deductions`, `outputs`, `structured`, `warnings`, `cache_hit` |
| `PerceiveDirectResult` | `v2.perceive_direct`, `v2.download_perceive_artifact` | `content` (octets bruts), `content_type`, `filename`, `render_quality`, `source_status_code` |
| `IngestJob` | `v2.ingest`, `v2.ingest_files`, `v2.get_ingest_job` | `job_id`, `status`, `mode`, `pages_processed`, `total_chunks`, `output_url`, `error_message` |
| `Watcher` | les méthodes watch de `v2` | `watcher_id`, `status`, `frequency_minutes`, `diff_mode`, `checks_count`, `next_check_at`, `last_change_at` |

Les URL d'artefacts V2 sont signées pour 15 minutes et resignées à chaque lecture de l'opération : appelez donc `get_perceive_operation` plutôt que de mettre une chaîne d'URL en cache.

---

## Source et tickets

- **PyPI :** [enconvert](https://pypi.org/project/enconvert/)
- **GitHub :** [conversionapi/python-sdk](https://github.com/conversionapi/python-sdk)
- **Python :** 3.9, 3.10, 3.11, 3.12, 3.13
- **Licence :** MIT
- **Autres clients :** [Tous les SDK](/fr/docs/sdks)

---

## Questions fréquentes

### Comment convertir des fichiers en Python ?

Lancez `pip install enconvert`, construisez un client avec `Enconvert(api_key=os.environ["ENCONVERT_API_KEY"])`, et appelez une méthode typée comme `convert_document`, `convert_image`, `convert_to_pdf` ou `convert_to_markdown`. Passez `save_to="out.pdf"` et le SDK écrit le fichier terminé en flux directement sur le disque, au lieu de vous remettre une URL à aller chercher vous-même.

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

Appelez `client.convert_url_to_pdf("https://example.com", save_to="page.pdf")`. Définissez `single_page=False` plus `pdf_options=PdfOptions(page_size="A4")` pour une sortie paginée, et ajustez `viewport_width`, `load_media` ou `enable_scroll` quand une page a besoin d'un canevas plus large ou d'images chargées en différé.

### Comment convertir du DOCX en PDF en Python ?

Soit `client.convert_document("report.docx", save_to="report.pdf")`, puisque `output_format` vaut déjà `"pdf"` par défaut, soit `client.convert_to_pdf("report.docx", save_to="report.pdf")` quand vous voulez la détection automatique du format côté serveur. Le même appel gère XLSX, PPTX, ODT, ODS, ODP, OTS, Pages et Numbers.

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

`client.convert_image("photo.heic", output_format="webp", save_to="photo.webp")`. Le format d'entrée vient de l'extension du nom de fichier, et `jpeg`, `png`, `svg`, `heic` et `webp` se convertissent tous les uns vers les autres. Vous travaillez en mémoire plutôt que sur disque ? Enveloppez les octets dans `FileData(data=blob, filename="photo.heic")` pour que l'extension survive.

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

Utilisez `client.v2.perceive(url, outputs=["markdown"])` et lisez `op.outputs["markdown"].url`, ou `client.v2.perceive_direct(url, outputs=["markdown"])` pour récupérer les octets dans le corps de la réponse. Ajoutez `only_main_content=True` pour écarter la navigation et les pieds de page. Pour une conversion simple, sans scoring ni extraction, `convert_url_to_markdown` est l'appel le plus léger.

### Que signifie render_quality, et quand faut-il réessayer une page ?

C'est un flottant de 0.0 à 1.0 sur chaque lecture V2 qui dit avec quelle honnêteté la page s'est rendue. Un écran de défi, un mur de cookies, une porte de connexion, une page d'erreur HTTP ou une coquille vide d'application monopage obtiennent un score bas et nomment ce qui s'est déclenché dans `deductions`, avec le détail dans `warnings` et le statut HTTP propre à la page dans `status_code`. Servez-vous-en comme d'une porte : traitez un score bas comme un signal pour réessayer avec `cache_mode="refresh"`, un sélecteur `wait_for` que seul le vrai contenu satisfait, ou des `cookies` différents, plutôt que de donner le texte à votre modèle.

### Comment transformer un site de documentation entier en chunks RAG avec Python ?

Appelez `client.v2.ingest(mode="sitemap", url="https://docs.example.com", max_pages=100, chunk=IngestChunkOptions(max_words=512, sentence_overlap=1))`. La tâche est asynchrone : interrogez donc `get_ingest_job(job_id)` jusqu'à ce que le statut quitte `queued`, `discovering` et `processing`, ou passez `webhook_url` et attendez d'être appelé. Le JSONL terminé se trouve à `output_url`. Pour des documents locaux plutôt qu'un site, `ingest_files` exécute le même pipeline.

### Le SDK Python prend-il en charge asyncio ?

Pas nativement. Toutes les méthodes sont synchrones et construites sur `requests`. Dans une application asynchrone, enveloppez les appels dans `await asyncio.to_thread(client.convert_to_pdf, "report.docx")` ou confiez-les à un `concurrent.futures.ThreadPoolExecutor` pour que la boucle d'événements continue de tourner. Le client peut être partagé entre threads sans risque, car il détient une `requests.Session` mutualisée.

### Comment le SDK survit-il à un timeout de proxy sur une conversion longue ?

Chaque conversion de fichier unique et d'URL unique envoie un `job_id` généré. Si la requête renvoie une 5xx, le SDK interroge `GET /v1/convert/status/{job_id}` toutes les 3 secondes pendant un maximum de 300 secondes, renvoie normalement dès que la tâche rapporte `success`, lève `APIError(500, ...)` si elle rapporte `failed`, et lève `APIError(504, "Conversion timed out")` si l'échéance est dépassée. Les soumissions de lots de sites sautent ce chemin, puisqu'un échec à cet endroit signifie que la soumission elle-même n'a pas abouti.
