---
seo_title: URL zu PDF API | Webseiten in PDF umwandeln | EnConvert
meta_desc: Wandeln Sie jede URL mit POST /v1/convert/url-to-pdf in ein PDF um. Unterstützt Lazy Loading, Cookie-Banner und Auth; sync/async liefert signierte URLs oder Bytes.
keywords: url in pdf umwandeln api, webseite als pdf speichern api, website zu pdf api, html url zu pdf rest api, puppeteer alternative api, komplette webseite als pdf api, ganze seite als pdf api, batch url zu pdf konvertierung api
---

# URL zu PDF API

Der Endpunkt `POST /v1/convert/url-to-pdf` konvertiert jede öffentlich zugängliche URL in ein hochwertiges PDF-Dokument. Er unterstützt durchgehendes einseitiges Rendering oder paginierte Ausgabe mit benutzerdefinierten Seitengrößen, außerdem Lazy-Load-Handling, das Schließen von Cookie-Bannern, HTTP Basic Auth, Cookie-Injektion und benutzerdefinierte Header. Führen Sie Konvertierungen synchron aus, um eine signierte Download-URL oder rohe PDF-Bytes zu erhalten, oder verwenden Sie den asynchronen Modus, um mehrere URLs im Batch mit Webhook- und E-Mail-Benachrichtigungen zu konvertieren.

---

## Endpunkt

```
POST /v1/convert/url-to-pdf
```

**Content-Type:** `application/json`

---

## Authentifizierung

Dieser Endpunkt unterstützt sowohl die Authentifizierung mit privatem als auch mit öffentlichem Schlüssel.

### Privater Schlüssel

Fügen Sie Ihren geheimen Schlüssel im `X-API-Key`-Header ein. Verwenden Sie dies für Server-zu-Server-Aufrufe, bei denen der Schlüssel dem Client nie offengelegt wird.

```
X-API-Key: sk_your_private_key
```

### Öffentlicher Schlüssel mit JWT

Generieren Sie für die clientseitige Nutzung zunächst mit Ihrem öffentlichen Schlüssel ein JWT-Token und übergeben Sie es dann als Bearer-Token.

**Schritt 1 -- Token abrufen:**

```
POST /v1/auth/token
X-API-Key: pk_your_public_key
```

**Schritt 2 -- Token verwenden:**

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

<div class="alert alert-info">
<strong>Hinweis:</strong> Anfragen mit öffentlichem Schlüssel sind auf eine einzelne URL, den synchronen Modus und den direkten Download beschränkt. Asynchroner Modus, Batch-Verarbeitung, Webhooks und Benachrichtigungs-E-Mails stehen mit öffentlichen Schlüsseln nicht zur Verfügung.
</div>

---

## Anfrageparameter

### Top-Level-Parameter

| Parameter | Typ | Erforderlich | Standard | Beschreibung | Plan-Einschränkung |
|-----------|------|----------|---------|-------------|-------------|
| `url` | `string` oder `string[]` | Ja | -- | Ein einzelner URL-String oder ein Array von URLs zum Konvertieren. Mehrere URLs erfordern den asynchronen Modus. | -- |
| `async_mode` | `boolean` | Nein | `false` | Führt die Konvertierung asynchron aus. Gibt sofort eine `batch_id` zum Polling zurück. Erforderlich für Batches (mehrere URLs). | Erfordert Zugriff auf den asynchronen Modus |
| `direct_download` | `boolean` | Nein | `false` | Gibt rohe PDF-Bytes im Response-Body zurück statt einer JSON-Antwort mit einer signierten URL. Bei öffentlichen Schlüsseln fest auf `true` gesetzt. Nicht kompatibel mit `async_mode` und mehreren URLs. | -- |
| `output_format` | `boolean` | Nein | `false` | Bei `true` mit mehreren URLs werden alle Ausgabe-PDFs in einem einzigen ZIP-Archiv gebündelt. Erfordert mehrere URLs. | Erfordert Zugriff auf ZIP-Ausgabe |
| `output_filename` | `string` | Nein | Automatisch generiert | Benutzerdefinierter Dateiname für die Ausgabedatei. Die Endung `.pdf` wird automatisch hinzugefügt. Standardformat: `{domain}_{timestamp}.pdf`. | -- |
| `job_id` | `string` | Nein | -- | Vom Client bereitgestellte Job-ID zur Timeout-Wiederherstellung. **Nur für öffentliche Schlüssel.** Wenn eine synchrone Konvertierung die Timeout-Grenzen des Reverse-Proxys überschreitet (60-120s bei umfangreichen Seiten), kann der Client mit `GET /v1/convert/status/{job_id}` das Ergebnis nachträglich abrufen. Wird bei privaten Schlüsseln ignoriert. | -- |
| `notification_email` | `string` | Nein | E-Mail des Projektinhabers | E-Mail-Adresse, die benachrichtigt wird, wenn ein asynchroner Job abgeschlossen ist. Nur für private Schlüssel. | -- |
| `callback_url` | `string` | Nein | -- | Webhook-URL, die bei Abschluss der Konvertierung eine POST-Anfrage erhält. Nur für private Schlüssel. | Erfordert Zugriff auf Webhooks |

### Browser- und Rendering-Parameter

| Parameter | Typ | Erforderlich | Standard | Beschreibung | Plan-Einschränkung |
|-----------|------|----------|---------|-------------|-------------|
| `viewport_width` | `integer` | Nein | `1920` | Breite des Browser-Viewports in Pixeln. | -- |
| `viewport_height` | `integer` | Nein | `1080` | Höhe des Browser-Viewports in Pixeln. | -- |
| `single_page` | `boolean` | Nein | `true` | Bei `true` wird die gesamte Seite als eine durchgehende PDF-Seite gerendert. Bei `false` entsteht eine paginierte Ausgabe anhand der Seitengröße aus `pdf_options`. | -- |
| `load_media` | `boolean` | Nein | `true` | Wartet vor der Konvertierung, bis alle Bilder und Videos vollständig geladen sind. Bei `false` ist die Konvertierung schneller, Medien können jedoch als Platzhalter erscheinen. | -- |
| `enable_scroll` | `boolean` | Nein | `true` | Scrollt die Seite von oben nach unten, um Lazy-Loading-Inhalte auszulösen (auf IntersectionObserver basierende Loader). | -- |
| `handle_sticky_header` | `boolean` | Nein | `true` | Erkennt sticky/fixe Header und scrollt vor der Erfassung nach oben, damit der Header am Anfang des PDFs korrekt dargestellt wird. | -- |
| `handle_cookies` | `boolean` | Nein | `true` | Schließt Cookie-Consent-Banner automatisch (OneTrust, Cookiebot, Didomi, Usercentrics und generische Banner). | -- |
| `wait_for_images` | `boolean` | Nein | `true` | Wartet, bis alle `<img>`-Elemente fertig geladen sind (Timeout von 5 Sekunden pro Bild). | -- |
| `wait_for_selector` | `string` | Nein | `null` | CSS-Selektor, auf den vor der Aufnahme gewartet wird. Gibt `422` zurück, wenn er nicht innerhalb von `wait_for_selector_timeout` erscheint. Nützlich für SPAs, die Inhalte nach dem Laden hydrieren. | -- |
| `wait_for_selector_timeout` | `integer` | Nein | `10000` | Millisekunden, die auf `wait_for_selector` gewartet wird (maximal `60000`). | -- |
| `block_ads` | `boolean` | Nein | `false` | Bricht Anfragen an bekannte Werbe-/Tracker-Domains ab, sodass diese nie geladen, gerendert werden oder die Aufnahme verlangsamen. | -- |
| `block_media` | `boolean` | Nein | `false` | Bricht Bild- und Audio-/Videoanfragen vollständig ab, für ein schnelleres, leichteres Rendering. Anders als `load_media` (das nur das Warten steuert), verhindert dies das Herunterladen von Medien vollständig. | -- |

### Authentifizierung und benutzerdefinierte Anfragen

| Parameter | Typ | Erforderlich | Standard | Beschreibung | Plan-Einschränkung |
|-----------|------|----------|---------|-------------|-------------|
| `auth` | `object` | Nein | `null` | HTTP-Basic-Auth-Anmeldedaten für die Ziel-URL. Format: `{"username": "...", "password": "..."}`. Kann nicht zusammen mit einem benutzerdefinierten `Authorization`-Header verwendet werden. | Erfordert Zugriff auf Basic Auth |
| `cookies` | `array` | Nein | `null` | Array von Cookie-Objekten, die vor der Navigation injiziert werden. Maximal 50 Cookies. Jedes Cookie muss `name`, `value` sowie entweder `domain` oder `url` enthalten. | Erfordert Zugriff auf Basic Auth |
| `headers` | `object` | Nein | `null` | Dictionary mit benutzerdefinierten HTTP-Headern, die bei jeder Anfrage an die Ziel-URL mitgesendet werden. Maximal 20 Header. Blockierte Header: `host`, `content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`. | Erfordert Zugriff auf Basic Auth |

### PDF-Optionen

Übergeben Sie diese innerhalb eines `pdf_options`-Objekts im Request-Body.

| Parameter | Typ | Standard | Beschreibung |
|-----------|------|---------|-------------|
| `page_size` | `string` | `"A4"` | Benannte Seitengröße. Wird ignoriert, wenn sowohl `page_width` als auch `page_height` gesetzt sind. |
| `page_width` | `float` | `null` | Benutzerdefinierte Seitenbreite in Millimetern. Muss positiv sein. `page_width` und `page_height` müssen gemeinsam gesetzt werden. |
| `page_height` | `float` | `null` | Benutzerdefinierte Seitenhöhe in Millimetern. Muss positiv sein. Beide müssen gemeinsam gesetzt werden. |
| `orientation` | `string` | `"portrait"` | `"portrait"` oder `"landscape"`. Vertauscht Breite und Höhe bei Landscape. |
| `margins` | `object` | `{"top": 10, "bottom": 10, "left": 10, "right": 10}` | Seitenränder in Millimetern. Alle Werte müssen nicht-negativ sein. |
| `scale` | `float` | `1.0` | Skalierungsfaktor für den Inhalt. Bereich: `0.1` bis `2.0`. Wird nur im paginierten Modus angewendet (`single_page=false`). |
| `grayscale` | `boolean` | `false` | Wandelt die PDF-Ausgabe per Nachbearbeitung in Graustufen um. |
| `header` | `object` | `null` | Seitenkopf für den paginierten Modus. Format: `{"content": "<html>", "height": 15}`. Inhalt max. 2000 Zeichen. Höhe in mm. |
| `footer` | `object` | `null` | Seitenfuß für den paginierten Modus. Gleiches Format wie header. |

**Unterstützte Seitengrößen:** `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`, `B0`, `B1`, `B2`, `B3`, `B4`, `B5`, `Letter`, `Legal`, `Tabloid`, `Ledger`

**Vorlagenvariablen für Header/Footer:** `{{page}}`, `{{total_pages}}`, `{{date}}`, `{{title}}`, `{{url}}`

<div class="alert alert-info">
<strong>Hinweis:</strong> Header und Footer werden nur im paginierten Modus gerendert (<code>single_page=false</code>). Im einseitigen durchgehenden Modus haben sie keine Wirkung.
</div>

---

## Cookie-Objektschema

Jedes Element im Array `cookies` muss dieser Struktur folgen:

| Feld | Typ | Erforderlich | Standard | Beschreibung |
|-------|------|----------|---------|-------------|
| `name` | `string` | Ja | -- | Name des Cookies. |
| `value` | `string` | Ja | -- | Wert des Cookies. |
| `domain` | `string` | Bedingt | -- | Domain des Cookies. Entweder `domain` oder `url` muss angegeben werden. |
| `url` | `string` | Bedingt | -- | URL, mit der das Cookie verknüpft wird. Entweder `domain` oder `url` muss angegeben werden. |
| `path` | `string` | Nein | `"/"` | Pfad des Cookies. Standardmäßig `"/"`, wenn `domain` gesetzt ist. |

---

## Antwort

### Synchron mit direktem Download (`direct_download=true`)

**Privater Schlüssel** -- gibt rohe PDF-Bytes zurück:

```
HTTP 200 OK
Content-Type: application/pdf
Content-Disposition: inline; filename="example_20260404_123456789.pdf"
X-Object-Key: env/files/{project_id}/url-to-pdf/example_20260404_123456789.pdf
X-File-Size: 123456
X-Conversion-Time: 12.5
X-Filename: example_20260404_123456789.pdf

(binary PDF data)
```

**Öffentlicher Schlüssel** -- gibt JSON mit einer signierten URL zurück:

```json
{
    "presigned_url": "https://spaces.example.com/...",
    "object_key": "env/files/{project_id}/url-to-pdf/example_20260404_123456789.pdf",
    "filename": "example_20260404_123456789.pdf",
    "file_size": 123456,
    "conversion_time_seconds": 12.5,
    "job_id": "client-provided-id"
}
```

### Synchron ohne direkten Download (`direct_download=false`)

Nur mit privaten Schlüsseln verfügbar.

```json
{
    "presigned_url": "https://spaces.example.com/...",
    "object_key": "env/files/{project_id}/url-to-pdf/example_20260404_123456789.pdf",
    "filename": "example_20260404_123456789.pdf",
    "file_size": 123456,
    "conversion_time_seconds": 12.5
}
```

### Asynchroner Modus

Gibt sofort eine `batch_id` zum Polling zurück.

```
HTTP 202 Accepted
```

```json
{
    "status": "processing",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "url_count": 5,
    "output_format": "individual"
}
```

Bei `output_format=true` (ZIP-Bündelung):

```json
{
    "status": "processing",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "url_count": 5,
    "output_format": "zip"
}
```

### Job-Status-Polling (nur öffentliche Schlüssel)

Der Status-Polling-Endpunkt ist für die **Timeout-Wiederherstellung bei öffentlichen Schlüsseln** konzipiert. Wenn eine synchrone Konvertierung länger dauert als der Timeout des Reverse-Proxys (in der Regel 60s), kann der Client das Ergebnis abrufen, indem er mit der im ursprünglichen Request angegebenen `job_id` pollt.

```
GET /v1/convert/status/{job_id}
Authorization: Bearer <jwt_token>
```

| Status | Antwort |
|--------|----------|
| Verarbeitung | `{"status": "processing"}` |
| Erfolg | `{"status": "success", "presigned_url": "...", "object_key": "..."}` |
| Fehlgeschlagen | `{"status": "failed", "error": "..."}` |

### Batch-Status-Polling (nur private Schlüssel) {: #batch-status-polling-private-keys-only }

Pollen Sie bei asynchronen Batch-Jobs den Batch-Status-Endpunkt mit der im 202-Response zurückgegebenen `batch_id`.

```
GET /v1/convert/batch/{batch_id}
X-API-Key: sk_your_private_key
```

**Antwort:**

```json
{
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "processing",
    "total": 5,
    "completed": 3,
    "failed": 1,
    "in_progress": 1,
    "output_mode": "individual",
    "zip_download_url": null,
    "items": [
        {
            "source_url": "https://example.com/page1",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 102400,
            "duration": "2.34"
        },
        {
            "source_url": "https://example.com/page2",
            "status": "Failed",
            "download_url": null,
            "output_file_size": null,
            "duration": "0.87"
        },
        {
            "source_url": "https://example.com/page3",
            "status": "In Progress",
            "download_url": null,
            "output_file_size": null,
            "duration": null
        }
    ]
}
```

**Batch-Statuswerte:**

| Status | Bedeutung |
|--------|---------|
| `processing` | Mindestens eine URL wird noch konvertiert |
| `completed` | Alle URLs wurden erfolgreich konvertiert |
| `partial` | Alle URLs sind fertig, aber einige sind fehlgeschlagen |
| `failed` | Alle URLs sind fehlgeschlagen |

Wenn `output_mode` gleich `"zip"` ist, wird eine einzelne `zip_download_url` bereitgestellt statt einzelner `download_url`-Werte pro Element.

### Webhook-Callback-Payload

Wenn eine `callback_url` angegeben wird, sendet EnConvert bei Abschluss eine POST-Anfrage an diese URL.

**Job mit einzelner URL:**

```json
{
    "job_id": "activity_id",
    "status": "success",
    "batch_id": "...",
    "gcs_uri": "object_key",
    "filename": "example_20260404_123456789.pdf",
    "file_size": 123456
}
```

**Batch-Job:**

```json
{
    "job_id": "activity_id",
    "status": "success",
    "batch_id": "...",
    "total_tasks": 10,
    "successful_tasks": 8,
    "failed_tasks": 2,
    "tasks": [
        {"url": "https://example.com/page1", "status": "success", "filename": "page1.pdf"},
        {"url": "https://example.com/page2", "status": "failed", "filename": null}
    ]
}
```

---

## Funktionen

### Sauberer Erfassungsmodus

EnConvert behandelt automatisch häufige Hindernisse auf Webseiten, um saubere PDFs zu erzeugen:

- **Cookie-Consent-Banner** -- Schließt Banner von OneTrust, Cookiebot, Didomi, Usercentrics und generischen Implementierungen automatisch. Funktioniert sowohl auf der Hauptseite als auch in iframes. Verwendet eine Strategie, die zuerst schließt und dann erst akzeptiert.
- **Schließen von Modals und Popups** -- Schließt Overlays mit mehreren Strategien: Escape-Taste, ARIA-Schließen-Buttons, klassenbasierte Schließen-Buttons und rollenbasierte Dialog-Buttons.
- **Aufdecken von Scroll-Animationen** -- Erzwingt die Sichtbarkeit von Elementen, die durch scroll-getriggerte Animationsbibliotheken wie WOW.js, AOS, ScrollReveal und GSAP ScrollTrigger verborgen werden.
- **Dropdown-Bereinigung** -- Schließt alle offenen Dropdowns und wandelt Navigations-Button-Elemente in echte Anker-Links um, damit sie im PDF klickbar bleiben.

### Seitengröße und Abmessungen

- **Einseitiger Modus** (Standard): Die gesamte Webseite wird als eine durchgehende PDF-Seite gerendert. Die Höhe wird dynamisch anhand des tatsächlichen Inhalts per DOM-Traversierung berechnet.
- **Paginierter Modus** (`single_page=false`): Die Ausgabe verwendet die konfigurierten Werte `page_size`, `orientation` und `margins`. Unterstützt 18 benannte Größen von A0 bis Ledger oder benutzerdefinierte Abmessungen in Millimetern.

### HTTP Basic Auth

Übergeben Sie `auth` mit `username` und `password`, um Seiten hinter HTTP Basic Authentication zu konvertieren. Die Anmeldedaten werden bei jeder Anfrage an die Zielseite als HTTP-Credentials gesendet.

```json
{
    "url": "https://staging.example.com/report",
    "auth": {
        "username": "admin",
        "password": "secret"
    }
}
```

### Cookie-Injektion

Injizieren Sie bis zu 50 Cookies, bevor die Seite geladen wird. Nützlich zum Konvertieren von Seiten, die eine aktive Sitzung oder bestimmte Benutzereinstellungen erfordern.

```json
{
    "url": "https://example.com/dashboard",
    "cookies": [
        {"name": "session_id", "value": "abc123", "domain": "example.com"},
        {"name": "locale", "value": "en-US", "domain": "example.com"}
    ]
}
```

### Benutzerdefinierte Header

Senden Sie bis zu 20 benutzerdefinierte HTTP-Header bei jeder Anfrage an die Zielseite. Nützlich, um API-Tokens, benutzerdefinierte User-Agents oder andere Anfrage-Metadaten zu übergeben.

```json
{
    "url": "https://example.com/report",
    "headers": {
        "X-Custom-Token": "my-token-value",
        "Accept-Language": "en-US"
    }
}
```

### Verzögertes Laden von Bildern (Lazy Loading)

Wenn `load_media` und `enable_scroll` aktiviert sind (beide standardmäßig `true`), geht der Converter wie folgt vor:

1. Scrollt die gesamte Seite langsam (120px alle 90ms), um auf IntersectionObserver basierende Lazy-Loader auszulösen
2. Wartet, bis alle `<img>`-Elemente ihr `onload`-Event auslösen (Timeout von 5 Sekunden pro Bild)
3. Wartet nach dem Laden aller Bilder 500ms, bis sich das Layout stabilisiert

Setzen Sie `load_media=false` für eine schnellere Konvertierung, wenn die originalgetreue Darstellung von Medien nicht entscheidend ist -- der Converter verwendet dann schnelles Scrollen (300px alle 30ms) und fügt Platzhalter-Styles für nicht geladene Bilder hinzu.

### Behandlung von Sticky Headers

Wenn `handle_sticky_header` aktiviert ist (Standard `true`), erkennt der Converter fixed und sticky positionierte Elemente, die wie Header aussehen (anhand semantischer Tags, ARIA-Rollen und gängiger Klassennamen-Muster), und scrollt vor der Erfassung an den Seitenanfang, damit der Header am Anfang des PDFs korrekt dargestellt wird.

### Header und Footer

Fügen Sie im paginierten Modus wiederkehrende Header und Footer mit HTML-Inhalt und Vorlagenvariablen hinzu:

```json
{
    "url": "https://example.com/report",
    "single_page": false,
    "pdf_options": {
        "page_size": "A4",
        "header": {
            "content": "<div style='font-size:10px;text-align:center;width:100%'>Confidential Report</div>",
            "height": 15
        },
        "footer": {
            "content": "<div style='font-size:9px;text-align:center;width:100%'>Page {{page}} of {{total_pages}}</div>",
            "height": 10
        }
    }
}
```

### Graustufen-Ausgabe

Setzen Sie `pdf_options.grayscale` auf `true`, um das fertige PDF per Ghostscript-Nachbearbeitung in Graustufen umzuwandeln.

### Weitere Rendering-Funktionen

- **Normalisierung von Viewport-Einheiten** -- Wandelt die CSS-Einheiten `vh`, `svh`, `lvh`, `dvh` in feste Pixelwerte um, um Layout-Probleme beim Druck-Rendering zu vermeiden.
- **Stealth-Modus** -- Verwendet Browser-Fingerprint-Maskierung, um Bot-Erkennung auf geschützten Seiten zu vermeiden.
- **Popup-Abfangen** -- Schließt automatisch alle neuen Browser-Tabs oder Popups, die von der Seite ausgelöst werden.
- **CSP-Umgehung** -- Behandelt Content-Security-Policy- und Trusted-Types-Beschränkungen, die die Konvertierung sonst blockieren würden.

---

## Abo-Plan-Einschränkungen

| Funktion | Founding | Indie | Studio | Enterprise |
|---------|------|---------|-----|------------|
| Basiskonvertierung (einzelne URL, synchron) | Ja | Ja | Ja | Ja |
| Benutzerdefinierte `pdf_options` | Ja | Ja | Ja | Ja |
| Viewport- und Rendering-Optionen | Ja | Ja | Ja | Ja |
| Asynchroner Modus | Nein | Ja | Ja | Ja |
| Batch-Verarbeitung (mehrere URLs) | Nein | Ja | Ja | Ja |
| ZIP-Ausgabebündelung | Nein | Nein | Ja | Ja |
| Webhook-Callbacks | Nein | Nein | Ja | Ja |
| HTTP Basic Auth | Nein | Ja | Ja | Ja |
| Cookie-Injektion | Nein | Ja | Ja | Ja |
| Benutzerdefinierte Header | Nein | Ja | Ja | Ja |
| Monatliche Konvertierungen | 100 | Planabhängig | Planabhängig | Unbegrenzt |
| Maximale Batch-Größe | 0 | Planabhängig | Planabhängig | Unbegrenzt |
| Dateiaufbewahrung | 1 Stunde | Planabhängig | Planabhängig | Planabhängig |

---

## Asynchroner Modus

Der asynchrone Modus eignet sich für lang laufende Konvertierungen oder beim Konvertieren mehrerer URLs.

### Funktionsweise

1. Senden Sie eine Anfrage mit `async_mode=true` (oder übergeben Sie mehrere URLs, wodurch der asynchrone Modus automatisch aktiviert wird).
2. Die API gibt sofort HTTP 202 mit einer `batch_id` und `url_count` zurück.
3. Jede URL wird im Hintergrund konvertiert, in den Storage hochgeladen und einzeln nachverfolgt.
4. Überwachen Sie den Abschluss per **E-Mail-Benachrichtigung** oder **Webhook-Callback**.

### E-Mail-Benachrichtigung

Standardmäßig wird bei Abschluss des asynchronen Jobs eine E-Mail an die E-Mail-Adresse des Projektinhabers gesendet. Sie können dies mit `notification_email` überschreiben:

```json
{
    "url": ["https://example.com/page1", "https://example.com/page2"],
    "async_mode": true,
    "notification_email": "team@example.com"
}
```

### Webhook-Callback

Geben Sie in der Anfrage eine `callback_url` an, um bei Abschluss des Jobs automatisch eine POST-Benachrichtigung zu erhalten:

```json
{
    "url": ["https://example.com/page1", "https://example.com/page2"],
    "async_mode": true,
    "callback_url": "https://your-server.com/webhook/enconvert"
}
```

Der Webhook wird mit einem Timeout von 30 Sekunden gesendet, wobei HTTP 200, 201, 202 und 204 als erfolgreiche Zustellung gelten.

---

## Batch- und Massenverarbeitung

Konvertieren Sie mehrere URLs in einer einzigen Anfrage. Erfordert den asynchronen Modus und einen privaten Schlüssel.

### Einzelne Ausgabe (Standard)

Jede URL erzeugt eine separate PDF-Datei:

```json
{
    "url": [
        "https://example.com/page1",
        "https://example.com/page2",
        "https://example.com/page3"
    ],
    "async_mode": true
}
```

### ZIP-Bündel-Ausgabe

Bündeln Sie alle PDFs in einem einzigen ZIP-Archiv:

```json
{
    "url": [
        "https://example.com/page1",
        "https://example.com/page2",
        "https://example.com/page3"
    ],
    "async_mode": true,
    "output_format": true,
    "output_filename": "monthly-reports"
}
```

Die ZIP-Datei wird `{output_filename}_{timestamp}.zip` genannt, oder `batch_{timestamp}.zip`, wenn kein benutzerdefinierter Name angegeben wird.

---

## Codebeispiele

### Python (privater Schlüssel)

```python
import requests

response = requests.post(
    "https://api.enconvert.com/v1/convert/url-to-pdf",
    headers={"X-API-Key": "sk_your_private_key"},
    json={
        "url": "https://example.com",
        "single_page": True,
        "pdf_options": {
            "page_size": "A4",
            "margins": {"top": 15, "bottom": 15, "left": 10, "right": 10}
        }
    }
)

data = response.json()
print(data["presigned_url"])
```

### PHP (privater Schlüssel)

```php
$ch = curl_init("https://api.enconvert.com/v1/convert/url-to-pdf");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Content-Type: application/json",
        "X-API-Key: sk_your_private_key"
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "url" => "https://example.com",
        "single_page" => true,
        "pdf_options" => [
            "page_size" => "A4",
            "margins" => ["top" => 15, "bottom" => 15, "left" => 10, "right" => 10]
        ]
    ])
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
echo $data["presigned_url"];
```

### Node.js (privater Schlüssel)

```javascript
const response = await fetch("https://api.enconvert.com/v1/convert/url-to-pdf", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_your_private_key"
    },
    body: JSON.stringify({
        url: "https://example.com",
        single_page: true,
        pdf_options: {
            page_size: "A4",
            margins: { top: 15, bottom: 15, left: 10, right: 10 }
        }
    })
});

const data = await response.json();
console.log(data.presigned_url);
```

### Go (privater Schlüssel)

```go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "io"
)

func main() {
    body, _ := json.Marshal(map[string]interface{}{
        "url":         "https://example.com",
        "single_page": true,
        "pdf_options": map[string]interface{}{
            "page_size": "A4",
            "margins":   map[string]int{"top": 15, "bottom": 15, "left": 10, "right": 10},
        },
    })

    req, _ := http.NewRequest("POST", "https://api.enconvert.com/v1/convert/url-to-pdf", bytes.NewBuffer(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-API-Key", "sk_your_private_key")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    respBody, _ := io.ReadAll(resp.Body)
    fmt.Println(string(respBody))
}
```

### JavaScript -- Browser (öffentlicher Schlüssel)

```javascript
// Step 1: Get a JWT token
const tokenRes = await fetch("https://api.enconvert.com/v1/auth/token", {
    method: "POST",
    headers: { "X-API-Key": "pk_your_public_key" }
});
const { token } = await tokenRes.json();

// Step 2: Convert URL to PDF
const convertRes = await fetch("https://api.enconvert.com/v1/convert/url-to-pdf", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${token}`
    },
    body: JSON.stringify({
        url: "https://example.com"
    })
});

const data = await convertRes.json();
// Open the PDF in a new tab
window.open(data.presigned_url, "_blank");
```

### React (öffentlicher Schlüssel)

```jsx
import { useState } from "react";

function UrlToPdf() {
    const [loading, setLoading] = useState(false);
    const [pdfUrl, setPdfUrl] = useState(null);

    async function convertUrl() {
        setLoading(true);
        try {
            // Get JWT token
            const tokenRes = await fetch("https://api.enconvert.com/v1/auth/token", {
                method: "POST",
                headers: { "X-API-Key": "pk_your_public_key" }
            });
            const { token } = await tokenRes.json();

            // Convert
            const convertRes = await fetch("https://api.enconvert.com/v1/convert/url-to-pdf", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${token}`
                },
                body: JSON.stringify({ url: "https://example.com" })
            });

            const data = await convertRes.json();
            setPdfUrl(data.presigned_url);
        } finally {
            setLoading(false);
        }
    }

    return (
        <div>
            <button onClick={convertUrl} disabled={loading}>
                {loading ? "Converting..." : "Convert to PDF"}
            </button>
            {pdfUrl && <a href={pdfUrl} target="_blank" rel="noreferrer">Download PDF</a>}
        </div>
    );
}

export default UrlToPdf;
```

---

## Fehlerantworten

| Status | Bedingung |
|--------|-----------|
| `400 Bad Request` | Fehlender oder leerer Parameter `url` |
| `400 Bad Request` | `output_format=true` bei einer einzelnen URL (erfordert mehrere URLs) |
| `400 Bad Request` | `direct_download=true` bei mehreren URLs |
| `400 Bad Request` | `direct_download=true` mit `async_mode=true` |
| `400 Bad Request` | Ungültiges `auth`-Objekt (fehlendes `username` oder `password`) |
| `400 Bad Request` | Ungültige `cookies` (kein Array, mehr als 50 Einträge, fehlende Pflichtfelder) |
| `400 Bad Request` | Ungültige `headers` (kein Objekt, mehr als 20 Einträge, blockierte Headernamen, Werte, die keine Strings sind) |
| `400 Bad Request` | Widerspruch zwischen `auth` und benutzerdefiniertem `Authorization`-Header |
| `400 Bad Request` | Öffentlicher Schlüssel versucht mehrere URLs zu verwenden |
| `400 Bad Request` | Ungültige `pdf_options` (unbekannte Seitengröße, `scale` außerhalb des Bereichs 0.1-2.0, negative Ränder, Header-/Footer-Inhalt über 2000 Zeichen) |
| `401 Unauthorized` | Fehlender oder ungültiger API-Schlüssel / JWT-Token |
| `402 Payment Required` | Monatliches Ops-Kontingent aufgebraucht |
| `402 Payment Required` | Batch würde das verbleibende monatliche Ops-Kontingent überschreiten |
| `402 Payment Required` | Speicherlimit erreicht |
| `403 Forbidden` | Endpunkt nicht in den erlaubten Endpunkten des API-Schlüssels |
| `403 Forbidden` | Funktion im aktuellen Plan nicht verfügbar (asynchron, Webhook, ZIP, Basic Auth) |
| `403 Forbidden` | Batch-Größe überschreitet das Batch-Limit des Plans |
| `404 Not Found` | Job-ID nicht gefunden (beim Status-Polling) |
| `500 Internal Server Error` | Konvertierung fehlgeschlagen (Browser-Absturz, Rendering-Fehler, Fehler bei der Nachbearbeitung) |

---

## Limits

| Limit | Wert |
|-------|-------|
| Timeout für Seitennavigation | 60 Sekunden |
| Ladetimeout pro Bild | 5 Sekunden |
| Timeout zum Schließen von Cookie-Bannern | 3 Sekunden |
| Maximale Cookies pro Anfrage | 50 |
| Maximale benutzerdefinierte Header pro Anfrage | 20 |
| Inhaltslänge von Header/Footer | 2000 Zeichen |
| Skalierungsbereich für PDF | 0.1 -- 2.0 |
| Monatliche Operationen | Planabhängig (Founding: 500) |
| Batch-Größe | Planabhängig (Founding: deaktiviert) |
| Dateiaufbewahrung | Planabhängig (Founding: 1 Stunde) |
| Zustell-Timeout für Webhooks | 30 Sekunden |

---

## Häufig gestellte Fragen

### Wie konvertiere ich eine Webseite mit einer REST-API in PDF?

Senden Sie eine `POST`-Anfrage an `/v1/convert/url-to-pdf` mit einem JSON-Body, der die zu konvertierende `url` enthält, und authentifizieren Sie sich mit Ihrem privaten Schlüssel im `X-API-Key`-Header (oder einem JWT-Bearer-Token eines öffentlichen Schlüssels). Die synchrone Antwort gibt eine `presigned_url` zum Herunterladen des PDFs zurück, oder rohe PDF-Bytes bei `direct_download=true`.

### Kann ich mehrere URLs in einer einzigen API-Anfrage in PDF konvertieren?

Ja. Übergeben Sie im Parameter `url` ein Array von URLs zusammen mit `async_mode=true` (ein privater Schlüssel ist erforderlich); die API gibt `HTTP 202` mit einer `batch_id` zurück, die Sie über `GET /v1/convert/batch/{batch_id}` abfragen. Setzen Sie `output_format=true`, um alle PDFs in einem einzigen ZIP-Archiv zu bündeln.

### Wie erfasse ich eine gesamte Webseite als einzelne durchgehende PDF-Seite?

Der einseitige Modus ist der Standard (`single_page=true`): Die gesamte Seite wird als eine durchgehende PDF-Seite gerendert, deren Höhe anhand des tatsächlichen Inhalts berechnet wird. Setzen Sie `single_page=false` für paginierte Ausgabe mit `page_size`, `orientation`, `margins` sowie optionalen Headern und Footern über `pdf_options`.

### Warum zeigt mein PDF Cookie-Banner oder fehlende Bilder?

Das Schließen von Cookie-Bannern (`handle_cookies`) und die Behandlung von Lazy Loading (`enable_scroll`, `load_media`, `wait_for_images`) sind standardmäßig alle `true` und decken OneTrust, Cookiebot, Didomi, Usercentrics und generische Banner ab. Wenn Sie `load_media=false` setzen, ist die Konvertierung schneller, aber Medien können als Platzhalter erscheinen.

### Kann ich eine Seite hinter einem Login in PDF konvertieren?

Ja. Verwenden Sie den Parameter `auth` für HTTP Basic Auth, injizieren Sie mit `cookies` bis zu 50 Sitzungscookies, oder senden Sie mit `headers` bis zu 20 benutzerdefinierte HTTP-Header. Diese Optionen erfordern Zugriff auf Basic Auth in Ihrem Plan, und `auth` kann nicht mit einem benutzerdefinierten `Authorization`-Header kombiniert werden.
