---
seo_title: PHP SDK für Dateikonvertierung: Composer-Client | EnConvert
meta_desc: Offizielles EnConvert PHP SDK für PHP 8.1+. Per Composer installieren, Dokumente und Bilder konvertieren und Webseiten per perceive, discover, distill, ingest und watch lesen.
keywords: php sdk dateikonvertierung, dateien konvertieren php, url zu pdf php, web scraping api php, docx in pdf konvertieren php, enconvert php sdk, composer paket dateikonvertierung, heic in webp konvertieren php, html in pdf umwandeln php composer, url zu markdown api php, dokumentkonvertierung laravel api
---

# PHP SDK für Dateikonvertierung

`enconvert/enconvert-php` ist der offizielle PHP-Client für die EnConvert API. Installiere ihn mit Composer, gib ihm einen API-Key, und du bekommst zwei Dinge: zwölf Konvertierungsmethoden, die URLs, Bilder und Dokumente in PDFs, PNGs, Markdown und Datenformate verwandeln, sowie einen `$client->v2`-Namensraum, der das lebende Web in agentenfertiges Markdown, Screenshots und strukturiertes JSON liest. Das SDK zielt auf PHP 8.1+, basiert auf Guzzle 7, nutzt durchgehend camelCase-Options-Arrays und gibt readonly typisierte Ergebnisobjekte statt loser Arrays zurück. Lange Konvertierungen, die ein HTTP-Timeout überdauern, werden per Job-Polling automatisch aufgefangen.

<div class="alert alert-info">
<strong>Composer:</strong> <code>enconvert/enconvert-php</code> · <strong>Quelle:</strong> <a href="https://github.com/conversionapi/php-sdk">conversionapi/php-sdk</a> · <strong>PHP:</strong> 8.1+ · <strong>Benötigt:</strong> <code>guzzlehttp/guzzle ^7.8</code>
</div>

---

## Installation

```bash
composer require enconvert/enconvert-php
```

Das Paket lädt automatisch unter dem PSR-4-Namensraum `Enconvert\` und bringt kein CLI, keine Konfigurationsdatei und keinen Service Provider mit. Es funktioniert unverändert in reinem PHP, Laravel, Symfony, WordPress und jedem PSR-4-Projekt.

---

## Schnellstart

```php
<?php

require __DIR__ . '/vendor/autoload.php';

use Enconvert\Client;

$client = new Client(getenv('ENCONVERT_API_KEY'));

// Eine Live-Seite in ein PDF konvertieren und direkt auf die Festplatte streamen.
$result = $client->convertUrlToPdf('https://example.com', [
    'saveTo' => 'page.pdf',
]);

echo $result->presignedUrl, ' ', $result->fileSize, ' bytes', PHP_EOL;

// Das Lesen einer Seite für einen Agenten nutzt denselben Client und denselben Key.
$op = $client->v2->perceive('https://example.com', ['outputs' => ['markdown', 'structured']]);

echo $op->renderQuality, PHP_EOL;              // 0.0 bis 1.0, z. B. 0.93
echo $op->outputs['markdown']->url, PHP_EOL;   // signierte Download-URL
```

Das SDK ist ausschließlich serverseitig. Ein privater Key (`sk_live_...`) darf niemals in einen Browser oder eine Mobile-App gelangen.

---

## Was der Client bereitstellt

| Oberfläche | Wie du sie erreichst | Was sie tut |
|---------|------------------|--------------|
| Datei- und URL-Konvertierung | `$client->convert*()` | Zwölf Methoden über `POST /v1/convert/*`, plus Job- und Batch-Polling. |
| Web-Intelligence (V2) | `$client->v2` (oder `$client->v2()`) | Dreiundzwanzig Methoden über `/v2/*`: perceive, discover, lookup, distill, ingest, watch. |
| Format-Introspektion | `Enconvert\Formats` | Die 43 implementierten `{input}-to-{output}`-Paare, MIME-Auflösung und Ausgabelisten pro Eingabe. |
| Fehler | `Enconvert\Exception\*` | Basisklasse `EnconvertException` plus `ApiException`, `AuthenticationException`, `QuotaException`, `RateLimitException`. |
| Ergebnisse | `Enconvert\Model\*` | Readonly Value Objects mit camelCase-Eigenschaften. |

`$client->v2` ist eine öffentliche readonly-Eigenschaft. `$client->v2()` ist ein identischer Zugriff für alle, die Methodensyntax bevorzugen. Jeder V2-Endpunkt verlangt einen privaten API-Key; öffentliche Keys werden abgelehnt.

---

## Datei-Konvertierung

Jede Einzeldatei-Methode gibt ein `ConversionResult` mit einer vorsignierten Download-URL zurück. Übergibst du `saveTo`, streamt das SDK die Bytes zusätzlich an diesen lokalen Pfad und legt dabei übergeordnete Verzeichnisse an.

### convertUrlToPdf

Rendere jede erreichbare URL zu einem PDF.

```php
$result = $client->convertUrlToPdf('https://example.com', [
    'singlePage' => false,
    'pdfOptions' => ['pageSize' => 'A4', 'orientation' => 'landscape'],
    'viewportWidth' => 1440,
    'saveTo' => 'report.pdf',
]);
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `saveTo` | `string` | keiner | Lokaler Pfad, in den das PDF gestreamt wird. Übergeordnete Verzeichnisse werden angelegt. |
| `singlePage` | `bool` | `true` | `true` erzeugt eine einzige fortlaufende Seite. `false` paginiert anhand von `pdfOptions.pageSize`. |
| `pdfOptions` | `array` | keiner | Seitengröße, Ausrichtung, Ränder, Skalierung, Graustufen, Kopf- und Fußzeile. Siehe [PDF-Optionen](#pdf-optionen). |
| `viewportWidth`, `viewportHeight` | `int` | `1920`, `1080` | Größe des Browser-Viewports in Pixeln. |
| `loadMedia` | `bool` | `true` | Vor der Erfassung auf Bilder und Videos warten. |
| `enableScroll` | `bool` | `true` | Von oben nach unten scrollen, damit Lazy-Loader auslösen. |
| `outputFilename` | `string` | auto | Überschreibt den generierten Dateinamen. |
| `auth`, `cookies`, `headers` | `array` | keiner | Seitenzugriff: Basic Auth `['username' => ..., 'password' => ...]`, eingeschleuste Cookies, eigene Request-Header. |

<div class="alert alert-warning">
<strong>Kombiniere <code>auth</code> nicht mit einem <code>Authorization</code>-Header.</strong> Die API lehnt diesen Konflikt ab, statt zu raten, welche Zugangsdaten gemeint sind.
</div>

### convertUrlToScreenshot

Erfasse ein PNG einer beliebigen URL.

```php
$shot = $client->convertUrlToScreenshot('https://example.com', [
    'viewportWidth' => 1440,
    'saveTo' => 'shot.png',
]);
```

Akzeptiert dieselben Optionen für Viewport, Medien, Scrollen, Dateiname und Seitenzugriff wie `convertUrlToPdf`, ohne `singlePage` und `pdfOptions`.

### convertUrlToMarkdown

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

```php
$md = $client->convertUrlToMarkdown('https://example.com/article', [
    'saveTo' => 'article.md',
]);
```

Gleicher Optionssatz wie bei `convertUrlToScreenshot`.

### convertImage

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

```php
// Von einem Pfad.
$client->convertImage('photo.heic', ['outputFormat' => 'webp', 'saveTo' => 'photo.webp']);

// Aus rohen Bytes. Der Dateiname löst Eingabeformat und MIME-Typ auf.
$client->convertImage(
    ['data' => file_get_contents('photo.heic'), 'filename' => 'photo.heic'],
    ['outputFormat' => 'webp', 'saveTo' => 'photo.webp']
);

$client->convertImage('scan.pdf', ['outputFormat' => 'jpeg', 'saveTo' => 'scan.jpeg']); // rastern
```

Das Eingabeformat ergibt sich aus der Dateiendung: `.jpg`, `.jpeg`, `.png`, `.svg`, `.heic`, `.webp` und `.pdf`. Das Ausgabeformat ist erforderlich und wird für dich normalisiert, `jpg` wird also zu `jpeg` aufgelöst.

| Option | Typ | Erforderlich | Beschreibung |
|--------|------|----------|-------------|
| `outputFormat` | `string` | Ja | Eines von `jpeg`, `png`, `svg`, `heic`, `webp`. |
| `saveTo` | `string` | Nein | Lokaler Pfad, in den das Ergebnis gestreamt wird. |
| `outputFilename` | `string` | Nein | Überschreibt den generierten Dateinamen. |

### convertDocument

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

```php
// docx zu pdf, dann json zu yaml
$client->convertDocument('report.docx', ['saveTo' => 'report.pdf']);
$client->convertDocument('data.json', ['outputFormat' => 'yaml', 'saveTo' => 'data.yaml']);

// markdown zu pdf mit Seiteneinrichtung
$client->convertDocument('README.md', [
    'outputFormat' => 'pdf',
    'pdfOptions' => ['pageSize' => 'A4', 'margins' => ['top' => 20, 'bottom' => 20]],
    'saveTo' => 'readme.pdf',
]);
```

**Erkannte Eingabe-Erweiterungen:** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`.

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `outputFormat` | `string` | `"pdf"` | Zielformat. Die Aliasse `jpg`, `yml`, `htm` und `md` werden aufgelöst. |
| `saveTo` | `string` | keiner | Lokaler Pfad, in den das Ergebnis gestreamt wird. |
| `outputFilename` | `string` | keiner | Überschreibt den generierten Dateinamen. |
| `pdfOptions` | `array` | keiner | Seiteneinrichtung. Wird berücksichtigt, wenn die Ausgabe ein PDF ist. |

EPUB hat kein eigenes Dokumentpaar. Schicke `.epub`-Dateien durch `convertToPdf` oder `convertToMarkdown`.

#### Unterstützte Konvertierungspaare

| 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` | untereinander (alle 20 Paare) |
| `pdf` | `jpeg` |

Das SDK prüft das `{input}-to-{output}`-Paar gegen diese Tabelle und wirft eine `EnconvertException`, bevor eine Anfrage deinen Prozess verlässt, samt der Liste der gültigen Ausgaben für diese Eingabe in der Meldung. Du kannst dieselbe Tabelle direkt abfragen:

```php
use Enconvert\Formats;

Formats::validOutputsFor('json');   // ["csv", "toml", "xml", "yaml"]
Formats::validOutputsFor('pdf');    // ["jpeg"]
Formats::normalizeOutputFormat('JPG');   // "jpeg"
Formats::mimeFor('deck.pptx');           // der PPTX-MIME-Typ
count(Formats::IMPLEMENTED_CONVERSIONS); // 43
```

### convertToMarkdown

Erkenne das Format eines Dokuments serverseitig automatisch und erhalte Markdown zurück. Das ist der Baustein für die RAG-Aufnahme von Dateien, die du bereits auf der Festplatte hast.

```php
$client->convertToMarkdown('handbook.docx', ['saveTo' => 'handbook.md']);
```

Akzeptiert PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD sowie ältere oder ODF-Office-Dateien. Bilder werden nicht unterstützt. Die Optionen sind `saveTo` und `outputFilename`; an diesem Endpunkt gibt es keine PDF-Optionen.

### convertToPdf

Erkenne fast jede Eingabe automatisch und erhalte ein PDF zurück.

```php
$client->convertToPdf('slides.pptx', ['saveTo' => 'slides.pdf']);

// Eine PDF-Eingabe wird durchgereicht, das dient also zugleich der Graustufen-Normalisierung.
$client->convertToPdf('scan.pdf', ['pdfOptions' => ['grayscale' => true], 'saveTo' => 'gray.pdf']);
```

Akzeptiert Office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, reinen Text, Rasterbilder, SVG, EPUB und ein bestehendes PDF als Durchreichung.

<div class="alert alert-warning">
<strong>Nur <code>pdfOptions.grayscale</code> wird hier berücksichtigt.</strong> Das Format wird serverseitig erkannt, die Seitengeometrie kommt also aus dem Quelldokument. Nimm <code>convertDocument</code> oder <code>convertUrlToPdf</code>, wenn du Seitengröße, Ränder, Ausrichtung, Kopf- oder Fußzeilen brauchst.
</div>

### convertWebsiteToPdf und convertWebsiteToScreenshot

Ermittle jede Seite einer Website, konvertiere jede einzelne im Hintergrund und sammle alles in einem einzigen ZIP. Beide arbeiten asynchron und geben ein `BatchSubmission` statt eines `ConversionResult` zurück. Sie benötigen einen privaten API-Key mit Crawl-Zugriff.

```php
$batch = $client->convertWebsiteToPdf('https://example.com', [
    'crawlMode' => 'sitemap',              // "auto" (Standard) | "sitemap" | "full"
    'excludePatterns' => ['/blog/tag/'],   // nur im Full-Crawl-Modus
    'notificationEmail' => 'ops@my.app',
]);

echo $batch->batchId, ' ', $batch->urlCount, ' ', $batch->discoveryMethod, PHP_EOL;

// Blockiert, bis der Batch "processing" verlässt, dann das ZIP speichern.
$status = $client->waitForBatch($batch->batchId, ['saveTo' => 'site.zip']);
echo $status->completed, ' of ', $status->total, ' pages converted', PHP_EOL;

// Oder frage ihn selbst mit getBatchStatus() ab.
$s = $client->getBatchStatus($batch->batchId);
echo $s->status === 'processing' ? 'still working' : $s->zipDownloadUrl, PHP_EOL;
```

`convertWebsiteToScreenshot` nimmt dieselben Optionen ohne `singlePage` und `pdfOptions` und erzeugt ein ZIP mit PNGs.

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `crawlMode` | `string` | `"auto"` | `auto`, `sitemap` oder `full`. |
| `includePatterns`, `excludePatterns` | `string[]` | keiner | URLs nach Muster behalten oder verwerfen. Ausschlüsse greifen im Full-Crawl-Modus. |
| `notificationEmail`, `callbackUrl` | `string` | keiner | E-Mail-Adresse und Webhook, die bei Abschluss benachrichtigt werden. |
| `viewportWidth`, `viewportHeight`, `loadMedia`, `enableScroll` | gemischt | Server-Standard | Render-Optionen pro Seite. Werden nur gesendet, wenn du sie setzt. |
| `auth`, `cookies`, `headers` | `array` | keiner | Seitenzugriff für Websites hinter einem Login. |
| `outputFilename` | `string` | keiner | Überschreibt den ZIP-Dateinamen. |

`waitForBatch` akzeptiert `intervalMs` (Standard `5000`), `timeoutMs` (Standard `1800000`, also 30 Minuten) und `saveTo`. Es wirft eine `ApiException` mit Status `504`, wenn die Frist verstreicht, während der Batch noch läuft.

### getJobStatus

Frage einen einzelnen asynchronen oder wiederhergestellten Konvertierungsjob ab.

```php
$status = $client->getJobStatus('job_abc123');

if ($status->status === 'success') {
    echo $status->presignedUrl, PHP_EOL;
} elseif ($status->status === 'failed') {
    echo $status->error, PHP_EOL;
}
```

Gibt einen `JobStatus` mit `status` (`processing`, `success` oder `failed`), `presignedUrl`, `objectKey` und `error` zurück. Du brauchst ihn selten direkt, weil das SDK für dich abfragt. Siehe [Timeout-Recovery](#timeout-recovery).

---

## Web-Intelligence (V2)

Der Namensraum `$client->v2` verwandelt lebende Webseiten in agentenfertige Daten: rendern, suchen, extrahieren, aufnehmen und überwachen. Jeder Lesevorgang trägt `renderQuality`, einen Wert von 0.0 bis 1.0, der ein echtes Rendering von einem gescheiterten unterscheidet. Eine Challenge-Seite, eine Cookie-Wall, ein Login-Bildschirm oder eine leere SPA-Hülle kommt mit niedrigem Wert und gefüllten `warnings` zurück, sodass ein schlechter Lesevorgang nie unbemerkt in den Kontext eines Agenten gelangt. Der Inhalt wird trotzdem zurückgegeben: Er ist markiert, nicht versteckt. Die Konzepte findest du in der [V2-Übersicht](/de/docs/v2-overview).

### Perceive

Rendere eine URL und erzeuge aus diesem einen Rendering alle Ausgaben, die du angefordert hast. Siehe [die Perceive-Referenz](/de/docs/v2-perceive).

```php
$op = $client->v2->perceive('https://example.com/pricing', [
    'outputs' => ['markdown', 'screenshot', 'structured'],
    'extract' => ['tables', 'metadata'],
    'onlyMainContent' => true,
    'waitFor' => '.price',
]);

echo $op->renderQuality, ' ', $op->statusCode, PHP_EOL;  // Score, HTTP-Status der Quelle
print_r($op->deductions);                       // z. B. ["login_wall" => 0.65]
echo $op->outputs['markdown']->url, PHP_EOL;    // signierte URL, 15 Minuten
print_r($op->structured);

// Signierte URLs laufen ab. Signiere sie später neu, ohne erneut zu rendern.
$again = $client->v2->getPerceiveOperation($op->operationId);
```

| Option | Typ | Beschreibung |
|--------|------|-------------|
| `outputs` | `string[]` | Beliebige aus `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `extract` | `string[]` | Strukturierte Felder, die gezogen werden, zum Beispiel `metadata`, `structured_data`, `headings`, `tables`, `main_content`, `all`. |
| `schema` | `array` | Schema für die LLM-gestützte strukturierte Extraktion. |
| `onlyMainContent` | `bool` | Entfernt das Seitengerüst aus der Markdown-Ausgabe. |
| `waitFor`, `waitTimeoutMs` | `string`, `int` | Nach der Navigation auf einen CSS-Selektor oder einen JS-Ausdruck warten. |
| `jsCode` | `string` | JavaScript, das nach der Navigation auf der Seite ausgeführt wird. |
| `viewport`, `mobile`, `blockResources` | `array`, `bool`, `string[]` | Render-Geometrie und Ressourcentypen, die vor dem Laden abgebrochen werden. |
| `cacheMode` | `string` | `enabled`, `bypass` oder `refresh`. |
| `headers`, `cookies`, `auth` | `array` | Seitenzugriff für authentifizierte Seiten. |
| `respectRobots` | `bool` | Lehnt URLs ab, die `robots.txt` verbietet. |
| `pdfOptions` | `array` | Seiteneinrichtung für die `pdf`-Ausgabe. |
| `directDownload` | `bool` | Gibt die rohen Artefakt-Bytes zurück. Nur von `perceive()` akzeptiert. |
| `proxyUrl`, `geolocation`, `actionChain` | gemischt | Vom SDK serialisiert, von der API reserviert. |

#### Batch-Perceive

```php
$batch = $client->v2->perceiveBatch(['https://a.com', 'https://b.com'], [
    'outputs' => ['markdown'],
    'outputMode' => 'zip',
]);

// Kleine Batches werden inline fertig. Größere kommen als queued zurück, frage sie also ab.
if ($batch->status !== 'completed') {
    $batch = $client->v2->getPerceiveBatch($batch->jobId);
}
foreach ($batch->items as $item) {
    echo $item->url, ' ', $item->renderQuality, PHP_EOL;
}
echo $batch->zip?->url, PHP_EOL;
```

Bis zu 1000 URLs teilen sich einen Options-Block. `outputMode` ist `manifest` (Standard) oder `zip`. `directDownload` wird hier nicht akzeptiert.

#### Ein einzelnes Artefakt streamen

`perceiveDirect()` überspringt den JSON-Envelope und reicht dir die Bytes. Es muss genau eine artefakterzeugende Ausgabe angefordert werden, und das SDK erzwingt das vor dem Senden, `structured` allein wirft also eine Exception.

```php
$direct = $client->v2->perceiveDirect('https://example.com', [
    'outputs' => ['pdf'],
]);

file_put_contents($direct->filename ?? 'page.pdf', $direct->content);
echo $direct->renderQuality, ' ', $direct->contentType, PHP_EOL;
echo $direct->operationId, ' cache hit: ', var_export($direct->cacheHit, true), PHP_EOL;

// Ein gespeichertes Artefakt später erneut herunterladen. Lass den Ausgabenamen weg,
// wenn die Operation genau ein Artefakt erzeugt hat.
$saved = $client->v2->downloadPerceiveArtifact($direct->operationId, 'pdf');
```

`PerceiveDirectResult` trägt `content`, `contentType`, `filename`, `operationId`, `objectKey`, `cacheHit`, `renderQuality`, `sourceStatusCode`, `contentHash` und `warningsCount`. Eine `ApiException` mit Status `410` bedeutet, dass das gespeicherte Artefakt nicht mehr verfügbar ist.

### Discover

Zähle die URLs einer Website ausschließlich über HTTP auf. Kein Browser-Rendering, daher ist es schnell. Siehe [die Discover-Referenz](/de/docs/v2-discover).

```php
$found = $client->v2->discover('https://example.com', [
    'mode' => 'hybrid',                 // "sitemap" | "crawl" | "hybrid"
    'maxUrls' => 200,
    'maxDepth' => 3,
    'excludePatterns' => ['/tag/'],
    'sameDomainOnly' => true,
]);

echo $found->total, ' urls, truncated: ', var_export($found->truncated, true), PHP_EOL;
print_r($found->sources);   // z. B. ["sitemap" => 42, "crawl" => 30]
```

Die Optionen sind `mode`, `maxUrls`, `maxDepth`, `includePatterns`, `excludePatterns`, `sameDomainOnly` und `respectRobots`.

### Lookup

Führe eine kategorisierte Websuche aus und rendere die besten Treffer auf Wunsch automatisch. Siehe [die Lookup-Referenz](/de/docs/v2-lookup).

```php
$search = $client->v2->lookup('best static site generators', [
    'category' => 'web',        // web | news | images | scholar | patents | maps
    'numResults' => 10,
    'country' => 'us',
    'timeFilter' => 'month',
    'perceiveTop' => 3,         // die 3 besten Treffer automatisch rendern
]);

foreach ($search->results as $hit) {
    echo $hit->position, '. ', $hit->title, ' ', $hit->url, PHP_EOL;
    echo '   quality ', $hit->perceive?->renderQuality ?? 'not perceived', PHP_EOL;
}
```

Die Optionen sind `category`, `country`, `locale`, `timeFilter`, `numResults`, `page`, `location`, `autocorrect` und `perceiveTop`.

### Distill

Schemagesteuerte strukturierte Extraktion über eine URL-Liste oder eine ermittelte Website. Siehe [die Distill-Referenz](/de/docs/v2-distill).

```php
$extraction = $client->v2->distill([
    'urls' => ['https://example.com/pricing'],
    'schema' => ['plans' => 'list of plan names with monthly prices'],
    'cssSchema' => [                     // optionaler kostenloser CSS-Durchlauf vor der LLM-Stufe
        'baseSelector' => '.plan-card',
        'fields' => [
            ['name' => 'name', 'type' => 'text', 'selector' => 'h3'],
            ['name' => 'price', 'type' => 'text', 'selector' => '.price'],
        ],
    ],
]);

print_r($extraction->results[0]->data);
echo $extraction->results[0]->extractionTier, PHP_EOL;  // css | llm | mixed | none
echo $extraction->results[0]->fieldsFromCss, '/', $extraction->results[0]->fieldsFromLlm, PHP_EOL;

// Oder ermittle zuerst die URL-Menge.
$client->v2->distill([
    'discoverFrom' => ['url' => 'https://example.com', 'mode' => 'sitemap', 'maxPages' => 10],
    'schema' => ['title' => 'page title', 'summary' => 'one-line summary'],
]);
```

`schema` ist erforderlich, und genau eines von `urls` oder `discoverFrom` muss vorhanden sein. Verletzt du eine der beiden Regeln, wirft das SDK vor dem Senden eine `EnconvertException`. Weitere Optionen sind `waitFor`, `waitTimeoutMs`, `headers`, `cookies` und `respectRobots`.

### Ingest

Verwandle eine ganze Website oder einen Stapel hochgeladener Dokumente über eine einzige Pipeline in gechunktes, RAG-fertiges JSONL. Ingest arbeitet immer asynchron. Siehe [die Ingest-Referenz](/de/docs/v2-ingest).

```php
// Von einer Website.
$job = $client->v2->ingest([
    'mode' => 'sitemap',                 // "urls" (Standard) | "sitemap" | "crawl"
    'url' => 'https://docs.example.com',
    'maxPages' => 100,
    'chunk' => ['maxWords' => 512, 'sentenceOverlap' => 1],
    'webhookUrl' => 'https://my.app/hooks/enconvert',
]);

// Oder aus hochgeladenen Dateien: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, ältere und ODF-Office.
$fileJob = $client->v2->ingestFiles(['handbook.pdf', 'notes.docx'], [
    'chunk' => ['maxWords' => 512, 'sentenceOverlap' => 1],
]);

// Abfragen, bis es fertig ist.
do {
    sleep(5);
    $status = $client->v2->getIngestJob($job->jobId);
} while (in_array($status->status, ['queued', 'discovering', 'processing'], true));

if ($status->status === 'completed') {
    echo $status->totalChunks, ' chunks: ', $status->outputUrl, PHP_EOL;  // signiertes JSONL
}
```

`mode` ist standardmäßig `urls`, was ein nicht leeres `urls`-Array verlangt und `url` ablehnt. Die Modi `sitemap` und `crawl` verlangen eine Start-`url` und lehnen `urls` ab. Das SDK erzwingt beide Regeln lokal. Weitere Optionen sind `maxPages`, `maxDepth`, `sameDomainOnly`, `includePatterns`, `excludePatterns`, `respectRobots`, `waitFor`, `waitTimeoutMs`, `chunk` und `webhookUrl`.

```php
// Job-Verwaltung.
$list = $client->v2->listIngestJobs(['limit' => 20, 'skip' => 0]);
foreach ($list->jobs as $summary) {
    echo $summary->jobId, ' ', $summary->status, ' ', $summary->totalChunks, PHP_EOL;
}
$client->v2->cancelIngestJob($job->jobId);        // idempotent

// Webhook-Signierung.
$secret = $client->v2->getWebhookSecret();
echo $secret->signatureHeader, ' ', $secret->signatureScheme, PHP_EOL;
$client->v2->rotateWebhookSecret();               // alte Signaturen verifizieren nicht mehr
$client->v2->retryIngestWebhook($job->jobId);     // einen Abschluss-Callback erneut zustellen
```

### Watch

Rendere eine URL in festem Takt erneut und lass dich benachrichtigen, wenn sie sich ändert. Siehe [die Watch-Referenz](/de/docs/v2-watch).

```php
$watcher = $client->v2->createWatcher('https://example.com/pricing', [
    'frequencyMinutes' => 60,            // Untergrenze eine Stunde
    'diffMode' => 'auto',                // auto | text | structured | tables | metadata
    'webhookUrl' => 'https://my.app/hooks/changes',
    'notifyEmail' => true,
]);

echo $watcher->watcherId, ' next check ', $watcher->nextCheckAt, PHP_EOL;

$snapshots = $client->v2->getWatcherSnapshots($watcher->watcherId, ['limit' => 10]);
foreach ($snapshots->snapshots as $snap) {
    if ($snap->hasChanges) {
        echo $snap->checkedAt, ' similarity ', $snap->similarity,
             ' changes ', $snap->changeCount, PHP_EOL;
    }
}
$client->v2->updateWatcher($watcher->watcherId, ['status' => 'paused']);
$client->v2->updateWatcher($watcher->watcherId, ['webhookUrl' => '']);  // löscht den Webhook
$client->v2->deleteWatcher($watcher->watcherId);                        // Soft Delete, idempotent
```

`createWatcher` akzeptiert `frequencyMinutes`, `diffMode`, `trackFields`, `webhookUrl` und `notifyEmail`. `updateWatcher` akzeptiert diese fünf plus `status` und verlangt mindestens ein Feld, sonst wirft es eine `EnconvertException`. `listWatchers` akzeptiert `skip` und `limit`.

<div class="alert alert-warning">
<strong>Snapshot-Diffs enthalten nicht vertrauenswürdige Seiteninhalte.</strong> Die `changes`-Einträge eines `WatcherSnapshot` stammen aus der überwachten Seite. Escape sie mit `htmlspecialchars()`, bevor du sie in deiner eigenen Oberfläche darstellst.
</div>

---

## PDF-Optionen

Werden als `pdfOptions`-Array an `convertUrlToPdf`, `convertDocument`, `convertToPdf` (nur Graustufen), `convertWebsiteToPdf` und V2-`perceive` mit einer `pdf`-Ausgabe übergeben. Das SDK serialisiert camelCase-Schlüssel in das Wire-Format der API und sendet nur die Schlüssel, die du setzt.

```php
$client->convertUrlToPdf('https://example.com', [
    'pdfOptions' => [
        'pageSize' => 'A4',
        'orientation' => 'landscape',
        'margins' => ['top' => 10, 'bottom' => 10, 'left' => 15, 'right' => 15],
        'scale' => 0.9,
        'header' => ['content' => 'Quarterly Report', 'height' => 15],
        'footer' => ['content' => 'Page {{page}} of {{total_pages}}', 'height' => 12],
    ],
    'singlePage' => false,
    'saveTo' => 'report.pdf',
]);
```

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `pageSize` | `string` | `A0` bis `A6`, `B0` bis `B5`, `Letter`, `Legal`, `Tabloid`, `Ledger`. Wird ignoriert, wenn sowohl `pageWidth` als auch `pageHeight` gesetzt sind. |
| `pageWidth`, `pageHeight` | `float` | Eigene Seitengröße in Millimetern. Setze beide gemeinsam. |
| `orientation` | `string` | `portrait` oder `landscape`. |
| `margins` | `array` | `['top' => ..., 'bottom' => ..., 'left' => ..., 'right' => ...]` in Millimetern. |
| `scale` | `float` | Render-Skalierung, `0.1` bis `2.0`. Nur bei paginierter Ausgabe. |
| `grayscale` | `bool` | Wandelt das PDF nachträglich in Graustufen um. |
| `header` | `array` | `['content' => '<html>', 'height' => 15]`. Höhe in Millimetern. |
| `footer` | `array` | Gleiche Struktur wie `header`. |

Inhalte von Kopf- und Fußzeile unterstützen die Vorlagenvariablen `{{page}}`, `{{total_pages}}`, `{{date}}`, `{{title}}` und `{{url}}`. Die vollständige Parameter-Referenz steht unter [Parameter und Optionen](/de/docs/parameters-options).

---

## Fehlerbehandlung

Alles, was das SDK wirft, stammt von `Enconvert\Exception\EnconvertException` ab, ein einziger catch-Block kann also die gesamte Oberfläche abdecken.

```php
use Enconvert\Exception\{
    ApiException, AuthenticationException, EnconvertException, QuotaException, RateLimitException
};

try {
    $client->convertUrlToPdf('https://example.com', ['saveTo' => 'page.pdf']);
} catch (AuthenticationException $e) {
    error_log('Check ENCONVERT_API_KEY: ' . $e->getMessage());
} catch (RateLimitException $e) {
    error_log('Too many requests, back off and retry.');
} catch (QuotaException $e) {
    error_log($e->getMessage());
} catch (ApiException $e) {
    error_log(sprintf('API error [%d]: %s', $e->getStatusCode(), $e->getMessage()));
} catch (EnconvertException $e) {
    error_log('Client-side or transport failure: ' . $e->getMessage());
}
```

| Klasse | Ausgelöst bei | Statuscode |
|-------|-----------|-------------|
| `AuthenticationException` | Ungültiger, fehlender oder widerrufener API-Key | Antworten mit `401` und `403` |
| `QuotaException` | HTTP 402 | `402` |
| `RateLimitException` | Zu viele Anfragen | `429` |
| `ApiException` | Jede andere 4xx- oder 5xx-Antwort | der tatsächliche Code |
| `EnconvertException` | Basisklasse und jeder clientseitige Fehler | keiner |

`ApiException::getStatusCode()` gibt den numerischen Status zurück; der Meldung ist `[code]` vorangestellt. Eine `403`-Antwort wird auf `AuthenticationException` abgebildet, deren `getStatusCode()` `401` meldet: Verzweige also über die Klasse statt über die Zahl, wenn du beide unterscheiden musst.

`EnconvertException` wird auch ohne Netzwerk-Roundtrip geworfen, sobald das SDK erkennt, dass die Anfrage aussichtslos ist: ein leerer API-Key, ein fehlender Dateipfad, eine unbekannte Dateiendung, ein nicht implementiertes Konvertierungspaar, ein `distill`-Aufruf ohne Schema oder mit `urls` und `discoverFrom` zugleich, ein Widerspruch zwischen `ingest`-Modus und Payload, eine leere `ingestFiles`-Liste, ein `perceiveDirect`-Aufruf, der nicht genau eine Artefakt-Ausgabe benennt, ein `updateWatcher`-Aufruf ohne Felder sowie jeder Guzzle-Transportfehler (gemeldet als `HTTP request failed: ...`).

Die vollständige Zuordnung der Meldungen steht in der [Fehlercodes-Referenz](/de/docs/error-codes).

---

## Timeout-Recovery

Große Dokumentkonvertierungen und langsame Seiten können ein Reverse-Proxy-Timeout überdauern, selbst wenn die Konvertierung auf dem Server letztlich erfolgreich ist. Das SDK fängt das von allein auf:

1. Vor jeder Konvertierungsanfrage erzeugt es eine 32 Zeichen lange Hex-Job-ID und sendet sie als `job_id` im Body oder im Multipart-Formular.
2. Kommt diese Anfrage mit 5xx zurück, wirft das SDK keine Exception mehr, sondern beginnt, `GET /v1/convert/status/{job_id}` alle 3 Sekunden abzufragen.
3. Sobald der Job `success` meldet, gibt das SDK das Ergebnis zurück. Meldet er `failed`, wirft es eine `ApiException` mit der Fehlermeldung des Servers.
4. Ein `404` beim Abfragen bedeutet "noch nicht erfasst", und das Polling läuft weiter. Die Frist beträgt 5 Minuten, danach wirft es `ApiException(504, 'Conversion timed out')`.

Dafür ist kein Code nötig. Zwei bewusste Ausnahmen: Die Website-Batch-Übermittlungen (`convertWebsiteToPdf`, `convertWebsiteToScreenshot`) haben keine Job-Zeile, ein 5xx wird dort also sofort gemeldet, und V2-Methoden nutzen überhaupt kein Job-Polling.

Erfolgreiche Antworten, die `job_id` weglassen, werden mit der vom Client erzeugten ID ergänzt, `$result->jobId` ist also immer mit `getJobStatus()` verwendbar.

---

## Konfiguration

```php
use Enconvert\Client;

$client = new Client(getenv('ENCONVERT_API_KEY'), [
    'timeout' => 300,                            // Sekunden
    'base_url' => 'https://api.enconvert.com',   // Überschreiben für ein selbst gehostetes Gateway
]);
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `$apiKey` (erstes Argument) | `string` | erforderlich | Privater API-Key (`sk_live_...`). Ein leerer String wirft eine `EnconvertException`. |
| `timeout` | `int\|float` | `300` | Anfrage-Timeout in **Sekunden**, der bei Guzzle übliche Wert. Gilt für jede HTTP-Anfrage des Clients. Die Frist für das Job-Polling ist davon getrennt und fest auf 300 Sekunden gesetzt. |
| `base_url` | `string` | `https://api.enconvert.com` | API-Host. Abschließende Schrägstriche werden entfernt. |

Beachte, dass der Options-Schlüssel `base_url` in snake_case steht, während jede andere Request-Option im SDK in camelCase geschrieben wird. Anfragen werden mit einem `X-API-Key`-Header authentifiziert; `saveTo`-Downloads gehen direkt an den Speicher und senden bewusst keinen Key, weil die URL bereits signiert ist.

<div class="alert alert-warning">
<strong>Schreibe den API-Key niemals fest in den Code.</strong> Lies ihn aus einer Umgebungsvariablen oder deinem Secret-Manager und halte ihn aus der Versionsverwaltung und aus allem heraus, was an einen Browser ausgeliefert wird. Keys erzeugst und rotierst du im <a href="/de/dashboard">Dashboard</a>, und die Key-Typen erklärt der <a href="/de/docs/authentication">Authentifizierungs-Leitfaden</a>.
</div>

---

## Ergebnisform

Jede Einzeldatei-Konvertierung gibt ein `Enconvert\Model\ConversionResult` mit readonly öffentlichen Eigenschaften zurück:

```php
final class ConversionResult
{
    public readonly string $presignedUrl;              // signierte Download-URL
    public readonly string $objectKey;                 // Objekt-Key im Speicher
    public readonly string $filename;                  // serverseitiger Dateiname
    public readonly int|float|null $fileSize;          // Bytes
    public readonly int|float|null $conversionTimeSeconds;
    public readonly ?string $jobId;                    // die ID für die Timeout-Recovery
}
```

Vorsignierte URLs laufen nach 15 Minuten ab und können bis dahin mehrfach abgerufen werden. Für dauerhaften Zugriff lade die Bytes herunter (übergib `saveTo` oder rufe die URL selbst ab) und lege sie in deinem eigenen Bucket ab.

Die übrigen Ergebnistypen folgen demselben Muster: `JobStatus`, `BatchSubmission`, `BatchStatus` mit seinen `BatchItem[]` und, unter `Enconvert\Model\V2`, `PerceiveResult`, `PerceiveDirectResult`, `PerceiveBatchResult`, `OutputArtifact`, `DiscoverResult`, `LookupResult`, `LookupItem`, `DistillResult`, `DistillItem`, `IngestJob`, `IngestJobList`, `IngestJobSummary`, `Watcher`, `WatcherList`, `WatcherSummary`, `WatcherSnapshot`, `WatcherSnapshotList`, `WebhookSecret`, `WebhookRetryResult` und `Tokens`. Wire-Felder kommen in snake_case an und werden auf camelCase-Eigenschaften abgebildet; von dir gelieferte Payloads wie Schemata, extrahierte Daten, überwachte Felder und Diff-Einträge werden unverändert durchgereicht.

---

## Quelle und Issues

- **Packagist:** [enconvert/enconvert-php](https://packagist.org/packages/enconvert/enconvert-php)
- **GitHub:** [conversionapi/php-sdk](https://github.com/conversionapi/php-sdk) · [ein Issue eröffnen](https://github.com/conversionapi/php-sdk/issues)
- **Lizenz:** MIT
- **Weitere Clients:** [alle SDKs](/de/docs/sdks) · [REST-Endpunkte](/de/docs/endpoints-overview)

---

## Häufig gestellte Fragen

### Wie konvertiere ich Dateien in PHP mit Composer?

Führe `composer require enconvert/enconvert-php` aus, erzeuge `new Enconvert\Client($apiKey)` und rufe eine Methode wie `convertUrlToPdf`, `convertImage`, `convertDocument`, `convertToPdf` oder `convertToMarkdown` auf. Ergänze bei jeder davon `saveTo`, und das SDK streamt die konvertierten Bytes für dich an diesen lokalen Pfad und legt dabei übergeordnete Verzeichnisse an.

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

Rufe `$client->convertDocument('report.docx', ['saveTo' => 'report.pdf'])` auf. Das Ausgabeformat ist standardmäßig `pdf`, `outputFormat` ist also nicht nötig. Das Eingabeformat wird aus der Dateiendung gelesen, und das SDK prüft das Paar `doc-to-pdf` gegen seine Tabelle mit 43 implementierten Konvertierungen, bevor irgendetwas gesendet wird: Ein nicht unterstütztes Paar scheitert sofort mit einer Meldung, die auflistet, was für diese Eingabe gültig ist.

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

Rufe `$client->convertUrlToPdf('https://example.com', ['saveTo' => 'page.pdf'])` auf. Standardmäßig wird die Seite als eine fortlaufende Seite bei einem Viewport von 1920x1080 gerendert, mit aktiviertem Laden von Medien und Scrollen. Setze `singlePage` auf `false` und übergib `pdfOptions`, wenn du echte Paginierung mit Seitengröße, Rändern, Kopf- und Fußzeilen willst.

### Wie scrape ich in PHP eine Webseite und bekomme sauberes Markdown?

Nutze den V2-Namensraum: `$client->v2->perceive($url, ['outputs' => ['markdown', 'structured']])`. Er rendert die Seite in einem echten Browser, JavaScript-lastige Websites funktionieren also, und liefert eine signierte Markdown-URL plus inline strukturierte Daten. Prüfe `renderQuality` im Ergebnis, bevor du dem Inhalt vertraust; ein niedriger Wert mit gefüllten `deductions` bedeutet, dass das Rendering auf eine Anti-Bot-Seite, eine Login-Wall oder eine leere Hülle gestoßen ist. Für einen leichteren Einmalweg ohne V2-Funktionen funktioniert auch `convertUrlToMarkdown`.

### Wie konvertiere ich HEIC in PHP in WebP?

Rufe `$client->convertImage('photo.heic', ['outputFormat' => 'webp', 'saveTo' => 'photo.webp'])` auf. Jedes Paar aus `jpeg`, `png`, `svg`, `heic` und `webp` wird unterstützt, dazu die Rasterung von `pdf` zu `jpeg`. Du kannst auch rohe Bytes als `['data' => $bytes, 'filename' => 'photo.heic']` übergeben, wobei der Dateiname Eingabeformat und MIME-Typ auflöst.

### Funktioniert das EnConvert PHP SDK mit Laravel oder Symfony?

Ja. Das Paket ist eine schlichte PSR-4-Bibliothek mit Guzzle 7 als einziger Abhängigkeit und ohne Framework-Bindung, es passt also unverändert in Laravel, Symfony, WordPress oder ein einfaches Skript. Binde `Enconvert\Client` mit dem Key aus deiner Umgebungskonfiguration in deinen Container und injiziere ihn, wo du ihn brauchst.

### Was passiert, wenn eine Konvertierung länger dauert als das HTTP-Timeout?

Das SDK fängt das von allein auf. Es sendet mit jeder Konvertierungsanfrage eine vom Client erzeugte `job_id`, und wenn die Anfrage 5xx zurückgibt, fragt es still `GET /v1/convert/status/{job_id}` alle 3 Sekunden für bis zu 5 Minuten ab und gibt das Ergebnis zurück, sobald der Job `success` erfasst. Nach Ablauf der Frist wirft es `ApiException(504, 'Conversion timed out')`. Website-Batch-Übermittlungen nehmen daran nicht teil, da sie keine Job-Zeile haben.

### Wie lange ist die vorsignierte Download-URL gültig?

Vorsignierte Download-URLs laufen nach 15 Minuten ab und können bis dahin mehrfach verwendet werden. V2-Artefakt-URLs tragen aus demselben Grund ein `expiresIn` von 900 Sekunden, und `getPerceiveOperation($operationId)` signiert sie aus den gespeicherten Objekt-Keys neu, ohne die Seite erneut zu rendern. Für alles Dauerhafte lade die Bytes herunter und behalte sie in deinem eigenen Speicher.

### Welche PHP-Version verlangt das SDK?

PHP 8.1 oder neuer. Das SDK nutzt durchgehend readonly-Eigenschaften, enum-artige Union-Typen, benannte Argumente und `match`-Ausdrücke, 8.0 und älter werden also nicht unterstützt. Seine einzige Laufzeitabhängigkeit ist `guzzlehttp/guzzle ^7.8`.
