Discover
POST /v2/discover enumera las URLs de un sitio sin renderizar una sola
página. No hay navegador, ni captura de pantalla, ni PDF, ni artefacto
almacenado — solo un rastreo exclusivamente por HTTP más el análisis del
sitemap que devuelve una lista plana de URLs y un recuento de la
procedencia de cada una. Es la primitiva económica de "mapear el sitio":
apúntalo a un dominio, recupera las páginas que vale la pena procesar y
luego pasa esa lista al endpoint perceive
o a un trabajo de ingesta en modo crawl.
Aquí tienes la llamada útil más pequeña. Envía una URL, recupera su mapa:
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 respuesta es una lista plana de URLs con contadores de procedencia — sin URLs firmadas, sin ID de operación, sin sondeo:
{
"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": []
}
Conviene advertirlo de entrada.
/v2/discoveres un endpoint de V2, controlado por el flag de plandiscover_enabled. A diferencia de la mayoría de los endpoints de V2, no tiene un contador de cuota por operación — es económico (sin navegador, sin almacenamiento), así que no hay un medidor mensual que agotar. Si tu plan no incluye discover de V2, la llamada devuelve402. Consulta la página de precios para ver qué planes lo incluyen.
Endpoints
| Method | Path | Propósito |
|---|---|---|
POST |
/v2/discover |
Mapea las URLs de un sitio solo por HTTP y devuelve una lista plana con recuentos por fuente. |
/v2/discover expone una única ruta. No tiene estado — no hay fila de
operación que volver a consultar ni trabajo que sondear, así que no hay un
endpoint GET complementario como el que tiene perceive.
Content-Type: application/json en el POST.
Autenticación
Autentícate con una clave privada en la cabecera X-API-Key para las
llamadas servidor a servidor. Esta es la ruta que usan los ejemplos de
abajo.
X-API-Key: sk_live_your_private_key
Las claves públicas con un token bearer JWT también funcionan, usando el
mismo flujo que el resto de los endpoints — genera un token con tu clave
pk_ y luego envíalo como Authorization: Bearer <token>. El flujo
completo, incluido el bloqueo de dominio y la renovación de token, está en
la guía de autenticación.
Cada clave de API lleva una lista de endpoints permitidos. Si /v2/discover
no está en la lista de la clave, la solicitud se rechaza con 403 y un
mensaje que nombra la ruta bloqueada.
Cómo funciona discover
Una solicitud se ejecuta enteramente por HTTP. El Chrome headless único
que impulsa perceive nunca se toca — la ruta de
rastreo usa la estrategia de crawler HTTP de Crawl4AI (un GET con httpx
más un análisis de enlaces con lxml por página), y la ruta del sitemap
reutiliza los mismos helpers de sitemap y feed exclusivamente por HTTP que
el resto de la plataforma.
- Filtrar la semilla. La URL que envías se comprueba contra SSRF antes
de cualquier fetch: el esquema, las credenciales embebidas, los
hostnames bloqueados y la IP resuelta se validan todos. Una URL que
resuelve a una dirección privada, de loopback, link-local o de
metadatos de la nube se rechaza con
400. La semilla siempre se incluye como la primera entrada de su propio mapa. - Recolectar de los sitemaps. En modo
sitemapohybrid, discover leerobots.txt, sondeasitemap.xml(recursando en los hijos<sitemapindex>) y extrae las páginas de feeds RSS/Atom. Un sitemap ausente o defectuoso se convierte en una advertencia, no en un error. - Rastrear por HTTP. En modo
crawlohybrid, discover ejecuta un rastreo en anchura desde la semilla hastamax_depth, recolectando los enlaces<a href>del HTML crudo de cada página obtenida. Cada enlace seguido se filtra contra SSRF antes de obtenerse. - Normalizar y filtrar. La lista cruda combinada se canonicaliza (se
eliminan los fragmentos y los parámetros de seguimiento, se quitan los
puertos por defecto, se ordenan las claves de la query), luego se pasa
por la comprobación de mismo dominio, tus patrones regex de inclusión y
exclusión, el filtro de
robots.txt, la deduplicación y, por último, el límitemax_urls.
Como no hay renderizado, una single-page app renderizada en el cliente
devuelve solo su cascarón HTML en modo crawl — normalmente la semilla
más cero o una URL. Ese es el comportamiento correcto de un endpoint sin
navegador, no un fallo. Para SPAs, usa el modo sitemap (la mayoría de las
SPAs conscientes del SEO publican un sitemap) o recurre a llamadas
perceive por URL.
Parámetros de la solicitud
Núcleo
| Parameter | Type | Default | Descripción |
|---|---|---|---|
url |
string |
— | El sitio a mapear. Debe empezar con http:// o https://. Máximo 2.048 caracteres. Obligatorio. |
mode |
string |
"hybrid" |
sitemap, crawl o hybrid. Consulta Modos. |
max_urls |
integer |
100 |
Máximo de URLs devueltas. 1–1.000. La lista se recorta aquí y truncated se marca si existían más. |
max_depth |
integer |
2 |
Profundidad de rastreo desde la semilla, en modo crawl/hybrid. 1–5. |
same_domain_only |
boolean |
true |
Conserva solo las URLs del host de la semilla. Cuando es false, los enlaces fuera del host descubiertos durante el rastreo también se conservan. |
Filtrado
| Parameter | Type | Default | Descripción |
|---|---|---|---|
include_patterns |
string[] |
[] |
Allowlist de regex de Python (semántica de re.search, no glob). Una URL debe coincidir con al menos uno para conservarse. Vacío significa permitir todo. Máximo 50 patrones. |
exclude_patterns |
string[] |
[] |
Denylist de regex de Python. Una URL que coincida con cualquier patrón se descarta. Se aplica después de include_patterns. Máximo 50 patrones. |
respect_robots |
boolean |
false |
Cuando es true, las URLs no permitidas por el robots.txt del sitio se descartan de la lista devuelta. |
Los patrones son expresiones regulares reales de Python, compiladas en el
momento de la validación. Un patrón mal formado es un 422 en el borde, no
un 500 a mitad del rastreo. Como la semántica es re.search, una
subcadena pelada como "/blog/" coincide en cualquier parte de la URL —
ánclala con ^/$ si necesitas una coincidencia posicional.
Conviene advertirlo de entrada.
respect_robotsse aplica en el momento del filtrado de salida, no en el momento del fetch. Crawl4AI 0.8.9 no tiene una compuerta nativa de robots, así que en modocrawlohybriduna página no permitida puede aun así obtenerse por HTTP y luego descartarse antes de llegar a la respuesta. A diferencia de perceive, donderespect_robots=truerechaza una URL no permitida con403, discover nunca devuelve403por una regla de robots — descarta la URL en silencio y no informa nada.
Modos
mode |
Qué hace |
|---|---|
sitemap |
Entradas de sitemap del robots.txt, sitemap.xml sondeado (con recursión de índice) y páginas de feeds RSS/Atom. Instantáneo, sin rastreo. |
crawl |
Rastreo en anchura exclusivamente por HTTP desde la semilla, recolectando enlaces <a href> del marcado crudo. |
hybrid (por defecto) |
La unión deduplicada de sitemap y crawl. |
Para ser directos: el modo crawl limita el número de páginas que
realmente obtiene a min(max_urls, 50). Un max_urls de 1.000 no emite mil
GETs HTTP — discover deja de obtener a las 50 páginas, pero cada página
aporta muchos enlaces <a href>, así que la lista devuelta puede aun así
alcanzar max_urls. El techo de 50 páginas obtenidas mantiene la solicitud
síncrona muy por debajo del timeout de solicitud de 300 segundos.
pages_crawled en la respuesta te dice exactamente cuántos GETs se
ejecutaron.
Respuesta
POST /v2/discover devuelve este objeto directamente — no hay trabajo
asíncrono ni segunda llamada.
| Field | Type | Descripción |
|---|---|---|
url |
string |
La URL semilla que enviaste. |
mode |
string |
El modo que se ejecutó: sitemap, crawl o hybrid. |
total |
integer |
Número de URLs en urls (tras dedup, filtrado y el límite). |
urls |
string[] |
La lista de URLs deduplicada, normalizada y recortada. La semilla siempre es la primera candidata. |
pages_crawled |
integer |
GETs HTTP emitidos por el rastreo. 0 en modo sitemap puro. |
truncated |
boolean |
true cuando existían más URLs únicas de las que max_urls permitía. |
robots_respected |
boolean |
Refleja el valor respect_robots que enviaste. |
sources |
object |
Recuento crudo de URLs por fuente antes del dedup/filtro, p. ej. {"sitemap": 42, "crawl": 30}. Los recuentos se solapan y suman más que total. |
warnings |
string[] |
Notas no fatales: un sitemap ausente, un rastreo que falló, un robots.txt inalcanzable. |
Los recuentos de sources son crudos — son el número de URLs que produjo
cada ruta antes de ejecutarse la normalización, la comprobación de mismo
dominio, tus filtros y la deduplicación. Rutinariamente sumarán más que
total, porque el modo hybrid encuentra las mismas páginas tanto desde el
sitemap como desde el rastreo. Úsalos para ver qué ruta está cargando el
mapa, no como un conteo posterior al filtrado.
Ejemplos de código
curl — mapa hybrid por defecto
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 — solo sitemap, páginas de blog, limitado a 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 patrón habitual es descubrir primero y luego renderizar: toma
data.urls y pásalas al endpoint perceive — llamadas
individuales para un puñado de páginas, o su ruta batch para la lista
entera.
Control por plan
/v2/discover está controlado por el flag de plan discover_enabled y se
comporta de forma distinta a los endpoints medidos de V2. Los medidos —
perceive entre ellos — consumen cada uno un contador
mensual (perceive_operations, lookup_queries, ingest_pages,
watch_checks, distill_operations). Discover no tiene tal contador: es
exclusivamente HTTP, no almacena nada, así que no hay un medidor por
operación que agotar.
Lo que eso significa en la práctica:
- Si tu plan lleva
discover_enabled, puedes llamarlo. No hay una asignación mensual de discover que se pueda agotar. - Si tu plan no lo lleva, la llamada devuelve
402con un aviso de mejora. - Los planes de administrador omiten la compuerta por completo.
Qué planes incluyen discover_enabled se establece por nivel; las
asignaciones actuales están en la página de precios. Los
planes de conversión de V1 no incluyen endpoints de V2 — una llamada a
discover nunca consume la cuota de conversión de V1.
Respuestas de error
| Status | Condición |
|---|---|
400 Bad Request |
La URL no es http(s), lleva credenciales embebidas, no tiene hostname o resuelve a una dirección privada, de loopback, link-local o de metadatos de la nube (protección SSRF). |
401 Unauthorized |
Clave de API / token JWT ausente o inválido. |
402 Payment Required |
Discover no está habilitado en tu plan actual. |
403 Forbidden |
/v2/discover no está entre los endpoints permitidos de la clave de API. |
422 Unprocessable Entity |
La validación de la solicitud falló: enum mode incorrecto, max_urls fuera de 1–1.000, max_depth fuera de 1–5, más de 50 patrones de inclusión/exclusión, un regex mal formado o una url de más de 2.048 caracteres. |
500 Internal Server Error |
El descubrimiento de URLs falló inesperadamente. El cliente recibe un mensaje genérico; el detalle completo va solo a los logs del servidor. |
Ten en cuenta que un fetch de sitemap fallido o un fallo de rastreo no
produce un estado de error — esos se degradan a entradas en el array
warnings, y la solicitud aun así devuelve 200 con lo que se haya
encontrado. La referencia completa de códigos de estado está en la guía de
códigos de error.
Límites
| Límite | Valor |
|---|---|
| Longitud de URL | 2.048 caracteres |
max_urls |
1–1.000 (por defecto 100) |
max_depth |
1–5 (por defecto 2) |
include_patterns |
50 patrones máximo |
exclude_patterns |
50 patrones máximo |
| Páginas obtenidas en modo crawl | 50 (min(max_urls, 50)) |
| Timeout de solicitud | 300 segundos |
| Cuota por operación | Ninguna — sin contador mensual de discover |