---
seo_title: Python SDK für Dateikonvertierung: pip install enconvert | EnConvert
meta_desc: Das offizielle Python-SDK von EnConvert: per pip installieren, über 40 Formate konvertieren und mit client.v2 Webseiten auslesen, durchsuchen und überwachen.
keywords: python sdk dateikonvertierung, dateien konvertieren python, url zu pdf python, python web scraping api, docx zu pdf python, enconvert python sdk, pip install enconvert, heic zu webp python, html zu markdown python, rag pipeline python, webseite auf änderungen überwachen python
---

# Python SDK für Dateikonvertierung

`enconvert` ist der offizielle Python-Client für die EnConvert-API: ein `pip install`, ein API-Schlüssel und typisierte Methoden, um Dateien zu konvertieren und das Live-Web zu lesen. Der Client läuft ab Python 3.9 mit einer einzigen Laufzeit-Abhängigkeit, `requests`, und liefert Inline-Typhinweise sowie einen `py.typed`-Marker mit, damit mypy und Pyright alles sehen. Er hat zwei Oberflächen. Zwölf Konvertierungsmethoden decken 43 `{input}-to-{output}`-Paare ab, dazu URL zu PDF, Screenshot und Markdown. Der Namensraum `client.v2` ergänzt Web-Intelligenz: perceive, discover, lookup, distill, ingest und watch.

<div class="alert alert-info">
<strong>PyPI:</strong> <code>enconvert</code> &middot; <strong>Quelle:</strong> <a href="https://github.com/conversionapi/python-sdk">conversionapi/python-sdk</a> &middot; <strong>Python:</strong> 3.9+ &middot; <strong>Laufzeit-Abhängigkeit:</strong> <code>requests&gt;=2.28</code> &middot; <strong>Lizenz:</strong> MIT
</div>

---

## Installation

```bash
pip install enconvert
```

`uv add enconvert` und `poetry add enconvert` funktionieren genauso. `requests` ist die einzige Laufzeit-Abhängigkeit, und die Type Stubs stecken im Wheel, es gibt also kein `types-`-Paket hinterherzujagen.

---

## Schnellstart

Lies eine Seite so, wie dein Agent es tun sollte, mit angehängtem Qualitätswert, und konvertiere anschließend über denselben Client eine lokale Datei.

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

Jede Methode arbeitet synchron und blockierend. Das SDK ist **ausschließlich serverseitig**: Es authentifiziert sich mit einem privaten API-Schlüssel, liefere es also niemals in einem Desktop-, Mobil- oder Browser-Client aus. Einen Schlüssel bekommst du in deinem [Dashboard](/de/dashboard), und wie Schlüssel abgegrenzt sind, steht unter [Authentifizierung](/de/docs/authentication).

---

## Was der Client bereitstellt

`Enconvert` ist die gesamte öffentliche Oberfläche. Konvertierungsmethoden hängen direkt am Client; alles Webseitige lebt unter `client.v2`.

| Konvertierungsmethode | Endpunkt | Rückgabe |
|--------|----------|---------|
| `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}` (per Polling) | `BatchStatus` |

| `client.v2`-Fähigkeit | Methoden | Rückgabe |
|------------|---------|---------|
| 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` |

Jedes Argument nach dem ersten positionellen ist keyword-only, in snake_case und optional, sofern eine Tabelle nichts anderes sagt. Ergebnisse sind eingefrorene Dataclasses, erzeuge also eine neue Instanz, statt eine bestehende zu verändern. Die REST-Formen stehen in der [Endpunkt-Übersicht](/de/docs/endpoints-overview).

---

## Dateikonvertierung

Jede Konvertierungsmethode nimmt `save_to` entgegen (ein `str` oder `os.PathLike`; das Ergebnis wird dorthin gestreamt, übergeordnete Verzeichnisse werden angelegt) sowie `output_filename` (überschreibt den generierten Namen). Beide fehlen in den Tabellen unten.

### convert_url_to_pdf

Rendert jede erreichbare URL als PDF.

```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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `single_page` | `bool` | `True` | `True` liefert eine durchgehende Seite. `False` paginiert anhand von `pdf_options.page_size`. |
| `pdf_options` | `PdfOptions` | -- | Seitengröße, Ausrichtung, Ränder, Skalierung, Graustufen, Kopfzeile, Fußzeile. Siehe [PDF-Optionen](#pdf-optionen). |
| `viewport_width` / `viewport_height` | `int` | `1920` / `1080` | Größe des Browser-Viewports in Pixeln. |

`load_media` und `enable_scroll` sind beide standardmäßig `True`: Ersteres wartet auf Bilder und Videos, Letzteres scrollt von oben nach unten, damit Lazy Loader auslösen. Drei weitere Optionen erreichen Seiten hinter einer Schranke: `auth` (`HttpBasicAuth`), `cookies` (`list[BrowserCookie]`) und `headers` (`dict[str, str]`).

<div class="alert alert-warning">
<strong>Kombiniere <code>auth</code> nicht mit einem <code>Authorization</code>-Header.</strong> Die API lehnt den Konflikt ab, statt zu raten, welche Anmeldedaten gelten sollen. Entscheide dich für eines von beidem.
</div>

---

### convert_url_to_screenshot

Nimmt ein PNG einer beliebigen URL auf.

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

Nimmt dieselben Optionen für Viewport, Medien, Scrollen, Dateinamen und Browser-Zugang entgegen wie `convert_url_to_pdf`, ohne `single_page` und `pdf_options`.

---

### convert_url_to_markdown

Zieht sauberes GitHub-Flavored Markdown aus einer URL. Navigation, Fußzeilen, Werbung und Skripte werden entfernt, der Hauptartikeltext bleibt erhalten, und YAML-Frontmatter mit title, description, url, links und images wird vorangestellt.

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

Derselbe Optionssatz wie bei `convert_url_to_screenshot`. Wenn du zusätzlich einen Qualitätswert, Seitenmetadaten oder eine strukturierte Extraktion im selben Lesevorgang möchtest, nimm stattdessen [`v2.perceive`](#perceive).

---

### convert_image

Konvertiert zwischen `jpeg`, `png`, `svg`, `heic` und `webp` oder rastert ein PDF nach JPEG. Das Eingabeformat ergibt sich aus der Dateiendung.

```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` ist erforderlich und muss einer der Werte `jpeg`, `png`, `svg`, `heic` oder `webp` sein; die Aliase `jpg`, `yml`, `htm` und `md` werden für dich normalisiert. `file` akzeptiert einen Pfad-String, ein `os.PathLike`, rohe `bytes` oder einen `FileData(data, filename)`-Wrapper. Rohe `bytes` tragen keinen Dateinamen und werden als `upload.bin` hochgeladen, nimm also `FileData`, sobald die Endung eine Rolle spielt.

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

Konvertiert Dokumente und Datenformate. `output_format` ist standardmäßig `"pdf"`, und `pdf_options` wird berücksichtigt, wenn die Ausgabe ein PDF ist.

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

**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`.

Für EPUB gibt es kein eigenes Dokumentpaar. Schicke `.epub` durch `convert_to_pdf` oder `convert_to_markdown`.

---

### convert_to_markdown

Erkennt ein hochgeladenes Dokument serverseitig automatisch und liefert sauberes Markdown zurück. Das ist der RAG-Ingestion-Baustein für eine einzelne Datei.

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

**Akzeptierte Eingaben:** PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie alte und ODF-Office-Formate. Bilder werden hier nicht unterstützt, und der Endpunkt kennt keine PDF-Optionen. Für eine ganze Website statt einer einzelnen Datei nimm [`v2.ingest`](#ingest).

---

### convert_to_pdf

Erkennt eine hochgeladene Datei serverseitig automatisch und liefert ein PDF zurück.

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

**Akzeptierte Eingaben:** Office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, Klartext, Rasterbilder, SVG, EPUB und ein bestehendes PDF zum Durchreichen. Weil eine `.pdf`-Eingabe durchgereicht wird, dient die Methode zugleich als Graustufen-Normalisierer.

<div class="alert alert-warning">
<strong>Nur <code>pdf_options.grayscale</code> wird an diesem Endpunkt berücksichtigt.</strong> Die übrigen Felder zur Seitengeometrie werden hier ignoriert. Wenn du echtes Seitenlayout brauchst, leite die Datei stattdessen über <code>convert_document</code> oder <code>convert_url_to_pdf</code>.
</div>

---

### convert_website_to_pdf und convert_website_to_screenshot

Ermittelt jede Seite einer Website, konvertiert sie im Hintergrund und sammelt alles in einem einzigen ZIP. Beide sind bewusst asynchron und liefern sofort eine `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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `crawl_mode` | `"auto" \| "sitemap" \| "full"` | Server-Standard | `sitemap` liest ausschließlich `sitemap.xml`. `full` ergänzt einen Breitensuch-Crawl. `auto` wählt den höchsten für den Schlüssel verfügbaren Modus. |
| `include_patterns` / `exclude_patterns` | `list[str]` | -- | URLs anhand eines Pfadfragments behalten oder verwerfen. Ausschlüsse greifen nur im Full-Crawl-Modus. |
| `notification_email` / `callback_url` | `str` | -- | Adresse für die E-Mail und Webhook für den Aufruf, sobald der Batch fertig ist. |
| `single_page` | `bool` | Server-Standard | Nur PDF. |
| `pdf_options` | `PdfOptions` | -- | Nur PDF. Siehe [PDF-Optionen](#pdf-optionen). |

Die Render-Optionen pro Seite gelten ebenfalls für jede ermittelte URL: `viewport_width`, `viewport_height`, `load_media`, `enable_scroll`, `auth`, `cookies` und `headers`. Was du nicht setzt, behält den Standard des Gateways.

`wait_for_batch` akzeptiert `interval_seconds` (Standard `5.0`), `timeout_seconds` (Standard `1800.0`) und `save_to`. Es löst `APIError(504, ...)` aus, wenn der Batch bei Ablauf der Frist noch läuft.

---

### Unterstützte Konvertierungspaare

Das SDK trägt eine Kopie der Konvertierungskarte des Gateways bei sich und lehnt ein nicht implementiertes Paar lokal ab, vor jedem Netzwerk-Roundtrip, und nennt in der Meldung die gültigen Ausgaben für diese Eingabe.

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

Das sind 43 implementierte Paare. Programmatisch prüfen kannst du sie so:

```python
from enconvert import IMPLEMENTED_CONVERSIONS, valid_outputs_for

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

Die vollständige Parameter-Referenz steht unter [Parameter und Optionen](/de/docs/parameters-options).

---

## Web-Intelligenz (V2)

Jeder V2-Lesevorgang trägt `render_quality`, einen Float zwischen 0.0 und 1.0, der angibt, wie ehrlich die Seite gerendert hat. Ein Challenge-Screen, eine Cookie-Wand, eine Login-Schranke, eine HTTP-Fehlerseite oder eine leere SPA-Hülle kommt mit niedrigem Wert, einer `deductions`-Zuordnung mit den ausgelösten Abzügen und einer `warnings`-Liste zurück. Der Inhalt wird trotzdem geliefert, nur eben markiert, damit ein schlechter Lesevorgang nie unbemerkt in den Kontext deines Agenten gelangt. Behandle den Wert als Schranke und prüfe ihn, bevor du den Text verwendest. `PerceiveResult`, `PerceiveDirectResult`, `DistillItem`, `LookupItem.perceive` und `WatcherSnapshot` stellen ihn alle bereit. Die Konzepte stehen in der [V2-Übersicht](/de/docs/v2-overview).

### Perceive

Rendert eine URL in agentenfertige Artefakte. Synchron: Der Aufruf liefert die abgeschlossene Operation mit signierten Artefakt-URLs zurück.

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

print(op.render_quality)            # 0.0 bis 1.0
print(op.status_code)               # HTTP-Status der Seite selbst
print(op.deductions)                # z. B. {"login_wall": 0.65}
print(op.outputs["markdown"].url)   # signierte URL, 15 Minuten
print(op.structured)

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

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `outputs` | `list[str]` | `["markdown", "structured"]` | Beliebige aus `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `extract` | `list[str]` | -- | Beliebige aus `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `wait_for` | `str` | -- | CSS-Selektor, auf den vor der Aufnahme gewartet wird. |
| `viewport` | `PerceiveViewport` | 1920 x 1080 | `width` 320 bis 3840, `height` 240 bis 2160. |
| `cache_mode` | `"enabled" \| "bypass" \| "refresh"` | Server-Standard | Das zwischengespeicherte Rendering wiederverwenden, überspringen oder neu schreiben. |
| `block_resources` | `list[str]` | -- | Beliebige aus `image`, `media`, `font`, `stylesheet`, `script`, `xhr`, `fetch`, `websocket`, `manifest`, `other`. |
| `only_main_content` | `bool` | Server-Standard | Entfernt Navigation, Header, Footer und sonstiges Seiten-Chrome aus dem extrahierten Inhalt. |
| `direct_download` | `bool` | `False` | Liefert rohe Bytes statt einer signierten URL. Nur von `perceive` akzeptiert; der Batch-Endpunkt lehnt die Option ab. |

Ebenfalls akzeptiert: `schema` (`dict`, ein frei geformtes Extraktionsschema, das unverändert durchgereicht wird), `js_code` (`str`, wird vor der Aufnahme in der Seite ausgeführt), `wait_timeout_ms` (`int`), `headers` (`dict[str, str]`), `cookies` (`list[BrowserCookie]`), `auth` (`HttpBasicAuth`), `proxy_url` (`str`), `geolocation` (`dict`), `action_chain` (`list[dict]` mit skriptgesteuerten Klick-, Tipp- und Scroll-Schritten), `pdf_options` (`PdfOptions`, angewandt auf die `pdf`-Ausgabe), `mobile` (`bool`) und `respect_robots` (`bool`). `perceive_batch` nimmt denselben Satz entgegen, abgesehen von `direct_download`.

Artefakt-URLs werden bei jedem Lesen neu signiert, rufe also `client.v2.get_perceive_operation(op.operation_id)` für eine frische URL auf, statt den String zwischenzuspeichern.

**Rohe Bytes, ohne Umweg über eine signierte URL.** `perceive_direct` erzwingt `direct_download` und reicht dir das Artefakt selbst, mit Metadaten aus den Antwort-Headern.

```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` verlangt genau eine artefakterzeugende Ausgabe aus `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links` und `images`. `structured` darf mitfahren, bleibt serverseitig aber inline; übergibst du etwas anderes, löst das SDK `EnconvertError` aus, bevor es die Anfrage sendet. Bei `download_perceive_artifact` darf `output` entfallen, wenn die Operation genau ein Artefakt erzeugt hat, und ein Artefakt jenseits seiner Aufbewahrungsfrist antwortet mit `410`.

**Batches.** Bis zu 1000 URLs teilen sich einen Optionsblock. Kleine Batches werden inline fertig; größere kommen mit dem Status `"queued"` zurück, frage also die Job-ID ab.

```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` ist `"manifest"` (Standard) oder `"zip"`; bei `"zip"` liegt das fertige Bündel unter `done.zip.url`. Details unter [Perceive](/de/docs/v2-perceive).

### Discover

Zählt die URLs einer Website ohne Browser-Rendering auf, was den Schritt zum günstigen ersten Zug macht, bevor du überhaupt etwas wahrnimmst.

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

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `mode` | `"sitemap" \| "crawl" \| "hybrid"` | Server-Standard | Sitemap-Auswertung, ein HTTP-Crawl oder beides zusammengeführt und dedupliziert. |
| `max_urls` / `max_depth` | `int` | Server-Standard | Limit für zurückgegebene URLs (`truncated` ist `True`, wenn es mehr gab) und Crawl-Tiefe ab der Start-URL. |
| `same_domain_only` | `bool` | Server-Standard | Bleibt auf dem Start-Host. |
| `respect_robots` | `bool` | Server-Standard | Beachtet `robots.txt`. |

`include_patterns` und `exclude_patterns` (beide `list[str]`) filtern die Ergebnismenge. `DiscoverResult.sources` enthält die rohen Zählungen pro Quelle vor der Deduplizierung, etwa `{"sitemap": 42, "crawl": 30}`. Mehr dazu unter [Discover](/de/docs/v2-discover).

### Lookup

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

```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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `category` | `"web" \| "news" \| "images" \| "scholar" \| "patents" \| "maps"` | Server-Standard | Suchvertikale. |
| `time_filter` | `"hour" \| "day" \| "week" \| "month" \| "year"` | -- | Aktualitätsfenster. |
| `num_results` / `page` | `int` | Server-Standard | Treffer pro Seite und die 1-basierte Seitennummer. |
| `perceive_top` | `int` | `0` | Rendert die Top-N-Ergebnis-URLs automatisch. Jeder Treffer bringt sein vollständiges `PerceiveResult` unter `hit.perceive` mit. |

Ebenfalls akzeptiert: `country` (`str`), `locale` (`str`), `location` (`str`) für ortsabhängige Suchanfragen und `autocorrect` (`bool`). `LookupResult` trägt `answer_box`, `knowledge_graph`, `perceive_operation_ids` und `perceive_top`, wobei Letzteres meldet, wie viele Treffer tatsächlich gerendert wurden, was weniger sein kann als angefordert. Referenz unter [Lookup](/de/docs/v2-lookup).

### Distill

Schemagesteuerte strukturierte Extraktion über eine oder viele Seiten. Übergib genau eines von `urls` oder `discover_from`; `schema` ist immer erforderlich. Beide Regeln werden clientseitig geprüft und lösen `EnconvertError` aus, bevor eine Anfrage rausgeht.

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

Das optionale `css_schema` läuft zuerst und beantwortet alles, was die Selektoren erreichen. Nur die Felder, die es verfehlt, eskalieren auf die Sprachmodell-Stufe, und `extraction_tier` meldet bei jedem Eintrag, welcher Pfad gelaufen ist: `css`, `llm`, `mixed` oder `none`.

Ermitteln und destillieren in einem Aufruf:

```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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `schema` | `dict` | -- (erforderlich) | Die Ausgabeform, die du zurückbekommen willst, wird unverändert durchgereicht. |
| `urls` | `list[str]` | -- | Explizite Seitenliste. Schließt `discover_from` aus. |
| `discover_from` | `DistillDiscoverFrom` | -- | `url`, optionales `mode`, optionales `max_pages` (1 bis 50, Standard 10). |
| `css_schema` | `CssSchema` | -- | Selektor-Durchgang vor jedem Modellaufruf. |

Ebenfalls akzeptiert: `wait_for` (`str`), `wait_timeout_ms` (`int`), `headers` (`dict[str, str]`), `cookies` (`list[BrowserCookie]`) und `respect_robots` (`bool`). `CssField` unterstützt die Typen `text`, `attribute`, `html`, `regex`, `nested`, `list` und `nested_list`, mit optionalem `attribute`, `pattern`, `default`, `transform` (`lowercase`, `uppercase`, `strip`) und verschachtelten `fields` bis zu fünf Ebenen tief. Siehe [Distill](/de/docs/v2-distill).

### Ingest

Verwandelt eine ganze Website, eine explizite URL-Liste oder einen Stapel hochgeladener Dokumente in gechunktes, RAG-fertiges JSONL. Ingest läuft immer asynchron.

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

Hochgeladene Dateien durchlaufen denselben Job-Lebenszyklus im Modus `files`. Akzeptiert werden PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie alte und ODF-Office-Formate, und jeder Eintrag darf ein Pfad, ein `os.PathLike`, rohe `bytes` oder ein `FileData` sein.

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

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `mode` | `"urls" \| "sitemap" \| "crawl" \| "files"` | `"urls"` | `urls` braucht eine nicht leere `urls`-Liste und lehnt `url` ab. Jeder andere Modus braucht eine Start-`url` und lehnt `urls` ab. |
| `url` / `urls` | `str` / `list[str]` | -- | Start-URL für `sitemap` und `crawl` oder die explizite Seitenliste für den Modus `urls`. |
| `max_pages` | `int` | Server-Standard | Limit für die Anzahl ingestierter Seiten. |
| `chunk` | `IngestChunkOptions` | -- | `max_words` 32 bis 4000, Standard 512. `sentence_overlap` 0 bis 10, Standard 1. Kombiniere es mit `webhook_url` (`str`), das aufgerufen wird, sobald der Job einen Endzustand erreicht. |

Crawl-Steuerung und Render-Optionen werden ebenfalls akzeptiert: `max_depth`, `same_domain_only`, `include_patterns`, `exclude_patterns`, `respect_robots`, `wait_for` und `wait_timeout_ms`. `ingest_files` nimmt nur `chunk` und `webhook_url` entgegen.

```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` ist idempotent; das Abbrechen eines bereits beendeten Jobs liefert ihn unverändert zurück. `retry_ingest_webhook` antwortet mit `409`, wenn der Job nicht abgeschlossen ist, und mit `400`, wenn kein Webhook konfiguriert war. `rotate_webhook_secret` macht das vorherige Secret sofort ungültig. Ausführlicher unter [Ingest](/de/docs/v2-ingest).

### Watch

Rendert eine URL in festem Takt neu und meldet dir, wenn sie sich ändert.

```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 | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `frequency_minutes` | `int` | Server-Standard | Prüfintervall. Eine Stunde ist die Untergrenze. |
| `diff_mode` | `"auto" \| "text" \| "structured" \| "tables" \| "metadata"` | Server-Standard | Welche Ebene der Seite die Diff-Engine vergleicht. |
| `track_fields` | `dict` | -- | Benannte Felder zum Verfolgen, unverändert durchgereicht. |
| `webhook_url` | `str` | -- | Wird bei jeder erkannten Änderung aufgerufen. |
| `notify_email` | `bool` | Server-Standard | Verschickt Änderungsbenachrichtigungen per E-Mail. |

`update_watcher` braucht mindestens ein Feld und löst sonst `EnconvertError` aus; `status` akzeptiert `"active"` oder `"paused"`, und ein leeres `webhook_url` löscht den Webhook. `delete_watcher` ist ein weicher, idempotenter Löschvorgang, der den stillgelegten Watcher mit Status `"deleted"` zurückgibt; danach antwortet der Watcher mit `404`.

<div class="alert alert-warning">
<strong>Snapshot-Diffs tragen nicht vertrauenswürdige Seiteninhalte.</strong> Die Dicts in <code>WatcherSnapshot.changes</code> stammen direkt von der überwachten Website. Escape sie, bevor du sie in HTML, einen E-Mail-Text oder eine Chat-Nachricht renderst.
</div>

Die Diff-Engine und die Benachrichtigungs-Payloads sind unter [Watch](/de/docs/v2-watch) dokumentiert.

---

## PDF-Optionen

`PdfOptions` ist eine eingefrorene Dataclass, die sich `convert_url_to_pdf`, `convert_website_to_pdf`, `convert_document`, `convert_to_pdf` und die `v2.perceive`-Familie teilen.

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

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `page_size` | `str` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` und Verwandte. |
| `page_width` / `page_height` | `float` | Eigene Seitengeometrie. Gemeinsam gesetzt überschreiben sie `page_size`. |
| `orientation` | `"portrait" \| "landscape"` | Standard ist Hochformat. |
| `margins` | `PdfMargins` | `top`, `bottom`, `left`, `right`, alle optional. |
| `scale` | `float` | Render-Skalierung, zum Beispiel `0.9` für 90 Prozent. |
| `grayscale` | `bool` | Wandelt das PDF per Nachbearbeitung in Graustufen um. |
| `header` | `PdfHeaderFooter` | `content` (bis zu 2000 Zeichen) und `height`. |
| `footer` | `PdfHeaderFooter` | `content` (bis zu 2000 Zeichen) und `height`. |

`BrowserCookie` nimmt `name`, `value` und entweder `domain` oder `url` entgegen, dazu optional `path`, `expires`, `http_only`, `secure` und `same_site` (`"Strict"`, `"Lax"`, `"None"`). Das SDK bildet `http_only` und `same_site` für dich auf ihre camelCase-Wire-Keys ab.

---

## Fehlerbehandlung

Fehler sind Exception-Klassen, fange sie also mit `except` ab. Ordne die Handler von spezifisch nach allgemein: `AuthenticationError`, `QuotaError` und `RateLimitError` erben alle von `APIError`, das wiederum von `EnconvertError` erbt.

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

| Klasse | Ausgelöst bei | Statuscode |
|-------|-----------|-------------|
| `AuthenticationError` | Ungültiger, fehlender oder widerrufener Schlüssel | `401`, `403` |
| `QuotaError` | HTTP 402 | `402` |
| `RateLimitError` | Zu viele Anfragen | `429` |
| `APIError` | Jeder andere 4xx- oder 5xx-Fehler | der tatsächliche Code |
| `EnconvertError` | Basisklasse sowie clientseitige Validierung, die das Netzwerk nie erreicht | -- |

`APIError` trägt `status_code` und `message`, und sein `str()` liest sich als `[404] Not found`. Clientseitige Fehler, auf die du stoßen kannst: ein leerer `api_key`, eine nicht unterstützte Dateiendung, ein nicht implementiertes Konvertierungspaar, ein `distill`-Aufruf mit beiden oder keinem von `urls` und `discover_from`, eine Diskrepanz zwischen `ingest`-Modus und Argumenten, ein leeres `update_watcher`-Payload und eine `perceive_direct`-Ausgabeliste, die nicht genau ein Artefakt ergibt. Die vollständige Zuordnung der Meldungen steht unter [Fehlercodes](/de/docs/error-codes).

---

## Timeout-Recovery

Lange URL- und Dokumentkonvertierungen können ein Reverse-Proxy-Timeout überdauern, selbst wenn der Server den Job zu Ende bringt. Das Python-SDK fängt das transparent ab:

1. Vor jeder Einzeldatei- oder Einzel-URL-Konvertierung erzeugt das SDK einen UUID4-Hex-String und sendet ihn als `job_id`.
2. Kommt diese Anfrage mit 5xx zurück, wechselt das SDK alle 3 Sekunden auf Polling von `GET /v1/convert/status/{job_id}`. Ein `404` dort bedeutet, dass die Job-Zeile noch nicht geschrieben ist, das Polling läuft also weiter.
3. Bei `success` liefert das SDK das Ergebnis, als wäre nichts gewesen; bei `failed` löst es `APIError(500, ...)` mit der Fehlermeldung des Servers aus; und jenseits der Polling-Frist von 300 Sekunden löst es `APIError(504, "Conversion timed out")` aus.

Dafür schreibst du keine Zeile Code. Kommt eine Antwort über den Recovery-Pfad, ist `ConversionResult.job_id` gefüllt, sodass du sie in deinen Logs zuordnen kannst.

Zwei Ausnahmen. `convert_website_to_pdf` und `convert_website_to_screenshot` steigen aus, weil eine Website-Einreichung keine eigene Job-Zeile hat. Ein 5xx bedeutet dort, dass die Einreichung selbst fehlgeschlagen ist, und das kommt direkt an die Oberfläche. V2-Endpunkte werden ebenfalls nicht abgefragt; nutze deren eigene Job-IDs mit `get_perceive_batch` oder `get_ingest_job`.

---

## Konfiguration

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

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `api_key` | `str` | -- (erforderlich) | Privater API-Schlüssel. Ein leerer Wert löst beim Konstruieren `EnconvertError` aus. |
| `timeout` | `float` | `300.0` | Timeout pro Anfrage in Sekunden, angewandt auf jeden Aufruf einschließlich Uploads. |
| `base_url` | `str` | `https://api.enconvert.com` | Basis-URL der API. Schrägstriche am Ende werden entfernt. |

Der Client hält eine `requests.Session`, Verbindungen werden also über Aufrufe hinweg gepoolt; verwende einen Client wieder, statt pro Anfrage einen neuen zu bauen. Dein Schlüssel reist als `X-API-Key`-Header mit, und `save_to`-Downloads gehen als schlichtes, unauthentifiziertes GET an die signierte Storage-URL und werden in 64-KB-Blöcken auf die Festplatte gestreamt, der Schlüssel verlässt den API-Host also nie.

<div class="alert alert-warning">
<strong>Hardcode den API-Schlüssel niemals.</strong> Lies ihn aus einer Umgebungsvariable oder deinem Secret-Manager und halte ihn serverseitig. Wer deinen privaten Schlüssel besitzt, kann Arbeit gegen dein Projekt laufen lassen.
</div>

---

## Form des Ergebnisses

Konvertierungsmethoden liefern ein eingefrorenes `ConversionResult`:

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

Die vorsignierte URL ist kurzlebig. Übergib `save_to` oder rufe die URL selbst ab und lege die Bytes in deinem eigenen Bucket ab, wenn du dauerhaften Zugriff brauchst.

| Typ | Zurückgegeben von | Wichtige Felder |
|------|-------------|------------|
| `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` (rohe Bytes), `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` | die `v2`-Watch-Methoden | `watcher_id`, `status`, `frequency_minutes`, `diff_mode`, `checks_count`, `next_check_at`, `last_change_at` |

V2-Artefakt-URLs sind 15 Minuten lang signiert und werden bei jedem Lesen der Operation neu signiert, rufe also `get_perceive_operation` auf, statt einen URL-String zwischenzuspeichern.

---

## Quellcode und Issues

- **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
- **Lizenz:** MIT
- **Andere Clients:** [Alle SDKs](/de/docs/sdks)

---

## Häufig gestellte Fragen

### Wie konvertiere ich Dateien in Python?

Führe `pip install enconvert` aus, baue einen Client mit `Enconvert(api_key=os.environ["ENCONVERT_API_KEY"])` und rufe eine typisierte Methode wie `convert_document`, `convert_image`, `convert_to_pdf` oder `convert_to_markdown` auf. Übergib `save_to="out.pdf"`, dann streamt das SDK die fertige Datei direkt auf die Festplatte, statt dir eine URL zum Selbstabholen zu geben.

### Wie konvertiere ich in Python eine URL nach PDF?

Rufe `client.convert_url_to_pdf("https://example.com", save_to="page.pdf")` auf. Setze `single_page=False` plus `pdf_options=PdfOptions(page_size="A4")` für paginierte Ausgabe und passe `viewport_width`, `load_media` oder `enable_scroll` an, wenn eine Seite eine breitere Leinwand oder nachgeladene Bilder braucht.

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

Entweder `client.convert_document("report.docx", save_to="report.pdf")`, denn `output_format` steht ohnehin standardmäßig auf `"pdf"`, oder `client.convert_to_pdf("report.docx", save_to="report.pdf")`, wenn du die serverseitige Formaterkennung möchtest. Derselbe Aufruf bewältigt XLSX, PPTX, ODT, ODS, ODP, OTS, Pages und Numbers.

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

`client.convert_image("photo.heic", output_format="webp", save_to="photo.webp")`. Das Eingabeformat ergibt sich aus der Dateiendung, und `jpeg`, `png`, `svg`, `heic` und `webp` lassen sich alle ineinander umwandeln. Du arbeitest aus dem Speicher statt von der Festplatte? Verpacke die Bytes in `FileData(data=blob, filename="photo.heic")`, damit die Endung erhalten bleibt.

### Wie lese ich eine Webseite in Python als sauberes Markdown aus?

Nimm `client.v2.perceive(url, outputs=["markdown"])` und lies `op.outputs["markdown"].url`, oder `client.v2.perceive_direct(url, outputs=["markdown"])`, um die Bytes im Antwortkörper zurückzubekommen. Ergänze `only_main_content=True`, um Navigation und Fußzeilen zu entfernen. Für eine schlichte Konvertierung ohne Bewertung und Extraktion ist `convert_url_to_markdown` der leichtere Aufruf.

### Was bedeutet render_quality, und wann sollte ich eine Seite erneut abrufen?

Es ist ein Float zwischen 0.0 und 1.0 bei jedem V2-Lesevorgang, der angibt, wie ehrlich die Seite gerendert hat. Ein Challenge-Screen, eine Cookie-Wand, eine Login-Schranke, eine HTTP-Fehlerseite oder eine leere SPA-Hülle bekommt einen niedrigen Wert und benennt in `deductions`, was ausgelöst hat, mit Details in `warnings` und dem eigenen HTTP-Status der Seite in `status_code`. Nutze ihn als Schranke: Behandle einen niedrigen Wert als Signal, es mit `cache_mode="refresh"`, einem `wait_for`-Selektor, auf den nur echter Inhalt passt, oder anderen `cookies` erneut zu versuchen, statt den Text an dein Modell zu geben.

### Wie verwandle ich in Python eine ganze Dokumentations-Website in RAG-Chunks?

Rufe `client.v2.ingest(mode="sitemap", url="https://docs.example.com", max_pages=100, chunk=IngestChunkOptions(max_words=512, sentence_overlap=1))` auf. Der Job ist asynchron, frage also entweder `get_ingest_job(job_id)` ab, bis der Status `queued`, `discovering` und `processing` verlassen hat, oder übergib `webhook_url` und warte auf den Rückruf. Das fertige JSONL liegt unter `output_url`. Für lokale Dokumente statt einer Website durchläuft `ingest_files` dieselbe Pipeline.

### Unterstützt das Python-SDK asyncio?

Nicht von Haus aus. Jede Methode arbeitet synchron und baut auf `requests` auf. In einer asynchronen Anwendung verpackst du Aufrufe in `await asyncio.to_thread(client.convert_to_pdf, "report.docx")` oder übergibst sie einem `concurrent.futures.ThreadPoolExecutor`, damit die Event Loop weiterläuft. Der Client lässt sich gefahrlos über Threads hinweg teilen, weil er eine gepoolte `requests.Session` hält.

### Wie übersteht das SDK ein Proxy-Timeout bei einer langen Konvertierung?

Jede Einzeldatei- und Einzel-URL-Konvertierung sendet eine erzeugte `job_id`. Liefert die Anfrage 5xx, fragt das SDK bis zu 300 Sekunden lang alle 3 Sekunden `GET /v1/convert/status/{job_id}` ab, kehrt normal zurück, sobald der Job `success` meldet, löst `APIError(500, ...)` aus, wenn er `failed` meldet, und löst `APIError(504, "Conversion timed out")` aus, wenn die Frist verstreicht. Website-Batch-Einreichungen überspringen diesen Pfad, denn ein Fehlschlag bedeutet dort, dass die Einreichung selbst nicht angekommen ist.
