---
seo_title: URL zu Markdown API – Webseiten für LLMs konvertieren | EnConvert
meta_desc: Wandle URLs mit POST /v1/convert/url-to-markdown in sauberes GitHub-Flavored Markdown um. Readability-Extraktion und YAML-Frontmatter für LLM-Ingestion-Pipelines.
keywords: webseite in markdown umwandeln api, html zu markdown api, url in markdown konvertieren, markdown converter für llm pipelines, readability extraktion api, batch url markdown konvertierung, rag pipeline markdown extraktion, artikel content zu markdown api
---

# URL zu Markdown API

Der Endpoint `POST /v1/convert/url-to-markdown` konvertiert jede öffentlich zugängliche Webseite in sauberes GitHub-Flavored Markdown mit einem YAML-Frontmatter-Metadatenblock. Jede Seite wird in einem echten Browser gerendert, durch einen Readability-Extraktor geschickt, der Boilerplate entfernt (Navigation, Footer, Asides, Scripts, Formulare, Buttons), und anschließend zu Markdown serialisiert. Dabei werden Links normalisiert, Codeblöcke eingerahmt und relative URLs zu absoluten aufgelöst. Genau das, was LLM-Ingestion- und RAG-Pipelines brauchen, statt rohem HTML. Konvertierungen laufen synchron oder asynchron im Batch, und die Ergebnisse werden als rohe Markdown-Bytes oder als vorsignierte Download-URL zurückgegeben.

---

## Endpunkt

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

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

**Ausgabeformat:** Markdown (`.md`, UTF-8) mit einem YAML-Frontmatter-Block am Anfang der Datei, der Seiten-Metadaten enthält. Das Ausgabeformat ist nicht konfigurierbar. Es wird immer Markdown mit YAML-Frontmatter erzeugt.

---

## Authentifizierung

Dieser Endpunkt unterstützt sowohl Private-Key- als auch Public-Key-Authentifizierung.

### Privater Schlüssel

Gib deinen geheimen Schlüssel im `X-API-Key`-Header an. Verwende 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

Für die clientseitige Nutzung generierst du zuerst ein JWT-Token mit deinem öffentlichen Schlüssel und übergibst 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, synchronen Modus und direkten Download beschränkt. Async-Modus, Batch-Verarbeitung, Webhooks und Benachrichtigungs-E-Mails stehen bei öffentlichen Schlüsseln nicht zur Verfügung.
</div>

---

## Anfrageparameter

### Parameter der obersten Ebene

| Parameter | Type | Required | Default | Description | Plan Gating |
|-----------|------|----------|---------|-------------|-------------|
| `url` | `string` oder `string[]` | Ja | -- | Ein einzelner URL-String oder ein Array von URLs, die konvertiert werden sollen. Mehrere URLs erfordern den Async-Modus. | -- |
| `async_mode` | `boolean` | Nein | `false` | Führt die Konvertierung asynchron aus. Gibt sofort eine `batch_id` zum Polling zurück. Erforderlich für Batch (mehrere URLs). | Erfordert Async-Zugriff |
| `direct_download` | `boolean` | Nein | `false` | Gibt rohe Markdown-Bytes im Response-Body zurück statt einer JSON-Antwort mit vorsignierter 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 Markdown-Ausgabedateien in ein einzelnes 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 Erweiterung `.md` wird automatisch hinzugefügt. Standardformat: `{domain}_{timestamp}.md`. | -- |
| `job_id` | `string` | Nein | -- | Vom Client bereitgestellte Job-ID zur Timeout-Wiederherstellung. **Nur öffentliche Schlüssel.** Wenn eine synchrone Konvertierung die Timeout-Grenzen des Reverse-Proxys überschreitet, kann der Client `GET /v1/convert/status/{job_id}` abfragen, um das Ergebnis zu erhalten. Wird bei privaten Schlüsseln ignoriert. | -- |
| `notification_email` | `string` | Nein | E-Mail-Adresse des Projektinhabers | E-Mail-Adresse, die benachrichtigt wird, wenn ein Async-Job abgeschlossen ist. Nur private Schlüssel. | -- |
| `callback_url` | `string` | Nein | -- | Webhook-URL, die eine POST-Anfrage erhält, wenn die Konvertierung abgeschlossen ist. Nur private Schlüssel. | Erfordert Webhook-Zugriff |

### Browser- und Rendering-Parameter

| Parameter | Type | Required | Default | Description | Plan Gating |
|-----------|------|----------|---------|-------------|-------------|
| `viewport_width` | `integer` | Nein | `1920` | Breite des Browser-Viewports in Pixeln. Beeinflusst responsiven Content und welche Layout-Variante vor der Extraktion erfasst wird. | -- |
| `viewport_height` | `integer` | Nein | `1080` | Höhe des Browser-Viewports in Pixeln. Dient als Referenz für Rendering und die Berechnung von Viewport-Einheiten. | -- |
| `load_media` | `boolean` | Nein | `true` | Wartet, bis alle Bilder und Videos vollständig geladen sind, bevor die Extraktion beginnt. Bei `false` ist die Extraktion schneller, aber lazy-geladene Bilder können im Markdown-Output Platzhalter-Werte bei `src` haben. | -- |
| `enable_scroll` | `boolean` | Nein | `true` | Scrollt die Seite von oben nach unten, um Lazy-Loading-Content auszulösen (IntersectionObserver-basierte Loader). | -- |
| `handle_sticky_header` | `boolean` | Nein | `true` | Erkennt sticky/fixed Header und scrollt vor der Extraktion nach oben, sodass die Content-Reihenfolge korrekt erhalten bleibt. | -- |
| `handle_cookies` | `boolean` | Nein | `true` | Schließt Cookie-Consent-Banner (OneTrust, Cookiebot, Didomi, Usercentrics und generische Banner) vor der Extraktion automatisch. | -- |
| `wait_for_images` | `boolean` | Nein | `true` | Wartet, bis alle `<img>`-Elemente fertig geladen sind (5-Sekunden-Timeout pro Bild), damit `alt`-Text und finale `src`-Werte korrekt erfasst werden. | -- |
| `wait_for_selector` | `string` | Nein | `null` | CSS-Selektor, auf den vor der Extraktion 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 werden oder die Extraktion 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 | Type | Required | Default | Description | Plan Gating |
|-----------|------|----------|---------|-------------|-------------|
| `auth` | `object` | Nein | `null` | HTTP-Basic-Auth-Zugangsdaten für die Ziel-URL. Format: `{"username": "...", "password": "..."}`. Kann nicht zusammen mit einem benutzerdefinierten `Authorization`-Header verwendet werden. | Erfordert Basic-Auth-Zugriff |
| `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 Basic-Auth-Zugriff |
| `headers` | `object` | Nein | `null` | Dictionary mit benutzerdefinierten HTTP-Headern, die mit jeder Anfrage an die Ziel-URL gesendet werden. Maximal 20 Header. Blockierte Header: `host`, `content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`. | Erfordert Basic-Auth-Zugriff |

<div class="alert alert-warning">
<strong>Nicht unterstützt:</strong> Die Parameter <code>single_page</code> und <code>pdf_options</code> aus dem <a href="/de/docs/endpoints/convert/web-pages/url-to-pdf">url-to-pdf</a>-Endpunkt werden zur Wahrung der Request-Struktur akzeptiert, haben aber keine Auswirkung auf die Markdown-Ausgabe. Markdown kennt kein Konzept von Seiten, Rändern oder Ausrichtung.
</div>

---

## Cookie-Objekt-Schema

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

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `name` | `string` | Ja | -- | Cookie-Name. |
| `value` | `string` | Ja | -- | Cookie-Wert. |
| `domain` | `string` | Bedingt | -- | Cookie-Domain. 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 | `"/"` | Cookie-Pfad. Standardmäßig `"/"`, wenn `domain` gesetzt ist. |

---

## Antwort

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

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

```
HTTP 200 OK
Content-Type: text/markdown; charset=utf-8
Content-Disposition: inline; filename="example_20260421_123456789.md"
X-Object-Key: env/files/{project_id}/url-to-markdown/example_20260421_123456789.md
X-File-Size: 8421
X-Conversion-Time: 6.3
X-Filename: example_20260421_123456789.md

(UTF-8 Markdown with YAML frontmatter)
```

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

```json
{
    "presigned_url": "https://spaces.example.com/...",
    "object_key": "env/files/{project_id}/url-to-markdown/example_20260421_123456789.md",
    "filename": "example_20260421_123456789.md",
    "file_size": 8421,
    "conversion_time_seconds": 6.3,
    "job_id": "client-provided-id"
}
```

### Synchron ohne Direct 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-markdown/example_20260421_123456789.md",
    "filename": "example_20260421_123456789.md",
    "file_size": 8421,
    "conversion_time_seconds": 6.3
}
```

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

Für die Timeout-Wiederherstellung bei öffentlichem Schlüssel:

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

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

### Batch-Status-Polling (nur private Schlüssel)

Frage bei asynchronen Batch-Jobs mit der `batch_id` aus der 202-Antwort ab:

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

Gibt aggregierten Status, Status pro URL sowie vorsignierte Download-URLs zurück. Siehe [Batch-Status-Polling](/de/docs/endpoints/convert/web-pages/url-to-pdf.md#batch-status-polling-private-keys-only) für das vollständige Response-Schema.

### Webhook-Callback-Payload

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

**Einzel-URL-Job:**

```json
{
    "job_id": "activity_id",
    "status": "success",
    "batch_id": "...",
    "gcs_uri": "object_key",
    "filename": "example_20260421_123456789.md",
    "file_size": 8421
}
```

**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.md"},
        {"url": "https://example.com/page2", "status": "failed", "filename": null}
    ]
}
```

---

## Ausgabeformat

Jede Markdown-Datei beginnt mit einem YAML-Frontmatter-Block, der Seiten-Metadaten enthält, gefolgt vom extrahierten Artikeltext.

```markdown
---
url: https://example.com/articles/my-post
title: My Post Title
description: A short summary from the meta description or og:description tag.
links:
  - url: https://example.com/related
    text: Related article
  - url: https://example.com/about
    text: About the author
images:
  - url: https://example.com/hero.jpg
    alt: Hero image alt text
  - url: https://example.com/diagram.png
    alt: Architecture diagram
---

# My Post Title

Opening paragraph of the article body, converted to GitHub-Flavored Markdown...

## A Section Heading

- List item one
- List item two

\`\`\`python
def example():
    return "code blocks are fenced with language hints"
\`\`\`

[A link in the body](https://example.com/linked-page)

![An image in the body](https://example.com/inline-image.png)
```

### Frontmatter-Felder

| Field | Type | Description |
|-------|------|-------------|
| `url` | `string` | Die finale URL nach Weiterleitungen (nicht immer die URL, die du gesendet hast). |
| `title` | `string` | Der Seitentitel aus `<title>`, mit Fallback auf den von Readability erkannten Kurztitel. |
| `description` | `string` | Der Wert von `<meta name="description">`, mit Fallback auf `<meta property="og:description">`. |
| `links` | `array` | Jedes auf der Seite gefundene `<a href>`, mit absoluten URLs und sichtbarem Ankertext. |
| `images` | `array` | Jedes auf der Seite gefundene `<img src>`, mit absoluten URLs und `alt`-Text. |

### Markdown-Konventionen

- **Überschriftenstil:** ATX (`#`, `##`, `###`)
- **Listenpunkte:** `-`
- **Hervorhebung:** `*bold*`, `*italic*`, mit escaptem `*` und `_` in literalem Text
- **Weiche Zeilenumbrüche:** zwei nachgestellte Leerzeichen (im Output erhalten)
- **Codeblöcke:** eingerahmt (` ``` `) mit Sprachhinweisen, erkannt aus `class="language-xxx"`, `class="lang-xxx"`, `class="highlight-source-xxx"`, `data-lang` und `data-language`
- **Links:** `[text](url)`, wenn Ankertext vorhanden ist; Autolink-Form `<url>`, wenn der Anker leer ist; reine Anker-Links (`#foo`) und `javascript:`-Links werden zu reinem Text entpackt
- **Bilder:** `![alt](src)`, wobei `title` erhalten bleibt, falls vorhanden, mit Fallback auf `data-src`, wenn `src` fehlt (lazy-geladene Bilder)
- **Horizontale Linien:** `---`

---

## Funktionen

### Saubere Content-Extraktion

EnConvert verwendet den Readability-Algorithmus (dieselbe Bibliothek, die auch die Firefox Reader View antreibt), um den Hauptartikel-Content vom Rest der Seite zu isolieren, und wendet anschließend einen zweiten Nachbearbeitungsschritt an, um sauberes Markdown zu erzeugen.

**Vor der Konvertierung entfernt:**

- Navigation (`<nav>`), Footer (`<footer>`), Asides (`<aside>`)
- Scripts (`<script>`, `<noscript>`), Styles (`<style>`), iframes, Formulare, Buttons
- Inline-SVG, Canvas- und Template-Elemente
- `style`, `class`, `id` und alle `on*`-Event-Handler-Attribute

**Erhalten bleiben:**

- Überschriften, Absätze, Listen, Tabellen, Blockquotes, Codeblöcke
- Links mit ihrem `href` und Ankertext (absolute URLs)
- Bilder mit `alt`, `title` und absolutem `src`
- Figures und Figcaptions (Inline-Bilder bleiben darin erhalten)

### Sauberer Erfassungsmodus

Vor der Extraktion wird die Seite in einem echten Browser gerendert und auf dieselbe Weise bereinigt wie bei [url-to-pdf](/de/docs/endpoints/convert/web-pages/url-to-pdf.md):

- **Cookie-Consent-Banner** -- Werden auf der Hauptseite und in iframes automatisch geschlossen (OneTrust, Cookiebot, Didomi, Usercentrics und generische Banner).
- **Schließen von Modals und Popups** -- Overlays werden per Escape-Taste, ARIA-Schließen-Buttons, klassenbasierten Schließen-Buttons und rollenbasierten Dialog-Buttons geschlossen.
- **Scroll-Animation-Reveal** -- Erzwingt die Sichtbarkeit von Elementen, die durch WOW.js, AOS, ScrollReveal, GSAP ScrollTrigger und generische Animationsklassen verborgen sind.
- **Behandlung von Sticky-Headern** -- Sticky/fixed Header werden erkannt und die Seite wird zurück nach oben gescrollt, sodass die Content-Reihenfolge erhalten bleibt.

### Auflösung absoluter URLs

Jedes relative `href` und `src` im extrahierten Artikel wird gegen die finale Seiten-URL (nach Weiterleitungen) aufgelöst, sodass der Markdown-Output immer absolute, klickbare Links enthält. Das hilft LLM-Ingestion-Pipelines, die sonst defekte relative Pfade sehen würden.

Reine Anker-Links (`#section`), `javascript:`-Links, `mailto:`- und `tel:`-Links werden nicht umgeschrieben. Reine Anker-Links und `javascript:`-Links werden zu reinem Text entpackt, da sie außerhalb der Originalseite keine Bedeutung haben.

### Erkennung der Codeblock-Sprache

Codeblöcke werden nach Möglichkeit mit einem erkannten Sprachhinweis eingerahmt:

```
<pre><code class="language-python">...</code></pre>    →   ```python
<pre data-lang="js">...</pre>                          →   ```js
<pre><code class="highlight-source-shell">...</code></pre> →   ```shell
```

Klassen, die `language-*`, `lang-*`, `highlight-source-*` und `brush:*` entsprechen, werden erkannt, ebenso die Attribute `data-lang` und `data-language` sowohl am `<pre>` als auch am verschachtelten `<code>`. Wird kein Hinweis gefunden, wird der Block ohne Sprachkennzeichnung eingerahmt.

### HTTP Basic Auth

Übergib `auth` mit `username` und `password`, um Seiten hinter HTTP Basic Authentication zu konvertieren.

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

### Cookie-Injection

Injiziere bis zu 50 Cookies, bevor die Seite lädt. Nützlich zum Konvertieren von Mitglieder-exklusiven oder locale-spezifischen Artikelseiten.

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

### Benutzerdefinierte Header

Sende bis zu 20 benutzerdefinierte HTTP-Header mit jeder Anfrage an die Zielseite.

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

### Lazy-Loading von Bildern

Wenn `load_media` und `enable_scroll` aktiviert sind (beide standardmäßig `true`), scrollt der Converter die Seite langsam, um Lazy-Loader auszulösen, und wartet dann, bis alle Bilder fertig geladen sind, bevor das finale HTML erfasst wird. So wird sichergestellt, dass `data-src`-Werte zu echten `src`-Werten hochgestuft wurden und die `images`-Liste im Frontmatter vollständig ist.

Setze `load_media=false` für eine schnellere Extraktion, wenn du nur den Textkörper benötigst. Platzhalter-Werte bei `src` können dann im Output verbleiben.

### Weitere Rendering-Funktionen

- **Normalisierung von Viewport-Einheiten** -- CSS-Viewport-Einheiten (`vh`, `svh`, `lvh`, `dvh`) werden vor der Extraktion in feste Pixelwerte umgerechnet.
- **Stealth-Modus** -- Maskierung des Browser-Fingerprints, 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-Bypass** -- Behandelt Content-Security-Policy- und Trusted-Types-Beschränkungen, die die Seitenmanipulation sonst blockieren würden.

---

## Abo-Plan-Einschränkungen

| Feature | Founding | Indie | Studio | Enterprise |
|---------|------|---------|-----|------------|
| Basiskonvertierung (einzelne URL, synchron) | Ja | Ja | Ja | Ja |
| Viewport- und Rendering-Optionen | Ja | Ja | Ja | Ja |
| Async-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-Injection | Nein | Ja | Ja | Ja |
| Benutzerdefinierte Header | Nein | Ja | Ja | Ja |
| Monatliche Konvertierungen | 100 | Planabhängig | Planabhängig | Unbegrenzt |
| Batch-Größenlimit | 0 | Planabhängig | Planabhängig | Unbegrenzt |
| Dateiaufbewahrung | 1 Stunde | Planabhängig | Planabhängig | Planabhängig |

---

## Async-Modus

Der asynchrone Modus ist nützlich für lang laufende Konvertierungen oder bei der Konvertierung mehrerer URLs.

### So funktioniert's

1. Sende eine Anfrage mit `async_mode=true` (oder übergib mehrere URLs, wodurch Async 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. Überwache den Abschluss über **Batch-Status-Polling**, **E-Mail-Benachrichtigung** oder **Webhook-Callback**.

### E-Mail-Benachrichtigung

Standardmäßig wird eine Abschluss-E-Mail an die E-Mail-Adresse des Projektinhabers gesendet. Überschreibe dies mit `notification_email`:

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

### Webhook-Callback

Gib eine `callback_url` an, um bei Abschluss 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 30-Sekunden-Timeout gesendet und betrachtet HTTP 200, 201, 202 und 204 als erfolgreiche Zustellung.

---

## Batch- und Massenverarbeitung

Konvertiere mehrere URLs in einer einzigen Anfrage. Erfordert den Async-Modus und einen privaten Schlüssel.

### Einzelne Ausgabe (Standard)

Jede URL erzeugt eine separate Markdown-Datei:

```json
{
    "url": [
        "https://example.com/post-1",
        "https://example.com/post-2",
        "https://example.com/post-3"
    ],
    "async_mode": true
}
```

### ZIP-Bundle-Ausgabe

Bündle alle Markdown-Dateien in ein einzelnes ZIP-Archiv:

```json
{
    "url": [
        "https://example.com/post-1",
        "https://example.com/post-2",
        "https://example.com/post-3"
    ],
    "async_mode": true,
    "output_format": true,
    "output_filename": "blog-archive"
}
```

Die ZIP-Datei heißt `{output_filename}_{timestamp}.zip` 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-markdown",
    headers={"X-API-Key": "sk_your_private_key"},
    json={
        "url": "https://example.com/articles/my-post",
        "direct_download": True
    }
)

response.raise_for_status()
markdown_text = response.content.decode("utf-8")
print(markdown_text)
```

### PHP (privater Schlüssel)

```php
$ch = curl_init("https://api.enconvert.com/v1/convert/url-to-markdown");
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/articles/my-post"
    ])
]);

$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-markdown", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_your_private_key"
    },
    body: JSON.stringify({
        url: "https://example.com/articles/my-post"
    })
});

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

### Go (privater Schlüssel)

```go
package main

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

func main() {
    body, _ := json.Marshal(map[string]interface{}{
        "url": "https://example.com/articles/my-post",
    })

    req, _ := http.NewRequest("POST", "https://api.enconvert.com/v1/convert/url-to-markdown", 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 Markdown (public keys receive raw bytes)
const convertRes = await fetch("https://api.enconvert.com/v1/convert/url-to-markdown", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${token}`
    },
    body: JSON.stringify({
        url: "https://example.com/articles/my-post"
    })
});

const markdown = await convertRes.text();
console.log(markdown);
```

### React (öffentlicher Schlüssel)

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

function UrlToMarkdown() {
    const [loading, setLoading] = useState(false);
    const [markdown, setMarkdown] = useState("");

    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-markdown", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${token}`
                },
                body: JSON.stringify({ url: "https://example.com/articles/my-post" })
            });

            setMarkdown(await convertRes.text());
        } finally {
            setLoading(false);
        }
    }

    return (
        <div>
            <button onClick={convertUrl} disabled={loading}>
                {loading ? "Converting..." : "Convert to Markdown"}
            </button>
            {markdown && <pre>{markdown}</pre>}
        </div>
    );
}

export default UrlToMarkdown;
```

---

## Fehlerantworten

| Status | Condition |
|--------|-----------|
| `400 Bad Request` | Fehlender oder leerer `url`-Parameter |
| `400 Bad Request` | `output_format=true` mit einer einzelnen URL (erfordert mehrere URLs) |
| `400 Bad Request` | `direct_download=true` mit 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 Header-Namen, nicht-String-Werte) |
| `400 Bad Request` | Widersprüchliche `auth`- und benutzerdefinierter `Authorization`-Header |
| `400 Bad Request` | Öffentlicher Schlüssel versucht mehrere URLs zu verwenden |
| `401 Unauthorized` | Fehlender oder ungültiger API-Key / 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-Keys |
| `403 Forbidden` | Funktion im aktuellen Plan nicht verfügbar (Async, Webhook, ZIP, Basic Auth) |
| `403 Forbidden` | Batch-Größe überschreitet das Batch-Limit des Plans |
| `404 Not Found` | Job-ID nicht gefunden (beim Abfragen des Status) |
| `500 Internal Server Error` | Konvertierung fehlgeschlagen (Browser-Absturz, Navigationsfehler, Extraktionsfehler) |

---

## Limits

| Limit | Value |
|-------|-------|
| Timeout für Seitennavigation | 60 Sekunden |
| Ladezeit-Timeout pro Bild | 5 Sekunden |
| Timeout für Cookie-Banner-Schließen | 3 Sekunden |
| Maximale Cookies pro Anfrage | 50 |
| Maximale benutzerdefinierte Header pro Anfrage | 20 |
| Monatliche Operationen | Planabhängig (Founding: 500) |
| Batch-Größe | Planabhängig (Founding: deaktiviert) |
| Dateiaufbewahrung | Planabhängig (Founding: 1 Stunde) |
| Timeout für Webhook-Zustellung | 30 Sekunden |

---

## Häufig gestellte Fragen

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

Sende eine `POST`-Anfrage an `/v1/convert/url-to-markdown` mit einer `url` im JSON-Body und deinem Key im `X-API-Key`-Header. Du erhältst eine JSON-Antwort mit einer `presigned_url` zur Markdown-Datei zurück, oder die rohen UTF-8-Markdown-Bytes, wenn du `direct_download=true` setzt.

### Kann ich Webseiten für LLM- und RAG-Pipelines in Markdown konvertieren?

Ja. Der Output ist für LLM-Ingestion gebaut. Der Readability-Algorithmus (dieselbe Bibliothek hinter der Firefox Reader View) isoliert den Hauptartikel, Boilerplate wie `<nav>`, `<footer>`, Scripts und Formulare wird entfernt, jeder relative Link und jede relative Bild-URL wird zu einer absoluten URL aufgelöst, und ein YAML-Frontmatter-Block enthält die Seiten-`url`, `title`, `description`, `links` und `images`.

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

Ja. Übergib ein Array von URLs in `url` mit `async_mode=true` (erfordert einen privaten Schlüssel und einen Plan mit Batch-Zugriff); die API gibt HTTP `202` mit einer `batch_id` zurück, die du über `GET /v1/convert/batch/{batch_id}` abfragst. Setze `output_format=true`, um alle Markdown-Dateien in ein einzelnes ZIP-Archiv zu bündeln.

### Warum haben manche Bilder in meinem Markdown-Output Platzhalter-Werte für src?

Das passiert, wenn `load_media=false` ist. Die Extraktion ist dann schneller, aber lazy-geladene Bilder können Platzhalter-Werte bei `src` behalten. Lass `load_media` und `enable_scroll` auf ihrem Standardwert `true`, damit die Seite gescrollt wird, um Lazy-Loader auszulösen, und jedes Bild vor der Erfassung fertig lädt (5-Sekunden-Timeout pro Bild).

### Funktioniert die URL zu Markdown API auf Seiten hinter einem Login?

Ja, bei Plänen mit Basic-Auth-Zugriff: Übergib `auth` mit `username` und `password` für HTTP Basic Auth, injiziere bis zu 50 Session-`cookies`, oder sende bis zu 20 benutzerdefinierte `headers`. Das ist nützlich für Mitglieder-exklusive oder Staging-Artikelseiten.
