Discover
POST /v2/discover ermittelt die URLs einer Website, ohne eine einzige
Seite zu rendern. Kein Browser, kein Screenshot, kein PDF, kein
gespeichertes Artefakt — nur ein reiner HTTP-Crawl plus Sitemap-Parsing,
das eine flache Liste von URLs zurückgibt sowie eine Zählung, woher jede
einzelne stammt. Es ist das günstige "Site mappen"-Primitiv: Richte es auf
eine Domain, erhalte die verarbeitenswerten Seiten zurück und gib diese
Liste dann an den perceive-Endpunkt
oder einen Ingest-Job im Crawl-Modus weiter.
Hier ist der kleinste nützliche Aufruf. Sende eine URL, erhalte die Map zurück:
curl -X POST https://api.enconvert.com/v2/discover \
-H "X-API-Key: sk_live_your_private_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com"
}'
Die Antwort ist eine schlichte URL-Liste mit Herkunfts-Zählern — keine signierten URLs, keine Operations-ID, kein Polling:
{
"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": []
}
Vorab erwähnenswert.
/v2/discoverist ein V2-Endpunkt, geschützt durch das Plan-Flagdiscover_enabled. Anders als die meisten V2-Endpunkte hat er keinen Quota-Zähler pro Operation — er ist günstig (kein Browser, kein Speicher), daher gibt es kein monatliches Kontingent, das aufgebraucht werden könnte. Wenn dein Plan V2 discover nicht enthält, gibt der Aufruf402zurück. Siehe die Preisseite dazu, welche Pläne ihn umfassen.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v2/discover |
Mappt die URLs einer Website ausschließlich über HTTP und gibt eine flache Liste mit Quellenzählungen zurück. |
/v2/discover stellt einen einzigen Pfad bereit. Er ist zustandslos — es
gibt keine Operations-Zeile zum erneuten Abrufen und keinen Job zum
Pollen, also gibt es auch keinen begleitenden GET-Endpunkt, wie
perceive ihn hat.
Content-Type: application/json beim POST.
Authentication
Authentifiziere dich mit einem privaten Schlüssel im X-API-Key-Header
für Server-zu-Server-Aufrufe. Das ist der Weg, den die Beispiele unten
verwenden.
X-API-Key: sk_live_your_private_key
Öffentliche Schlüssel mit einem JWT-Bearer-Token funktionieren ebenfalls,
über denselben Ablauf wie bei jedem anderen Endpunkt — generiere ein Token
mit deinem pk_-Schlüssel und sende es dann als
Authorization: Bearer <token>. Der vollständige Ablauf, einschließlich
Domain-Sperre und Token-Refresh, findet sich in
der Authentifizierungsanleitung.
Jeder API-Schlüssel trägt eine Allowlist erlaubter Endpunkte. Wenn
/v2/discover nicht auf der Liste des Schlüssels steht, wird die Anfrage
mit 403 abgelehnt, samt einer Meldung, die den blockierten Pfad nennt.
How discover works
Eine Anfrage läuft vollständig über HTTP. Das Singleton-Headless-Chrome,
das perceive antreibt, wird nie berührt — der
Crawl-Pfad nutzt die HTTP-Crawler-Strategie von Crawl4AI (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.
- Den Seed prüfen. Die URL, die du sendest, wird vor jedem Abruf auf
SSRF geprüft: Schema, eingebettete Zugangsdaten, gesperrte Hostnamen und
die aufgelöste IP werden allesamt validiert. Eine URL, die auf eine
private, Loopback-, Link-Local- oder Cloud-Metadata-Adresse auflöst,
wird mit
400abgelehnt. Der Seed ist stets der erste Eintrag in seiner eigenen Map. - Aus Sitemaps sammeln. Im
sitemap- oderhybrid-Modus liest discoverrobots.txt, prüftsitemap.xml(mit Rekursion in<sitemapindex>-Kinder) und zieht RSS/Atom-Feed-Seiten heran. Eine fehlende oder defekte Sitemap wird zu einer Warnung, nicht zu einem Fehler. - Über HTTP crawlen. Im
crawl- oderhybrid-Modus führt discover einen Breitensuche-Crawl vom Seed aus bis zumax_depthdurch und liest die<a href>-Links aus dem rohen HTML jeder abgerufenen Seite. Jeder verfolgte Link wird vor dem Abruf auf SSRF geprüft. - Normalisieren und filtern. Die kombinierte Rohliste wird
kanonisiert (Fragmente und Tracking-Parameter entfernt, Standard-Ports
entfernt, Query-Keys sortiert), dann durch die Same-Domain-Prüfung,
deine include- und exclude-Regex-Muster, den
robots.txt-Filter, die Deduplizierung und schließlich dasmax_urls-Limit geführt.
Da es kein Rendering gibt, gibt eine clientseitig gerenderte
Single-Page-App im crawl-Modus nur ihre HTML-Hülle zurück — typischerweise
der Seed plus null oder eine URL. Das ist korrektes Verhalten für einen
Endpunkt ohne Browser, kein Fehler. Verwende für SPAs den sitemap-Modus
(die meisten SEO-bewussten SPAs veröffentlichen eine Sitemap) oder weiche
auf einzelne perceive-Aufrufe pro URL aus.
Request parameters
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string |
— | Die zu mappende Website. Muss mit http:// oder https:// beginnen. Maximal 2.048 Zeichen. Erforderlich. |
mode |
string |
"hybrid" |
sitemap, crawl oder hybrid. Siehe Modi. |
max_urls |
integer |
100 |
Maximale Anzahl zurückgegebener URLs. 1–1.000. Die Liste wird hier gekappt, und truncated wird gesetzt, falls mehr vorhanden waren. |
max_depth |
integer |
2 |
Crawl-Tiefe vom Seed aus, im crawl/hybrid-Modus. 1–5. |
same_domain_only |
boolean |
true |
Behält nur URLs auf dem Host des Seeds. Bei false werden auch beim Crawl entdeckte Off-Host-Links behalten. |
Filtering
| Parameter | Type | Default | Description |
|---|---|---|---|
include_patterns |
string[] |
[] |
Python-Regex-Allowlist (re.search-Semantik, kein Glob). Eine URL muss mindestens einem Muster entsprechen, um behalten zu werden. Leer bedeutet alle erlauben. Maximal 50 Muster. |
exclude_patterns |
string[] |
[] |
Python-Regex-Denylist. Eine URL, die einem beliebigen Muster entspricht, wird verworfen. Wird nach include_patterns angewendet. Maximal 50 Muster. |
respect_robots |
boolean |
false |
Bei true werden URLs, die durch die robots.txt der Site verboten sind, aus der zurückgegebenen Liste entfernt. |
Die Muster sind echte reguläre Python-Ausdrücke, kompiliert zur
Validierungszeit. Ein fehlerhaftes Muster ergibt ein 422 am Rand, kein
500 mitten im Crawl. Da die Semantik re.search ist, passt eine bloße
Teilzeichenkette wie "/blog/" überall in der URL — verankere mit
^/$, wenn du eine positionsgebundene Übereinstimmung brauchst.
Vorab erwähnenswert.
respect_robotswird zur Ausgabe-Filterzeit durchgesetzt, nicht zur Abrufzeit. Crawl4AI 0.8.9 hat kein natives robots-Gate, sodass imcrawl- oderhybrid-Modus eine verbotene Seite dennoch über HTTP abgerufen und dann verworfen werden kann, bevor sie die Antwort erreicht. Anders als bei perceive, worespect_robots=trueeine verbotene URL mit403ablehnt, gibt discover niemals403für eine robots-Regel zurück — es verwirft die URL still und meldet nichts.
Modes
mode |
What it does |
|---|---|
sitemap |
robots.txt-Sitemap-Einträge, geprüfte sitemap.xml (mit Index-Rekursion) und RSS/Atom-Feed-Seiten. Sofort, kein Crawl. |
crawl |
Breitensuche-Crawl ausschließlich über HTTP vom Seed aus, der <a href>-Links aus dem rohen Markup liest. |
hybrid (default) |
Die deduplizierte Vereinigung von sitemap und crawl. |
Um es klar zu sagen: Der crawl-Modus begrenzt die Anzahl der Seiten, die
er tatsächlich abruft, auf min(max_urls, 50). Ein max_urls von 1.000
löst keine tausend HTTP-GETs aus — discover hört bei 50 Seiten auf
abzurufen, doch jede Seite steuert viele <a href>-Links bei, sodass die
zurückgegebene Liste dennoch max_urls erreichen kann. Die Abruf-Obergrenze
von 50 Seiten hält die synchrone Anfrage deutlich unter dem
Anfrage-Timeout von 300 Sekunden. pages_crawled in der Antwort sagt dir
genau, wie viele GETs liefen.
Response
POST /v2/discover gibt dieses Objekt direkt zurück — es gibt keinen
asynchronen Job und keinen zweiten Aufruf.
| Field | Type | Description |
|---|---|---|
url |
string |
Die Seed-URL, die du gesendet hast. |
mode |
string |
Der ausgeführte Modus: sitemap, crawl oder hybrid. |
total |
integer |
Anzahl der URLs in urls (nach Dedup, Filterung und Limit). |
urls |
string[] |
Die deduplizierte, normalisierte, gekappte URL-Liste. Der Seed ist stets der erste Kandidat. |
pages_crawled |
integer |
Vom Crawl ausgelöste HTTP-GETs. 0 im reinen sitemap-Modus. |
truncated |
boolean |
true, wenn mehr eindeutige URLs vorhanden waren, als max_urls zuließ. |
robots_respected |
boolean |
Gibt den von dir gesendeten respect_robots-Wert zurück. |
sources |
object |
Rohe URL-Zählung pro Quelle vor Dedup/Filter, z. B. {"sitemap": 42, "crawl": 30}. Die Zählungen überschneiden sich und summieren sich über total. |
warnings |
string[] |
Nicht-fatale Hinweise: eine fehlende Sitemap, ein fehlgeschlagener Crawl, eine nicht erreichbare robots.txt. |
Die sources-Zählungen sind roh — sie sind die Anzahl der URLs, die jeder
Pfad produziert hat, bevor Normalisierung, die Same-Domain-Prüfung, deine
Filter und die Deduplizierung liefen. Sie werden sich regelmäßig zu mehr
als total summieren, weil der hybrid-Modus dieselben Seiten sowohl aus
der Sitemap als auch aus dem Crawl findet. Nutze sie, um zu sehen, welcher
Pfad die Map trägt, nicht als Zählung nach der Filterung.
Code examples
curl — default hybrid map
curl -X POST https://api.enconvert.com/v2/discover \
-H "X-API-Key: sk_live_your_private_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com"
}'
curl — sitemap only, blog pages, capped at 500
curl -X POST https://api.enconvert.com/v2/discover \
-H "X-API-Key: sk_live_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
import requests
response = requests.post(
"https://api.enconvert.com/v2/discover",
headers={"X-API-Key": "sk_live_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
const res = await fetch("https://api.enconvert.com/v2/discover", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "sk_live_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, zuerst zu discovern und dann zu rendern: Nimm
data.urls und gib sie an den perceive-Endpunkt
weiter — einzelne Aufrufe für eine Handvoll Seiten oder seinen Batch-Pfad
für die gesamte Liste.
Plan gating
/v2/discover ist durch das Plan-Flag discover_enabled geschützt und
verhält sich anders als die gezählten V2-Endpunkte. Die gezählten — darunter
perceive — verbrauchen jeweils einen monatlichen
Zähler (perceive_operations, lookup_queries, ingest_pages,
watch_checks, distill_operations). Discover hat keinen solchen Zähler:
Es läuft nur über HTTP, es speichert nichts, also gibt es kein Kontingent
pro Operation, das aufgebraucht werden könnte.
Was das in der Praxis bedeutet:
- Wenn dein Plan
discover_enabledträgt, kannst du es aufrufen. Es gibt kein monatliches discover-Kontingent, das ausgehen könnte. - Wenn nicht, gibt der Aufruf
402mit einer Upgrade-Aufforderung zurück. - Admin-Pläne umgehen das Gate vollständig.
Welche Pläne discover_enabled enthalten, wird pro Stufe festgelegt; die
aktuellen Kontingente stehen auf der Preisseite. V1-Conversion-Pläne
enthalten keine V2-Endpunkte — ein discover-Aufruf verbraucht niemals
V1-Conversion-Quota.
Error responses
| Status | Condition |
|---|---|
400 Bad Request |
URL ist nicht http(s), trägt eingebettete Zugangsdaten, hat keinen Hostnamen oder löst auf eine private, Loopback-, Link-Local- oder Cloud-Metadata-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 aktiviert. |
403 Forbidden |
/v2/discover steht nicht unter den erlaubten Endpunkten des API-Schlüssels. |
422 Unprocessable Entity |
Anfrage-Validierung fehlgeschlagen: ungültiges mode-Enum, max_urls außerhalb 1–1.000, max_depth außerhalb 1–5, mehr als 50 include/exclude-Muster, ein fehlerhaftes Regex oder eine url über 2.048 Zeichen. |
500 Internal Server Error |
URL-Discovery unerwartet fehlgeschlagen. Der Client erhält eine generische Meldung; das vollständige Detail geht nur 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 herabgestuft, und die Anfrage gibt weiterhin 200 mit
dem zurück, was gefunden wurde. Die vollständige Status-Code-Referenz steht
in der Anleitung zu Fehlercodes.
Limits
| Limit | Value |
|---|---|
| URL-Länge | 2.048 Zeichen |
max_urls |
1–1.000 (Standard 100) |
max_depth |
1–5 (Standard 2) |
include_patterns |
maximal 50 Muster |
exclude_patterns |
maximal 50 Muster |
| Im crawl-Modus abgerufene Seiten | 50 (min(max_urls, 50)) |
| Anfrage-Timeout | 300 Sekunden |
| Quota pro Operation | Keine — kein monatlicher discover-Zähler |