---
seo_title: Discover (Fase 2): API de Rastreo de Sitemaps | EnConvert
meta_desc: Beta privada, Fase 2: enumera todas las URLs de un sitio en una llamada. Análisis de sitemap más rastreo HTTP, sin renderizar página a página, en lista plana.
keywords: api de rastreo de sitemap, listar todas las urls de un sitio web con api, obtener todas las páginas de un sitio web api, rastrear un sitio web para encontrar urls api, api analizadora de sitemap, api de descubrimiento de urls de un sitio, mapeo de urls de un sitio web api, api de rastreador http sin navegador
---

# API de rastreo de sitemaps

<div class="alert alert-warning">
<strong>Beta privada.</strong> Discover ya se puede llamar hoy con tu clave de API habitual, en cualquier plan incluido Founding, y descuenta de tu cuota mensual de ops igual que cualquier otra llamada. No está anunciado ni disponible de forma general: las formas de la solicitud y de la respuesta pueden cambiar sin avisar, y no hay ningún compromiso de estabilidad ni de soporte, así que todavía no montes nada crítico encima. La hoja de ruta está en <a href="/es/docs/coming-soon">Próximamente</a>, y cada versión se anuncia en <a href="/es/changelog">el changelog</a>.
</div>

`POST /v2/discover` es una API de rastreo de sitemaps que lista las URLs
de un sitio web de la forma económica: un rastreo que prioriza HTTP más
el análisis del sitemap, con un fallback opcional de renderizado de
JavaScript para aplicaciones de una sola página (SPA). No hay captura de
pantalla, ni PDF, ni artefacto almacenado. Devuelve una lista plana y sin
duplicados de URLs junto con un recuento de la procedencia de cada
una, de forma síncrona en una sola llamada. Será la primitiva ligera
para "mapear el sitio": apúntala a un dominio, obtén de vuelta las
páginas que vale la pena procesar y luego pasa esa lista a
[el endpoint perceive](/es/docs/endpoints/perceive.md) o a un trabajo de ingesta en
modo crawl.

Aquí está la llamada mínima útil. Envía una URL, obtén su mapa de vuelta:

```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"
  }'
```

La respuesta es una lista plana de URLs con contadores de procedencia:
sin URLs firmadas, sin ID de operación, sin sondeo:

```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": []
}
```

---

## Endpoints

| Método | Ruta | 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 una fila
de operación que volver a consultar ni un trabajo que sondear, así que
no existe un endpoint `GET` complementario como sí
[lo tiene perceive](/es/docs/endpoints/perceive.md).

**Content-Type:** `application/json` en el `POST`.

---

## Autenticación

Autentícate con una clave privada en el encabezado `X-API-Key` para
llamadas de servidor a servidor. Este es el método que usan los
ejemplos siguientes.

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

Las claves públicas con un token JWT bearer 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
tokens, está en [la guía de autenticación](/es/docs/authentication.md).

Cada clave de API lleva una lista de permitidos de endpoints. Si
`/v2/discover` no está en la lista de la clave, la solicitud se
rechaza con `403` y un mensaje que indica la ruta bloqueada.

---

## Cómo funciona discover

Una solicitud se ejecuta enteramente por HTTP. El Chrome headless
singleton que impulsa [perceive](/es/docs/endpoints/perceive.md) nunca se toca.
La ruta de rastreo usa la estrategia de rastreador 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 feeds
puramente HTTP que el resto de la plataforma.

1. **Filtra la semilla.** La URL que envías se verifica contra SSRF
   antes de cualquier solicitud: se validan el esquema, las
   credenciales incrustadas, los hostnames bloqueados y la IP
   resuelta. Una URL que resuelve a una dirección privada, loopback,
   link-local o de metadatos de nube se rechaza con `400`. La semilla
   siempre se incluye como la primera entrada en su propio mapa.
2. **Recopila desde sitemaps.** En modo `sitemap` o `hybrid`, discover
   lee `robots.txt` y obtiene las URLs `Sitemap:` que declara, sondea
   `sitemap.xml` (recurriendo en los hijos `<sitemapindex>`) y extrae
   páginas de feeds RSS/Atom. Ejecuta estos sondeos tanto contra el host
   que enviaste como contra el dominio registrable (apex) del sitio, de
   modo que un sitemap publicado solo en el apex se encuentra igualmente
   desde una semilla en un subdominio o en una ruta profunda. Un sitemap
   ausente o roto se convierte en una advertencia, no en un error.
3. **Rastrea por HTTP.** En modo `crawl` o `hybrid`, discover ejecuta
   un rastreo en anchura desde la semilla hasta `max_depth`,
   recolectando los enlaces `<a href>` del HTML crudo de cada página
   obtenida. Cada enlace seguido se filtra contra SSRF antes de
   solicitarlo.
4. **Normaliza y filtra.** La lista combinada en bruto se canonicaliza
   (se eliminan los fragmentos y los parámetros de seguimiento, se
   quitan los puertos por defecto, se ordenan las claves de consulta),
   y luego pasa por la verificación de mismo dominio, tus patrones
   regex de inclusión y exclusión, el filtro de `robots.txt`, la
   deduplicación y finalmente el límite de `max_urls`.

En la ruta solo por HTTP, una aplicación de una sola página (SPA)
renderizada en el cliente devuelve solo su shell HTML en modo `crawl`,
típicamente la semilla más cero o una URL. El fallback opcional
`render_js` (consulta [renderizado de JavaScript](#javascript-rendering))
cierra esa brecha: en su valor por defecto `"auto"`, discover renderiza
la página una vez en el navegador y recolecta los enlaces que esta
inyecta en tiempo de ejecución siempre que el rastreo por HTTP devuelve
solo un shell. El modo `sitemap` sigue siendo una ruta rápida y sin
navegador para las SPAs con buen SEO, que normalmente publican un
sitemap.

---

## Parámetros de la solicitud

### Básicos

| Parámetro | Tipo | Valor por defecto | Descripción |
|-----------|------|---------|-------------|
| `url` | `string` | ninguno | 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](#modes). |
| `max_urls` | `integer` | `100` | Número máximo de URLs devueltas. 1–1,000. La lista se limita aquí y `truncated` indica 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`, también se conservan los enlaces fuera de ese host descubiertos durante el rastreo. |
| `render_js` | `string` | `"auto"` | Descubrimiento con renderizado en el navegador para sitios JavaScript/SPA (solo `crawl`/`hybrid`). `auto` renderiza solo cuando el rastreo por HTTP devuelve un shell; `always` lo fuerza; `never` se mantiene solo por HTTP. Consulta [renderizado de JavaScript](#javascript-rendering). |

### Filtrado

| Parámetro | Tipo | Valor por defecto | Descripción |
|-----------|------|---------|-------------|
| `include_patterns` | `string[]` | `[]` | Lista de permitidos con regex de Python (semántica `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[]` | `[]` | Lista de denegados con 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 eliminan de la lista devuelta. |

Los patrones son expresiones regulares de Python reales, compiladas en
el momento de la validación. Un patrón mal formado da un `422` en el
borde, no un `500` a mitad del rastreo. Como la semántica es
`re.search`, una subcadena simple como `"/blog/"` coincide en cualquier
parte de la URL: usa `^`/`$` como anclas si necesitas una coincidencia
posicional.

> **Vale la pena aclarar esto de entrada.** `respect_robots` se aplica
> en el momento del filtrado de salida, no en el momento de la
> solicitud. Crawl4AI 0.8.9 no tiene una validación nativa de robots,
> así que en modo `crawl` o `hybrid` una página no permitida puede
> igualmente obtenerse por HTTP y descartarse después, antes de llegar
> a la respuesta. A diferencia de [perceive](/es/docs/endpoints/perceive.md),
> donde `respect_robots=true` rechaza una URL no permitida con `403`,
> discover nunca devuelve `403` por una regla de robots: descarta la
> URL en silencio y no reporta nada.

### Modos {: #modes }

| `mode` | Qué hace |
|--------|--------------|
| `sitemap` | Entradas de sitemap de `robots.txt`, sondeo de `sitemap.xml` (con recursión de índice) y páginas de feeds RSS/Atom. Instantáneo, sin rastreo. |
| `crawl` | Rastreo en anchura solo por HTTP desde la semilla, recolectando enlaces `<a href>` del markup crudo. |
| `hybrid` (por defecto) | La unión sin duplicados de `sitemap` y `crawl`. |

El modo `crawl` solicita hasta tu `max_urls` completo (hasta 1,000), así
que un sitio grande se enumera en una sola pasada. El modo crawl es solo
por HTTP (sin navegador), así que sigue siendo rápido. Los operadores
pueden bajar el techo con la variable de entorno
`DISCOVER_CRAWL_MAX_PAGES` si un rastreo muy grande alguna vez necesita
acotarse. `pages_crawled` en la respuesta te indica exactamente cuántos
GETs se ejecutaron.

La obtención de sitemaps maneja **sitemaps comprimidos con gzip**
(`sitemap.xml.gz` y cualquier sitemap servido como `application/gzip`),
y envía encabezados de navegador realistas, de modo que los sitios
detrás de un WAF tienen muchas más probabilidades de devolver su sitemap
en lugar de un `403`/`503`.

### Renderizado de JavaScript {: #javascript-rendering }

Los modos `crawl` y `hybrid` leen el HTML crudo que devuelve un servidor,
así que una aplicación de una sola página (SPA) renderizada en el cliente
que construye sus enlaces en el navegador es invisible para ellos.
`render_js` habilita un fallback acotado en el navegador que cierra esa
brecha:

| `render_js` | Qué hace |
|-------------|--------------|
| `auto` (por defecto) | Ejecuta primero el rastreo por HTTP; renderiza en el navegador solo cuando este devuelve un shell vacío (una URL o ninguna), y luego recolecta los enlaces inyectados por el cliente. |
| `always` | Ejecuta siempre el rastreo con renderizado en el navegador. |
| `never` | Se mantiene estrictamente solo por HTTP: el comportamiento preexistente. |

El fallback usa el mismo Chrome headless compartido que
[perceive](/es/docs/endpoints/perceive.md) y está limitado a unas pocas páginas
para que la llamada se mantenga dentro del timeout de la solicitud. Los
enlaces que encuentra se contabilizan bajo la clave `crawl_js` en
`sources`. Se aplica solo a los modos `crawl` y `hybrid`: el modo
`sitemap` ya es sin navegador y no se ve afectado.

---

## Respuesta

`POST /v2/discover` devuelve este objeto directamente: no hay un
trabajo asíncrono ni una segunda llamada.

| Campo | Tipo | 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` (después de la deduplicación, el filtrado y el límite). |
| `urls` | `string[]` | La lista de URLs sin duplicados, normalizada y limitada. La semilla siempre es el primer candidato. |
| `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 permitía `max_urls`. |
| `robots_respected` | `boolean` | Refleja el valor de `respect_robots` que enviaste. |
| `sources` | `object` | Recuento de URLs en bruto por fuente antes de la deduplicación/filtrado, p. ej. `{"sitemap": 42, "crawl": 30}`. Las claves incluyen `sitemap`, `crawl`, `sitemap_apex` (sitemaps encontrados en el dominio apex) y `crawl_js` (enlaces del fallback de renderizado de JavaScript). 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` inaccesible. |

Los recuentos de `sources` son en bruto: son el número de URLs que
produjo cada ruta antes de que se ejecutaran la normalización, la
verificación de mismo dominio, tus filtros y la deduplicación.
Habitualmente 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á aportando el mapa, no como un
recuento posterior al filtrado.

---

## Ejemplos de código

### curl: mapa hybrid por defecto

```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: solo sitemap, páginas de blog, limitado a 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));
```

Un patrón común es primero descubrir y luego renderizar: toma
`data.urls` y pásalas [al endpoint perceive](/es/docs/endpoints/perceive.md):
llamadas individuales para un puñado de páginas, o su ruta por lotes
(batch) para la lista completa.

---

## Respuestas de error

| Estado | Condición |
|--------|-----------|
| `400 Bad Request` | La URL no es `http(s)`, lleva credenciales incrustadas, no tiene hostname, o resuelve a una dirección privada, loopback, link-local o de metadatos de nube (protección SSRF). |
| `401 Unauthorized` | Falta la clave de API / token JWT o es inválido. |
| `402 Payment Required` | Discover no está habilitado en tu plan actual, o tu cuota mensual de ops está agotada. Cada llamada a `/v2/discover` factura una op. |
| `403 Forbidden` | `/v2/discover` no está en los endpoints permitidos de la clave de API. |
| `422 Unprocessable Entity` | Falló la validación de la solicitud: enum `mode` inválido, `max_urls` fuera de 1–1,000, `max_depth` fuera de 1–5, más de 50 patrones de inclusión/exclusión, una regex mal formada, o una `url` de más de 2,048 caracteres. |
| `500 Internal Server Error` | El descubrimiento de URLs falló de forma inesperada. El cliente recibe un mensaje genérico; el detalle completo va solo a los logs del servidor. |

Ten en cuenta que un fallo al obtener el sitemap o un error de rastreo
no producen un estado de error: se degradan a entradas en el arreglo
`warnings`, y la solicitud igual 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](/es/docs/reference/errors.md).

---

## Límites

| Límite | Valor |
|-------|-------|
| Longitud de la URL | 2,048 caracteres |
| `max_urls` | 1–1,000 (por defecto 100) |
| `max_depth` | 1–5 (por defecto 2) |
| `include_patterns` | Máximo 50 patrones |
| `exclude_patterns` | Máximo 50 patrones |
| Páginas solicitadas en modo crawl | hasta `max_urls` (máx. 1,000; se baja con `DISCOVER_CRAWL_MAX_PAGES`) |
| Timeout de la solicitud | 300 segundos |
| Ops por llamada | 1, facturada contra la cuota mensual unificada |

---

## Preguntas frecuentes

### ¿Cómo listo todas las URLs de un sitio web con una API?

Envía `POST /v2/discover` con la URL del sitio. El modo `hybrid` por defecto combina el análisis del sitemap (entradas de `robots.txt`, `sitemap.xml` con recursión de índice, feeds RSS/Atom) con un rastreo en anchura por HTTP, y devuelve hasta `max_urls` (1–1,000, por defecto 100) URLs sin duplicados en una sola respuesta síncrona.

### ¿El endpoint discover usa un navegador headless o renderiza JavaScript?

Por defecto se ejecuta por HTTP y solo renderiza cuando es necesario. La opción `render_js` controla esto: en el modo por defecto `"auto"`, discover se mantiene solo por HTTP y renderiza una página en el navegador justo cuando el rastreo por HTTP devuelve un shell JavaScript vacío, recolectando los enlaces inyectados por el cliente; `"always"` fuerza el rastreo en el navegador y `"never"` lo mantiene estrictamente solo por HTTP. El modo `sitemap` sigue siendo una opción rápida y sin navegador para las SPAs con buen SEO.

### ¿Cuántas páginas solicita realmente el modo crawl?

El modo crawl solicita hasta tu `max_urls` completo (hasta 1,000). Se mantiene solo por HTTP, así que sigue siendo rápido; los operadores pueden bajar el techo con la variable de entorno `DISCOVER_CRAWL_MAX_PAGES`. Cada página solicitada aporta además muchos enlaces. El campo `pages_crawled` de la respuesta indica exactamente cuántos GETs HTTP se ejecutaron.

### ¿Puedo filtrar qué URLs se devuelven?

Sí: `include_patterns` y `exclude_patterns` aceptan hasta 50 regex de Python cada uno (semántica `re.search`, no glob), y `respect_robots: true` elimina de la lista devuelta las URLs no permitidas por el `robots.txt` del sitio.
