Discover
POST /v2/discover enumera gli URL di un sito senza renderizzare una
singola pagina. Niente browser, niente screenshot, niente PDF, nessun
artefatto archiviato — solo una scansione HTTP-only più il parsing della
sitemap che restituisce un elenco piatto di URL e un conteggio di dove
ciascuno è stato trovato. È la primitiva economica per "mappare il sito":
puntala su un dominio, ottieni le pagine che vale la pena elaborare, poi
passa quell'elenco all'endpoint perceive o a un
job di ingest in crawl-mode.
Ecco la chiamata utile più piccola. Invia un URL, ottieni la sua mappa:
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"
}'
La risposta è un semplice elenco di URL con contatori di provenienza — nessun URL firmato, nessun ID operazione, nessun 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": []
}
Da segnalare subito.
/v2/discoverè un endpoint V2, vincolato dal flag di pianodiscover_enabled. A differenza della maggior parte degli endpoint V2 non ha un contatore di quota per operazione — è economico (niente browser, niente storage), quindi non c'è alcun contatore mensile da esaurire. Se il tuo piano non include V2 discover, la chiamata restituisce402. Consulta la pagina dei prezzi per sapere quali piani lo includono.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v2/discover |
Mappa gli URL di un sito solo via HTTP e restituisce un elenco piatto con i conteggi per sorgente. |
/v2/discover espone un singolo path. È stateless — non c'è alcuna riga
di operazione da rileggere e nessun job da interrogare, quindi non esiste
un endpoint GET corrispondente come perceive ne ha uno.
Content-Type: application/json sul POST.
Authentication
Autenticati con una chiave privata nell'header X-API-Key per le chiamate
server-to-server. Questo è il percorso usato dagli esempi qui sotto.
X-API-Key: sk_live_your_private_key
Funzionano anche le chiavi pubbliche con un token bearer JWT, usando lo
stesso flusso di ogni altro endpoint — genera un token con la tua chiave
pk_, poi invialo come Authorization: Bearer <token>. Il flusso
completo, incluso il domain locking e il refresh del token, è descritto
ne la guida all'autenticazione.
Ogni chiave API porta con sé un'allowlist di endpoint consentiti. Se
/v2/discover non è nell'elenco della chiave, la richiesta viene rifiutata
con 403 e un messaggio che indica il path bloccato.
How discover works
Una richiesta gira interamente su HTTP. Il singleton di Chrome headless che
alimenta perceive non viene mai toccato — il
percorso di crawl usa la strategia HTTP crawler di Crawl4AI (un GET httpx
più un parse dei link lxml per pagina), e il percorso della sitemap
riutilizza gli stessi helper puramente HTTP per sitemap e feed del resto
della piattaforma.
- Verifica il seed. L'URL che invii viene controllato per SSRF prima
di qualsiasi fetch: schema, credenziali incorporate, hostname bloccati e
l'IP risolto vengono tutti validati. Un URL che si risolve in un
indirizzo privato, loopback, link-local o di cloud-metadata viene
rifiutato con
400. Il seed è sempre incluso come prima voce nella sua stessa mappa. - Raccogli dalle sitemap. In modalità
sitemapohybrid, discover leggerobots.txt, sondasitemap.xml(ricorrendo nei figli<sitemapindex>) e recupera le pagine dei feed RSS/Atom. Una sitemap mancante o danneggiata diventa un warning, non un errore. - Scansiona via HTTP. In modalità
crawlohybrid, discover esegue una scansione breadth-first dal seed fino amax_depth, raccogliendo i link<a href>dall'HTML grezzo di ogni pagina recuperata. Ogni link seguito viene verificato per SSRF prima di essere recuperato. - Normalizza e filtra. L'elenco grezzo combinato viene canonicalizzato
(frammenti e parametri di tracking rimossi, porte di default rimosse,
chiavi di query ordinate), poi passato attraverso il controllo
stesso-dominio, i tuoi pattern regex di include ed exclude, il filtro
robots.txt, la de-duplicazione e infine il limitemax_urls.
Poiché non c'è alcun render, una single-page app renderizzata lato client
restituisce solo il suo guscio HTML in modalità crawl — tipicamente il
seed più zero o un URL. È il comportamento corretto per un endpoint senza
browser, non un fallimento. Per le SPA, usa la modalità sitemap (la
maggior parte delle SPA attente alla SEO pubblica una sitemap) oppure
ripiega su chiamate perceive per singolo URL.
Request parameters
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string |
— | Il sito da mappare. Deve iniziare con http:// o https://. Massimo 2.048 caratteri. Obbligatorio. |
mode |
string |
"hybrid" |
sitemap, crawl o hybrid. Vedi Modes. |
max_urls |
integer |
100 |
Numero massimo di URL restituiti. 1–1.000. L'elenco viene limitato qui e truncated segnala se ne esistevano altri. |
max_depth |
integer |
2 |
Profondità di scansione dal seed, in modalità crawl/hybrid. 1–5. |
same_domain_only |
boolean |
true |
Mantiene solo gli URL sull'host del seed. Quando è false, vengono mantenuti anche i link fuori host scoperti durante la scansione. |
Filtering
| Parameter | Type | Default | Description |
|---|---|---|---|
include_patterns |
string[] |
[] |
Allowlist di regex Python (semantica re.search, non glob). Un URL deve corrispondere ad almeno uno per essere mantenuto. Vuoto significa consenti tutto. Massimo 50 pattern. |
exclude_patterns |
string[] |
[] |
Denylist di regex Python. Un URL che corrisponde a qualsiasi pattern viene scartato. Applicato dopo include_patterns. Massimo 50 pattern. |
respect_robots |
boolean |
false |
Quando è true, gli URL non consentiti dal robots.txt del sito vengono scartati dall'elenco restituito. |
I pattern sono vere espressioni regolari Python, compilate al momento
della validazione. Un pattern malformato è un 422 al confine, non un
500 a metà scansione. Poiché la semantica è re.search, una semplice
sottostringa come "/blog/" corrisponde in qualsiasi punto dell'URL —
ancora con ^/$ se ti serve una corrispondenza posizionale.
Da segnalare subito.
respect_robotsviene applicato al momento del filtro di output, non al momento del fetch. Crawl4AI 0.8.9 non ha un gate robots nativo, quindi in modalitàcrawlohybriduna pagina non consentita può comunque essere recuperata via HTTP e poi scartata prima che raggiunga la risposta. A differenza di perceive, doverespect_robots=truerifiuta un URL non consentito con403, discover non restituisce mai403per una regola robots — scarta silenziosamente l'URL e non segnala nulla.
Modes
mode |
What it does |
|---|---|
sitemap |
Voci sitemap del robots.txt, sitemap.xml sondato (con ricorsione sull'indice) e pagine dei feed RSS/Atom. Istantaneo, nessuna scansione. |
crawl |
Scansione breadth-first solo via HTTP dal seed, raccogliendo i link <a href> dal markup grezzo. |
hybrid (default) |
L'unione de-duplicata di sitemap e crawl. |
Per essere chiari: la modalità crawl limita il numero di pagine che
recupera effettivamente a min(max_urls, 50). Un max_urls di 1.000 non
emette mille GET HTTP — discover smette di recuperare a 50 pagine, ma ogni
pagina contribuisce con molti link <a href>, quindi l'elenco restituito
può comunque raggiungere max_urls. Il tetto di 50 pagine recuperate
mantiene la richiesta sincrona ben al di sotto del timeout di richiesta di
300 secondi. pages_crawled nella risposta ti dice esattamente quanti GET
sono stati eseguiti.
Response
POST /v2/discover restituisce questo oggetto direttamente — non c'è alcun
job asincrono né una seconda chiamata.
| Field | Type | Description |
|---|---|---|
url |
string |
L'URL seed che hai inviato. |
mode |
string |
La modalità eseguita: sitemap, crawl o hybrid. |
total |
integer |
Numero di URL in urls (dopo dedup, filtraggio e limite). |
urls |
string[] |
L'elenco di URL de-duplicato, normalizzato e limitato. Il seed è sempre il primo candidato. |
pages_crawled |
integer |
GET HTTP emessi dalla scansione. 0 in modalità sitemap pura. |
truncated |
boolean |
true quando esistevano più URL univoci di quanti max_urls ne consentisse. |
robots_respected |
boolean |
Restituisce il valore respect_robots che hai inviato. |
sources |
object |
Conteggio grezzo di URL per sorgente prima di dedup/filtro, es. {"sitemap": 42, "crawl": 30}. I conteggi si sovrappongono e sommano oltre total. |
warnings |
string[] |
Note non fatali: una sitemap mancante, una scansione fallita, un robots.txt irraggiungibile. |
I conteggi sources sono grezzi — sono il numero di URL prodotti da
ciascun percorso prima che venissero eseguiti normalizzazione, controllo
stesso-dominio, i tuoi filtri e la de-duplicazione. Sommeranno
abitualmente a un valore maggiore di total, perché la modalità hybrid
trova le stesse pagine sia dalla sitemap sia dalla scansione. Usali per
vedere quale percorso sta sostenendo la mappa, non come conteggio
post-filtro.
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));
Un pattern comune è prima fare discover, poi renderizzare: prendi
data.urls e passali all'endpoint perceive —
chiamate singole per una manciata di pagine, oppure il suo percorso batch
per l'intero elenco.
Plan gating
/v2/discover è vincolato dal flag di piano discover_enabled e si
comporta diversamente dagli endpoint V2 misurati. Quelli misurati —
perceive tra essi — consumano ciascuno un contatore
mensile (perceive_operations, lookup_queries, ingest_pages,
watch_checks, distill_operations). Discover non ha un contatore del
genere: è solo HTTP, non archivia nulla, quindi non c'è alcun contatore per
operazione da esaurire.
Cosa significa in pratica:
- Se il tuo piano include
discover_enabled, puoi chiamarlo. Non c'è alcuna quota mensile di discover che possa esaurirsi. - Se non lo include, la chiamata restituisce
402con un invito all'upgrade. - I piani Admin bypassano del tutto il gate.
Quali piani includono discover_enabled è impostato per tier; le quote
correnti sono su la pagina dei prezzi. I piani di conversione
V1 non includono gli endpoint V2 — una chiamata discover non consuma mai la
quota di conversione V1.
Error responses
| Status | Condition |
|---|---|
400 Bad Request |
L'URL non è http(s), porta credenziali incorporate, non ha hostname o si risolve in un indirizzo privato, loopback, link-local o di cloud-metadata (protezione SSRF). |
401 Unauthorized |
Chiave API / token JWT mancante o non valido. |
402 Payment Required |
Discover non è abilitato sul tuo piano attuale. |
403 Forbidden |
/v2/discover non è tra gli endpoint consentiti della chiave API. |
422 Unprocessable Entity |
Validazione della richiesta fallita: enum mode errato, max_urls fuori da 1–1.000, max_depth fuori da 1–5, più di 50 pattern di include/exclude, una regex malformata o un url oltre i 2.048 caratteri. |
500 Internal Server Error |
La discovery degli URL è fallita inaspettatamente. Il client riceve un messaggio generico; il dettaglio completo va solo nei log del server. |
Nota che un fetch di sitemap fallito o un guasto nella scansione non
producono uno status di errore — questi degradano in voci nell'array
warnings, e la richiesta restituisce comunque 200 con tutto ciò che è
stato trovato. Il riferimento completo dei codici di stato è ne la guida
ai codici di errore.
Limits
| Limit | Value |
|---|---|
| URL length | 2.048 caratteri |
max_urls |
1–1.000 (default 100) |
max_depth |
1–5 (default 2) |
include_patterns |
50 pattern max |
exclude_patterns |
50 pattern max |
| Pages fetched in crawl mode | 50 (min(max_urls, 50)) |
| Request timeout | 300 secondi |
| Per-operation quota | Nessuna — nessun contatore mensile di discover |