---
seo_title: Discover (Phase 2): Sitemap-Crawler-API | EnConvert
meta_desc: Private Beta, Phase 2: Alle URLs einer Website in einem Aufruf auflisten. Sitemap-Parsing plus HTTP-Crawl, ohne Rendering pro Seite, als flache Liste zurückgegeben.
keywords: sitemap crawler api, alle urls einer website auflisten api, website urls per api finden, sitemap xml auslesen api, website crawlen ohne browser api, url discovery api, seiten einer website auflisten api, robots.txt sitemap api
---

# Sitemap-Crawler-API

<div class="alert alert-warning">
<strong>Private Beta.</strong> Discover ist heute mit deinem normalen API-Schlüssel aufrufbar, in jedem Tarif einschließlich Founding, und zieht wie jeder andere Aufruf von deinem monatlichen Ops-Kontingent ab. Angekündigt oder allgemein verfügbar ist Discover nicht: Request- und Response-Formen können sich jederzeit ohne Vorankündigung ändern, und es gibt keine Stabilitäts- oder Supportzusage, baue also noch nichts Tragendes darauf. Die Roadmap steht unter <a href="/de/docs/coming-soon">Demnächst</a>, und jedes Release wird im <a href="/de/changelog">Changelog</a> angekündigt.
</div>

`POST /v2/discover` ist eine Sitemap-Crawler-API, die die URLs einer
Website auf die günstige Art auflistet: ein HTTP-first-Crawl plus
Sitemap-Parsing, mit einem optionalen JavaScript-Render-Fallback für
Single-Page-Apps. Es gibt keinen Screenshot, kein PDF und kein
gespeichertes Artefakt. Sie liefert eine flache,
deduplizierte Liste von URLs sowie eine Zählung, aus welcher Quelle
jede URL stammt, synchron in einem einzigen Aufruf. Sie wird das
leichtgewichtige „Website kartieren“-Primitiv sein: eine Domain angeben, die zu
verarbeitenden Seiten zurückbekommen, dann diese Liste an
[den Perceive-Endpunkt](/de/docs/endpoints/perceive.md) oder einen Ingest-Job im
Crawl-Modus übergeben.

Hier ist der kleinste sinnvolle Aufruf. Sende eine URL, erhalte ihre Karte zurück:

```bash
curl -X POST https://api.enconvert.com/v2/discover \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com"
  }'
```

Die Antwort ist eine einfache URL-Liste mit Herkunfts-Zählern. Es gibt
keine signierten URLs, keine Operation-ID und kein Polling:

```json
{
    "url": "https://example.com",
    "mode": "hybrid",
    "total": 47,
    "urls": [
        "https://example.com/",
        "https://example.com/pricing",
        "https://example.com/docs",
        "https://example.com/blog/launch"
    ],
    "pages_crawled": 12,
    "truncated": false,
    "robots_respected": false,
    "sources": {"sitemap": 42, "crawl": 30},
    "warnings": []
}
```

---

## Endpunkte

| Methode | Pfad | Zweck |
|--------|------|---------|
| `POST` | `/v2/discover` | Ermittelt die URLs einer Website ausschließlich über HTTP und liefert eine flache Liste mit Quellenzahlen zurück. |

`/v2/discover` stellt einen einzigen Pfad bereit. Er ist zustandslos:
Es gibt keine Operation-Zeile zum erneuten Abrufen und keinen Job zum
Pollen, daher gibt es auch keinen begleitenden `GET`-Endpunkt, wie ihn
[Perceive hat](/de/docs/endpoints/perceive.md).

**Content-Type:** `application/json` beim `POST`.

---

## Authentifizierung

Authentifiziere dich für Server-zu-Server-Aufrufe mit einem privaten
Schlüssel im `X-API-Key`-Header. Diesen Weg verwenden auch die
folgenden Beispiele.

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

Öffentliche Schlüssel mit einem JWT-Bearer-Token funktionieren
ebenfalls, nach demselben Ablauf wie bei jedem anderen Endpunkt:
Erzeuge mit deinem `pk_`-Schlüssel ein Token und sende es dann als
`Authorization: Bearer <token>`. Der vollständige Ablauf, einschließlich
Domain-Sperre und Token-Refresh, steht in
[der Authentifizierungs-Anleitung](/de/docs/authentication.md).

Jeder API-Schlüssel führt eine Allowlist erlaubter Endpunkte mit sich.
Steht `/v2/discover` nicht auf der Liste des Schlüssels, wird die
Anfrage mit `403` abgelehnt, samt einer Meldung, die den blockierten
Pfad nennt.

---

## So funktioniert Discover

Eine Anfrage läuft vollständig über HTTP ab. Das
Singleton-Headless-Chrome, das [Perceive](/de/docs/endpoints/perceive.md)
antreibt, wird dabei nie angefasst. Der Crawl-Pfad nutzt Crawl4AIs
HTTP-Crawler-Strategie (ein `httpx`-GET plus ein `lxml`-Link-Parse pro
Seite), und der Sitemap-Pfad verwendet dieselben reinen
HTTP-Sitemap- und Feed-Helfer wie der Rest der Plattform.

1. **Seed prüfen.** Die von dir gesendete URL wird vor jedem Abruf auf
   SSRF geprüft: Schema, eingebettete Zugangsdaten, blockierte
   Hostnamen und die aufgelöste IP werden allesamt validiert. Eine
   URL, die zu einer privaten, Loopback-, Link-Local- oder
   Cloud-Metadaten-Adresse auflöst, wird mit `400` abgelehnt. Der Seed
   ist immer der erste Eintrag in seiner eigenen Karte.
2. **Aus Sitemaps sammeln.** Im Modus `sitemap` oder `hybrid` liest
   Discover die `robots.txt` und ruft die dort deklarierten
   `Sitemap:`-URLs ab, prüft `sitemap.xml` (rekursiv durch
   `<sitemapindex>`-Kindelemente) und zieht RSS/Atom-Feed-Seiten
   hinzu. Diese Prüfungen führt Discover sowohl gegen den von dir
   gesendeten Host als auch gegen die registrierbare (Apex-)Domain der
   Website aus, sodass eine nur auf der Apex-Domain veröffentlichte
   Sitemap auch von einem Subdomain- oder Deep-Path-Seed aus gefunden
   wird. Eine fehlende oder defekte Sitemap wird zu einer Warnung,
   nicht zu einem Fehler.
3. **Über HTTP crawlen.** Im Modus `crawl` oder `hybrid` führt
   Discover ausgehend vom Seed einen Breadth-First-Crawl bis
   `max_depth` aus und sammelt dabei die `<a href>`-Links aus dem
   rohen HTML jeder abgerufenen Seite. Jeder verfolgte Link wird vor
   dem Abruf auf SSRF geprüft.
4. **Normalisieren und filtern.** Die zusammengeführte Rohliste wird
   kanonisiert (Fragmente und Tracking-Parameter entfernt,
   Standard-Ports gestrichen, Query-Keys sortiert) und durchläuft dann
   die Same-Domain-Prüfung, deine Include- und Exclude-Regex-Muster,
   den `robots.txt`-Filter, die Deduplizierung und schließlich die
   `max_urls`-Obergrenze.

Auf dem reinen HTTP-Pfad liefert eine clientseitig gerenderte
Single-Page-App im Modus `crawl` nur ihre HTML-Hülle zurück,
typischerweise den Seed plus null oder eine URL. Der optionale
`render_js`-Fallback (siehe [JavaScript-Rendering](#javascript-rendering))
schließt diese Lücke: In seiner Standardeinstellung `"auto"` rendert
Discover die Seite einmal im Browser und sammelt die Links, die sie
zur Laufzeit einfügt, immer dann, wenn der HTTP-Crawl nur eine Hülle
zurückliefert. Der Modus `sitemap` bleibt ein schneller,
browser-freier Weg für SEO-bewusste SPAs, die üblicherweise eine
Sitemap veröffentlichen.

---

## Request-Parameter

### Kern

| Parameter | Typ | Standard | Beschreibung |
|-----------|------|---------|-------------|
| `url` | `string` | -- | Die zu kartierende Website. Muss mit `http://` oder `https://` beginnen. Max. 2,048 Zeichen. Erforderlich. |
| `mode` | `string` | `"hybrid"` | `sitemap`, `crawl` oder `hybrid`. Siehe [Modi](#modes). |
| `max_urls` | `integer` | `100` | Maximale Anzahl zurückgegebener URLs. 1 bis 1,000. Die Liste wird hier begrenzt, und `truncated` zeigt an, ob mehr existierten. |
| `max_depth` | `integer` | `2` | Crawl-Tiefe ab dem Seed, im Modus `crawl`/`hybrid`. 1 bis 5. |
| `same_domain_only` | `boolean` | `true` | Behält nur URLs auf dem Host des Seeds. Bei `false` werden auch host-fremde Links behalten, die während des Crawls entdeckt werden. |
| `render_js` | `string` | `"auto"` | Browser-gerenderte Ermittlung für JavaScript-/SPA-Seiten (nur `crawl`/`hybrid`). `auto` rendert nur, wenn der HTTP-Crawl eine Hülle zurückliefert; `always` erzwingt es; `never` bleibt reines HTTP. Siehe [JavaScript-Rendering](#javascript-rendering). |

### Filterung

| Parameter | Typ | Standard | Beschreibung |
|-----------|------|---------|-------------|
| `include_patterns` | `string[]` | `[]` | Python-Regex-Allowlist (Semantik von `re.search`, kein Glob). Eine URL muss mindestens ein Muster treffen, um behalten zu werden. Leer bedeutet: alles erlauben. Max. 50 Muster. |
| `exclude_patterns` | `string[]` | `[]` | Python-Regex-Denylist. Eine URL, die auf ein Muster passt, wird verworfen. Wird nach `include_patterns` angewendet. Max. 50 Muster. |
| `respect_robots` | `boolean` | `false` | Bei `true` werden URLs, die laut der `robots.txt` der Website nicht erlaubt sind, aus der zurückgegebenen Liste entfernt. |

Die Muster sind echte reguläre Python-Ausdrücke, die zur
Validierungszeit kompiliert werden. Ein fehlerhaftes Muster ergibt am
Rand ein `422`, nicht mitten im Crawl ein `500`. Da die Semantik
`re.search` ist, passt ein bloßer Teilstring wie `"/blog/"` überall in
der URL, verankere also mit `^`/`$`, wenn du eine positionsbezogene
Übereinstimmung brauchst.

> **Wichtig vorab.** `respect_robots` wird zum Zeitpunkt der
> Ausgabefilterung durchgesetzt, nicht beim Abruf. Crawl4AI 0.8.9 hat
> kein natives Robots-Gate, daher kann im Modus `crawl` oder `hybrid`
> eine nicht erlaubte Seite trotzdem über HTTP abgerufen und erst
> danach verworfen werden, bevor sie die Antwort erreicht. Anders als
> [Perceive](/de/docs/endpoints/perceive.md), wo `respect_robots=true` eine
> nicht erlaubte URL mit `403` ablehnt, liefert Discover für eine
> Robots-Regel niemals `403`. Es verwirft die URL stillschweigend und
> meldet nichts.

### Modi {: #modes }

| `mode` | Was er tut |
|--------|--------------|
| `sitemap` | Sitemap-Einträge aus `robots.txt`, geprüfte `sitemap.xml` (mit Index-Rekursion) und RSS/Atom-Feed-Seiten. Sofort, ohne Crawl. |
| `crawl` | Reiner HTTP-Breadth-First-Crawl ab dem Seed, der `<a href>`-Links aus dem rohen Markup sammelt. |
| `hybrid` (Standard) | Die deduplizierte Vereinigung von `sitemap` und `crawl`. |

Der Modus `crawl` ruft bis zu deinem vollen `max_urls` ab (bis zu
1,000), sodass eine große Website in einem Durchgang erfasst wird. Der
Crawl-Modus ist reines HTTP (kein Browser), daher bleibt das schnell.
Betreiber können die Obergrenze über die Umgebungsvariable
`DISCOVER_CRAWL_MAX_PAGES` senken, falls ein sehr großer Crawl je
begrenzt werden muss. `pages_crawled` in der Antwort gibt genau an, wie
viele GETs ausgeführt wurden.

Der Sitemap-Abruf verarbeitet **gzip-komprimierte Sitemaps**
(`sitemap.xml.gz` und jede als `application/gzip` ausgelieferte
Sitemap) und sendet realistische Browser-Header, sodass Websites hinter
einer WAF ihre Sitemap deutlich wahrscheinlicher zurückliefern statt
eines `403`/`503`.

### JavaScript-Rendering

Die Modi `crawl` und `hybrid` lesen das rohe HTML, das ein Server
zurückgibt, sodass eine clientseitig gerenderte Single-Page-App, die
ihre Links im Browser aufbaut, für sie unsichtbar ist. `render_js`
aktiviert einen begrenzten Browser-Fallback, der diese Lücke schließt:

| `render_js` | Was er tut |
|-------------|--------------|
| `auto` (Standard) | Führt zuerst den HTTP-Crawl aus; rendert nur dann im Browser, wenn dieser eine bloße Hülle zurückliefert (eine oder keine URL), und sammelt dann die clientseitig eingefügten Links. |
| `always` | Führt immer den browser-gerenderten Crawl aus. |
| `never` | Bleibt strikt reines HTTP, das bisherige Verhalten. |

Der Fallback nutzt dasselbe gemeinsame Headless-Chrome wie
[Perceive](/de/docs/endpoints/perceive.md) und ist auf wenige Seiten begrenzt,
damit der Aufruf innerhalb des Request-Timeouts bleibt. Von ihm
gefundene Links werden unter dem Schlüssel `crawl_js` in `sources`
gezählt. Er gilt nur für die Modi `crawl` und `hybrid`, denn der Modus
`sitemap` ist bereits browser-frei und unberührt.

---

## Antwort

`POST /v2/discover` liefert dieses Objekt direkt zurück, ohne
asynchronen Job und ohne zweiten Aufruf.

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `url` | `string` | Die von dir gesendete Seed-URL. |
| `mode` | `string` | Der ausgeführte Modus: `sitemap`, `crawl` oder `hybrid`. |
| `total` | `integer` | Anzahl der URLs in `urls` (nach Deduplizierung, Filterung und Obergrenze). |
| `urls` | `string[]` | Die deduplizierte, normalisierte, begrenzte URL-Liste. Der Seed ist immer der erste Kandidat. |
| `pages_crawled` | `integer` | Vom Crawl ausgelöste HTTP-GETs. `0` im reinen Modus `sitemap`. |
| `truncated` | `boolean` | `true`, wenn mehr eindeutige URLs existierten, als `max_urls` erlaubte. |
| `robots_respected` | `boolean` | Spiegelt den von dir gesendeten Wert von `respect_robots`. |
| `sources` | `object` | Rohe URL-Anzahl pro Quelle vor Deduplizierung/Filterung, z. B. `{"sitemap": 42, "crawl": 30}`. Die Schlüssel umfassen `sitemap`, `crawl`, `sitemap_apex` (auf der Apex-Domain gefundene Sitemaps) und `crawl_js` (Links aus dem JavaScript-Render-Fallback). Die Zahlen überschneiden sich und summieren sich auf mehr als `total`. |
| `warnings` | `string[]` | Nicht-fatale Hinweise: eine fehlende Sitemap, ein fehlgeschlagener Crawl, eine unerreichbare `robots.txt`. |

Die `sources`-Zahlen sind roh: Sie geben an, wie viele URLs jeder Pfad
erzeugt hat, bevor Normalisierung, Same-Domain-Prüfung, deine Filter
und Deduplizierung liefen. Sie summieren sich routinemäßig auf mehr
als `total`, weil der Modus `hybrid` dieselben Seiten sowohl über die
Sitemap als auch über den Crawl findet. Nutze sie, um zu sehen, welcher
Pfad die Karte trägt, nicht als Nachfilter-Summe.

---

## Codebeispiele

### curl: Standard-Hybrid-Karte

```bash
curl -X POST https://api.enconvert.com/v2/discover \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com"
  }'
```

### curl: nur Sitemap, Blog-Seiten, begrenzt auf 500

```bash
curl -X POST https://api.enconvert.com/v2/discover \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "mode": "sitemap",
    "max_urls": 500,
    "include_patterns": ["/blog/"],
    "exclude_patterns": ["/tag/", "/author/"],
    "respect_robots": true
  }'
```

### Python

```python
import requests

response = requests.post(
    "https://api.enconvert.com/v2/discover",
    headers={"X-API-Key": "sk_your_private_key"},
    json={
        "url": "https://example.com",
        "mode": "hybrid",
        "max_urls": 200,
        "include_patterns": [r"/docs/"],
    },
)
response.raise_for_status()
data = response.json()

print(f"found {data['total']} URLs from {data['sources']}")
for page_url in data["urls"]:
    print(page_url)
```

### Node.js

```javascript
const res = await fetch("https://api.enconvert.com/v2/discover", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_your_private_key"
    },
    body: JSON.stringify({
        url: "https://example.com",
        mode: "hybrid",
        max_urls: 200,
        include_patterns: ["/docs/"]
    })
});

const data = await res.json();

console.log(`found ${data.total} URLs from`, data.sources);
data.urls.forEach((pageUrl) => console.log(pageUrl));
```

Ein gängiges Muster ist erst discovern, dann rendern: `data.urls`
nehmen und an [den Perceive-Endpunkt](/de/docs/endpoints/perceive.md) übergeben,
mit einzelnen Aufrufen für eine Handvoll Seiten oder dessen Batch-Pfad
für die gesamte Liste.

---

## Fehlerantworten

| Status | Bedingung |
|--------|-----------|
| `400 Bad Request` | Die URL ist nicht `http(s)`, enthält eingebettete Zugangsdaten, hat keinen Hostnamen oder löst zu einer privaten, Loopback-, Link-Local- oder Cloud-Metadaten-Adresse auf (SSRF-Schutz). |
| `401 Unauthorized` | Fehlender oder ungültiger API-Schlüssel / JWT-Token. |
| `402 Payment Required` | Discover ist in deinem aktuellen Plan nicht freigeschaltet, oder dein monatliches Ops-Kontingent ist aufgebraucht. Jeder `/v2/discover`-Aufruf kostet eine Op. |
| `403 Forbidden` | `/v2/discover` ist nicht in den erlaubten Endpunkten des API-Schlüssels enthalten. |
| `422 Unprocessable Entity` | Die Request-Validierung ist fehlgeschlagen: ungültiger `mode`-Enum-Wert, `max_urls` außerhalb von 1 bis 1,000, `max_depth` außerhalb von 1 bis 5, mehr als 50 Include-/Exclude-Muster, ein fehlerhafter Regex oder eine `url` über 2,048 Zeichen. |
| `500 Internal Server Error` | Die URL-Ermittlung ist unerwartet fehlgeschlagen. Der Client erhält eine generische Meldung; die vollständigen Details gehen ausschließlich in die Server-Logs. |

Beachte, dass ein fehlgeschlagener Sitemap-Abruf oder ein Crawl-Fehler
keinen Fehlerstatus erzeugt. Diese werden zu Einträgen im
`warnings`-Array degradiert, und die Anfrage liefert trotzdem `200`
mit allem, was gefunden wurde. Die vollständige Statuscode-Referenz
steht in [der Fehlercode-Anleitung](/de/docs/reference/errors.md).

---

## Limits

| Limit | Wert |
|-------|-------|
| URL-Länge | 2,048 Zeichen |
| `max_urls` | 1 bis 1,000 (Standard 100) |
| `max_depth` | 1 bis 5 (Standard 2) |
| `include_patterns` | max. 50 Muster |
| `exclude_patterns` | max. 50 Muster |
| Abgerufene Seiten im Crawl-Modus | bis zu `max_urls` (max. 1,000; senkbar über `DISCOVER_CRAWL_MAX_PAGES`) |
| Request-Timeout | 300 Sekunden |
| Ops pro Aufruf | 1, berechnet gegen das einheitliche monatliche Kontingent |

---

## Häufig gestellte Fragen

### Wie liste ich alle URLs einer Website mit einer API auf?

Sende `POST /v2/discover` mit der URL der Website. Der Standardmodus `hybrid` kombiniert Sitemap-Parsing (`robots.txt`-Einträge, `sitemap.xml` mit Index-Rekursion, RSS/Atom-Feeds) mit einem Breadth-First-HTTP-Crawl und liefert in einer einzigen synchronen Antwort bis zu `max_urls` (1 bis 1,000, Standard 100) deduplizierte URLs.

### Nutzt der Discover-Endpunkt einen Headless-Browser oder rendert er JavaScript?

Standardmäßig läuft er über HTTP und rendert nur, wenn es sein muss. Die Option `render_js` steuert dies: Im Standardmodus `"auto"` bleibt Discover reines HTTP und rendert eine Seite nur dann im Browser, wenn der HTTP-Crawl eine bloße JavaScript-Hülle zurückliefert, wobei die clientseitig eingefügten Links gesammelt werden; `"always"` erzwingt den Browser-Crawl und `"never"` hält ihn strikt bei reinem HTTP. Der Modus `sitemap` bleibt eine schnelle, browser-freie Option für SEO-bewusste SPAs.

### Wie viele Seiten ruft der Crawl-Modus tatsächlich ab?

Der Crawl-Modus ruft bis zu deinem vollen `max_urls` ab (bis zu 1,000). Er bleibt reines HTTP und damit schnell; Betreiber können die Obergrenze über die Umgebungsvariable `DISCOVER_CRAWL_MAX_PAGES` senken. Jede abgerufene Seite trägt außerdem viele Links bei. Das Antwortfeld `pages_crawled` gibt genau an, wie viele HTTP-GETs ausgeführt wurden.

### Kann ich filtern, welche URLs zurückkommen?

Ja. `include_patterns` und `exclude_patterns` akzeptieren jeweils bis zu 50 Python-Regexe (Semantik von `re.search`, kein Glob), und `respect_robots: true` entfernt URLs, die laut der `robots.txt` der Website nicht erlaubt sind, aus der zurückgegebenen Liste.
