---
seo_title: Discover (Phase 2) : API de crawl de sitemap | EnConvert
meta_desc: Bêta privée, Phase 2 : énumérez toutes les URLs d'un site en un appel. Parsing de sitemap et crawl HTTP, sans rendu par page, renvoyés en liste plate.
keywords: lister toutes les urls d'un site api, crawler un sitemap xml api, récupérer toutes les pages d'un site api, api de découverte d'urls, mapper un site web api, crawl http sans navigateur api, extraire les liens d'un site web api, parser un sitemap api
---

# API de crawl de sitemap

<div class="alert alert-warning">
<strong>Bêta privée.</strong> Discover est appelable dès aujourd'hui avec votre clé d'API habituelle, sur tous les forfaits y compris Founding, et décompte votre quota mensuel d'ops comme n'importe quel autre appel. Ce n'est ni annoncé ni disponible de façon générale : les formes de requête et de réponse peuvent changer sans préavis, et il n'y a aucun engagement de stabilité ni de support, donc ne construisez rien de critique dessus pour l'instant. La feuille de route est sur <a href="/fr/docs/coming-soon">Bientôt disponible</a>, et chaque publication est annoncée dans <a href="/fr/changelog">le changelog</a>.
</div>

`POST /v2/discover` est une API de crawl de sitemap qui liste les URLs
d'un site à moindre coût : un crawl en priorité HTTP combiné à un
parsing de sitemap, avec un repli de rendu JavaScript optionnel pour les
applications monopage (SPA). Il n'y a ni capture d'écran, ni PDF, ni
artefact stocké. Elle renvoie une liste plate et dédupliquée
d'URLs ainsi qu'un décompte de la provenance de chacune, de façon
synchrone en un seul appel. Ce sera la primitive légère « cartographier
le site » : pointez-la vers un domaine, récupérez les pages qui
méritent d'être traitées, puis injectez cette liste dans
[l'endpoint perceive](/fr/docs/endpoints/perceive.md) ou dans un job d'ingestion en
mode crawl.

Voici l'appel minimal utile. Envoyez une URL, récupérez sa carte :

```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 réponse est une simple liste d'URLs avec des compteurs de
provenance. Il n'y a pas d'URLs signées, pas d'ID d'opération et pas de
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": []
}
```

---

## Endpoints

| Méthode | Chemin | Objectif |
|--------|------|---------|
| `POST` | `/v2/discover` | Cartographie les URLs d'un site en HTTP uniquement et renvoie une liste plate avec des compteurs par source. |

`/v2/discover` expose un seul chemin. Il est sans état : il n'y a
aucune ligne d'opération à récupérer ni aucun job à interroger, donc il
n'existe pas d'endpoint `GET` compagnon comme
[c'est le cas pour perceive](/fr/docs/endpoints/perceive.md).

**Content-Type :** `application/json` sur le `POST`.

---

## Authentification

Authentifiez-vous avec une clé privée dans l'en-tête `X-API-Key` pour
les appels serveur à serveur. C'est la voie utilisée par les exemples
ci-dessous.

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

Les clés publiques avec un jeton bearer JWT fonctionnent également,
selon le même flux que tous les autres endpoints : générez un jeton
avec votre clé `pk_`, puis envoyez-le en tant que
`Authorization: Bearer <token>`. Le flux complet, y compris le
verrouillage de domaine et le renouvellement du jeton, se trouve dans
[le guide d'authentification](/fr/docs/authentication.md).

Chaque clé API dispose d'une liste d'endpoints autorisés. Si
`/v2/discover` ne figure pas dans la liste de la clé, la requête est
rejetée avec `403` et un message précisant le chemin bloqué.

---

## Fonctionnement de discover

Une requête s'exécute entièrement en HTTP. Le Chrome headless
singleton qui alimente [perceive](/fr/docs/endpoints/perceive.md) n'est jamais
sollicité. Le chemin de crawl utilise la stratégie de crawler HTTP de
Crawl4AI (un `GET` `httpx` suivi d'un parsing de liens `lxml` par
page), et le chemin sitemap réutilise les mêmes helpers sitemap et
feed purement HTTP que le reste de la plateforme.

1. **Filtrer l'URL de départ.** L'URL que vous envoyez est vérifiée
   contre le SSRF avant toute requête : le schéma, les identifiants
   intégrés, les noms d'hôte bloqués et l'IP résolue sont tous
   validés. Une URL qui résout vers une adresse privée, loopback,
   link-local ou de métadonnées cloud est rejetée avec `400`. L'URL de
   départ est toujours incluse comme première entrée de sa propre
   carte.
2. **Collecter depuis les sitemaps.** En mode `sitemap` ou `hybrid`,
   discover lit `robots.txt` et récupère les URLs `Sitemap:` qu'il
   déclare, sonde `sitemap.xml` (en récursant dans les enfants
   `<sitemapindex>`), et récupère les pages de flux RSS/Atom. Elle
   exécute ces sondes à la fois contre l'hôte que vous avez envoyé et
   contre le domaine enregistrable (apex) du site, de sorte qu'un
   sitemap publié uniquement sur l'apex est quand même trouvé depuis un
   sous-domaine ou une URL de départ à chemin profond. Un sitemap
   manquant ou cassé devient un avertissement, pas une erreur.
3. **Crawler en HTTP.** En mode `crawl` ou `hybrid`, discover exécute
   un crawl en largeur depuis l'URL de départ jusqu'à `max_depth`, en
   récupérant les liens `<a href>` de chaque page brute récupérée.
   Chaque lien suivi est filtré contre le SSRF avant d'être récupéré.
4. **Normaliser et filtrer.** La liste brute combinée est
   canonicalisée (fragments et paramètres de tracking supprimés, ports
   par défaut retirés, clés de requête triées), puis passée par la
   vérification du même domaine, vos motifs regex d'inclusion et
   d'exclusion, le filtre `robots.txt`, la déduplication, et enfin le
   plafond `max_urls`.

Sur le chemin HTTP uniquement, une application monopage (SPA) rendue
côté client ne renvoie que sa coquille HTML en mode `crawl`,
typiquement l'URL de départ plus zéro ou une URL. Le repli optionnel
`render_js` (voir [Rendu JavaScript](#javascript-rendering)) comble
cette lacune : dans son réglage `"auto"` par défaut, discover effectue
le rendu de la page une fois dans le navigateur et récolte les liens
qu'elle injecte à l'exécution chaque fois que le crawl HTTP ne renvoie
qu'une coquille. Le mode `sitemap` reste une voie rapide et sans
navigateur pour les SPA soucieuses du SEO, qui publient généralement un
sitemap.

---

## Paramètres de la requête

### Paramètres de base

| Paramètre | Type | Défaut | Description |
|-----------|------|---------|-------------|
| `url` | `string` | aucun | Le site à cartographier. Doit commencer par `http://` ou `https://`. Max 2,048 caractères. Obligatoire. |
| `mode` | `string` | `"hybrid"` | `sitemap`, `crawl` ou `hybrid`. Voir [Modes](#modes). |
| `max_urls` | `integer` | `100` | Nombre maximum d'URLs renvoyées. De 1 à 1,000. La liste est plafonnée ici et `truncated` l'indique si davantage existait. |
| `max_depth` | `integer` | `2` | Profondeur de crawl depuis l'URL de départ, en mode `crawl`/`hybrid`. De 1 à 5. |
| `same_domain_only` | `boolean` | `true` | Ne conserve que les URLs sur l'hôte de l'URL de départ. Quand `false`, les liens hors hôte découverts pendant le crawl sont aussi conservés. |
| `render_js` | `string` | `"auto"` | Découverte par rendu navigateur pour les sites JavaScript/SPA (`crawl`/`hybrid` uniquement). `auto` n'effectue le rendu que lorsque le crawl HTTP renvoie une coquille ; `always` le force ; `never` reste en HTTP uniquement. Voir [Rendu JavaScript](#javascript-rendering). |

### Filtrage

| Paramètre | Type | Défaut | Description |
|-----------|------|---------|-------------|
| `include_patterns` | `string[]` | `[]` | Liste d'autorisation de regex Python (sémantique `re.search`, pas de glob). Une URL doit correspondre à au moins un motif pour être conservée. Vide signifie tout autoriser. Max 50 motifs. |
| `exclude_patterns` | `string[]` | `[]` | Liste de blocage de regex Python. Une URL correspondant à un motif quelconque est écartée. Appliqué après `include_patterns`. Max 50 motifs. |
| `respect_robots` | `boolean` | `false` | Quand `true`, les URLs interdites par le `robots.txt` du site sont retirées de la liste renvoyée. |

Les motifs sont de véritables expressions régulières Python, compilées
au moment de la validation. Un motif malformé produit un `422` en
amont, pas un `500` en plein crawl. Comme la sémantique est celle de
`re.search`, une simple sous-chaîne comme `"/blog/"` correspond
n'importe où dans l'URL, donc ancrez avec `^`/`$` si vous avez besoin
d'une correspondance positionnelle.

> **À signaler d'emblée.** `respect_robots` est appliqué au moment du
> filtrage de sortie, pas au moment de la récupération. Crawl4AI 0.8.9
> n'a pas de blocage robots natif, donc en mode `crawl` ou `hybrid` une
> page interdite peut quand même être récupérée en HTTP puis écartée
> avant d'atteindre la réponse. Contrairement à
> [perceive](/fr/docs/endpoints/perceive.md), où `respect_robots=true` rejette
> une URL interdite avec `403`, discover ne renvoie jamais `403` pour
> une règle robots. Elle retire l'URL en silence et ne le signale nulle
> part.

### Modes

| `mode` | Ce que ça fait |
|--------|--------------|
| `sitemap` | Entrées sitemap de `robots.txt`, `sitemap.xml` sondé (avec récursion d'index), et pages de flux RSS/Atom. Instantané, sans crawl. |
| `crawl` | Crawl HTTP pur en largeur depuis l'URL de départ, récupérant les liens `<a href>` du balisage brut. |
| `hybrid` (par défaut) | L'union dédupliquée de `sitemap` et `crawl`. |

Le mode `crawl` récupère jusqu'à votre `max_urls` complet (jusqu'à
1,000), de sorte qu'un grand site est énuméré en une seule passe. Le
mode crawl est purement HTTP (pas de navigateur), donc cela reste
rapide. Les opérateurs peuvent abaisser ce plafond avec la variable
d'environnement `DISCOVER_CRAWL_MAX_PAGES` si un très grand crawl doit
un jour être borné. `pages_crawled` dans la réponse vous indique
exactement combien de GET ont été exécutés.

La récupération de sitemap gère les **sitemaps compressés en gzip**
(`sitemap.xml.gz` et tout sitemap servi en `application/gzip`), et
envoie des en-têtes de navigateur réalistes, de sorte que les sites
derrière un WAF sont bien plus susceptibles de renvoyer leur sitemap
plutôt qu'un `403`/`503`.

### Rendu JavaScript {: #javascript-rendering }

Les modes `crawl` et `hybrid` lisent le HTML brut renvoyé par un
serveur, de sorte qu'une application monopage (SPA) rendue côté client,
qui construit ses liens dans le navigateur, leur est invisible.
`render_js` active un repli navigateur borné qui comble cette lacune :

| `render_js` | Ce que ça fait |
|-------------|--------------|
| `auto` (par défaut) | Exécute d'abord le crawl HTTP ; n'effectue le rendu dans le navigateur que lorsqu'il ne renvoie qu'une coquille nue (une URL ou aucune), puis récolte les liens injectés côté client. |
| `always` | Exécute toujours le crawl par rendu navigateur. |
| `never` | Reste strictement en HTTP uniquement, le comportement préexistant. |

Le repli utilise le même Chrome headless partagé que
[perceive](/fr/docs/endpoints/perceive.md) et est plafonné à quelques pages afin
que l'appel reste dans le timeout de la requête. Les liens qu'il trouve
sont comptabilisés sous la clé `crawl_js` dans `sources`. Il s'applique
aux modes `crawl` et `hybrid` uniquement, car le mode `sitemap` est
déjà sans navigateur et n'est pas affecté.

---

## Réponse

`POST /v2/discover` renvoie directement cet objet, sans job asynchrone
ni second appel.

| Champ | Type | Description |
|-------|------|-------------|
| `url` | `string` | L'URL de départ que vous avez envoyée. |
| `mode` | `string` | Le mode exécuté : `sitemap`, `crawl` ou `hybrid`. |
| `total` | `integer` | Nombre d'URLs dans `urls` (après déduplication, filtrage et plafonnement). |
| `urls` | `string[]` | La liste d'URLs dédupliquée, normalisée et plafonnée. L'URL de départ est toujours le premier candidat. |
| `pages_crawled` | `integer` | GET HTTP émis par le crawl. `0` en mode `sitemap` pur. |
| `truncated` | `boolean` | `true` quand il existait plus d'URLs uniques que `max_urls` n'en autorisait. |
| `robots_respected` | `boolean` | Reprend la valeur de `respect_robots` que vous avez envoyée. |
| `sources` | `object` | Décompte brut d'URLs par source avant déduplication/filtrage, ex. `{"sitemap": 42, "crawl": 30}`. Les clés incluent `sitemap`, `crawl`, `sitemap_apex` (sitemaps trouvés sur le domaine apex) et `crawl_js` (liens issus du repli de rendu JavaScript). Les décomptes se chevauchent et leur somme dépasse `total`. |
| `warnings` | `string[]` | Notes non bloquantes : un sitemap manquant, un crawl en échec, un `robots.txt` inaccessible. |

Les décomptes de `sources` sont bruts : ce sont les nombres d'URLs
produits par chaque chemin avant la normalisation, la vérification du
même domaine, vos filtres et la déduplication. Ils dépassent
régulièrement `total`, parce que le mode `hybrid` retrouve les mêmes
pages à la fois via le sitemap et le crawl. Utilisez-les pour voir
quel chemin porte la carte, pas comme un décompte post-filtrage.

---

## Exemples de code

### curl : carte hybride par défaut

```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 : sitemap uniquement, pages de blog, plafonné à 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 pattern courant consiste à découvrir d'abord, puis à rendre :
prenez `data.urls` et transmettez-les à
[l'endpoint perceive](/fr/docs/endpoints/perceive.md), avec des appels individuels
pour une poignée de pages ou son chemin batch pour la liste entière.

---

## Réponses d'erreur

| Statut | Condition |
|--------|-----------|
| `400 Bad Request` | L'URL n'est pas en `http(s)`, contient des identifiants intégrés, n'a pas de nom d'hôte, ou résout vers une adresse privée, loopback, link-local ou de métadonnées cloud (protection SSRF). |
| `401 Unauthorized` | Clé API / jeton JWT manquant ou invalide. |
| `402 Payment Required` | Discover n'est pas activé sur votre plan actuel, ou votre quota mensuel d'ops est épuisé. Chaque appel `/v2/discover` facture une op. |
| `403 Forbidden` | `/v2/discover` ne figure pas dans les endpoints autorisés de la clé API. |
| `422 Unprocessable Entity` | Échec de validation de la requête : énumération `mode` invalide, `max_urls` en dehors de la plage 1 à 1,000, `max_depth` en dehors de la plage 1 à 5, plus de 50 motifs include/exclude, une regex malformée, ou une `url` de plus de 2,048 caractères. |
| `500 Internal Server Error` | La découverte d'URLs a échoué de façon inattendue. Le client reçoit un message générique ; le détail complet ne va que dans les logs serveur. |

À noter qu'un échec de récupération du sitemap ou un incident de crawl
ne produit pas de statut d'erreur. Ces cas dégradent en entrées dans
le tableau `warnings`, et la requête renvoie quand même `200` avec ce
qui a été trouvé. La référence complète des codes de statut se trouve
dans [le guide des codes d'erreur](/fr/docs/reference/errors.md).

---

## Limites

| Limite | Valeur |
|-------|-------|
| Longueur d'URL | 2,048 caractères |
| `max_urls` | de 1 à 1,000 (défaut 100) |
| `max_depth` | de 1 à 5 (défaut 2) |
| `include_patterns` | 50 motifs max |
| `exclude_patterns` | 50 motifs max |
| Pages récupérées en mode crawl | jusqu'à `max_urls` (max 1,000 ; à abaisser avec `DISCOVER_CRAWL_MAX_PAGES`) |
| Timeout de requête | 300 secondes |
| Ops par appel | 1, facturée sur le quota mensuel unifié |

---

## Foire aux questions

### Comment lister toutes les URLs d'un site web avec une API ?

Envoyez `POST /v2/discover` avec l'URL du site. Le mode `hybrid` par défaut combine le parsing de sitemap (entrées `robots.txt`, `sitemap.xml` avec récursion d'index, flux RSS/Atom) avec un crawl HTTP en largeur, et renvoie jusqu'à `max_urls` (de 1 à 1,000, défaut 100) URLs dédupliquées en une seule réponse synchrone.

### L'endpoint discover utilise-t-il un navigateur headless ou effectue-t-il du rendu JavaScript ?

Par défaut, il s'exécute en HTTP et n'effectue le rendu que lorsqu'il le faut. L'option `render_js` contrôle cela : dans le mode `"auto"` par défaut, discover reste en HTTP uniquement et n'effectue le rendu d'une page dans le navigateur que lorsque le crawl HTTP renvoie une coquille JavaScript nue, en récoltant les liens injectés côté client ; `"always"` force le crawl par navigateur et `"never"` le maintient strictement en HTTP uniquement. Le mode `sitemap` reste une option rapide et sans navigateur pour les SPA soucieuses du SEO.

### Combien de pages le mode crawl récupère-t-il réellement ?

Le mode crawl récupère jusqu'à votre `max_urls` complet (jusqu'à 1,000). Il reste purement HTTP, donc il reste rapide ; les opérateurs peuvent abaisser ce plafond avec la variable d'environnement `DISCOVER_CRAWL_MAX_PAGES`. Chaque page récupérée apporte en plus de nombreux liens. Le champ `pages_crawled` de la réponse indique exactement combien de GET HTTP ont été exécutés.

### Puis-je filtrer les URLs renvoyées ?

Oui. `include_patterns` et `exclude_patterns` acceptent jusqu'à 50 regex Python chacun (sémantique `re.search`, pas de glob), et `respect_robots: true` retire de la liste renvoyée les URLs interdites par le `robots.txt` du site.
