Ingestion

POST /v2/ingest transforme un site — ou une liste explicite d'URL — en chunks prêts pour le RAG et produit un seul fichier JSONL qui se charge directement dans LangChain JSONLoader, LlamaIndex SimpleDirectoryReader, ou un import en masse vers une base vectorielle. Il remplace la pile en deux étapes que vous construiriez sinon : Firecrawl /crawl pour découvrir et scraper, plus votre propre chunker pour découper le Markdown. EnConvert fait la découverte, le rendu en Chrome headless, le chunking sensible aux titres et l'assemblage du JSONL en un seul job.

Voici l'appel utile le plus minimal. Crawlez un site et chunkez chaque page qu'il découvre :

curl -X POST https://api.enconvert.com/v2/ingest \
  -H "X-API-Key: sk_live_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "crawl",
    "url": "https://example.com/docs"
  }'

La réponse est l'enregistrement du job, renvoyé avec 202 Accepted. Notez que le statut est queued, et que output_url est absent tant que le job n'est pas terminé :

{
    "job_id": "ing_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7",
    "status": "queued",
    "mode": "crawl",
    "pages_discovered": 0,
    "pages_processed": 0,
    "pages_failed": 0,
    "total_chunks": 0,
    "webhook_delivered": false,
    "created_at": "2026-06-24T09:14:02.118Z"
}

L'ingestion est toujours asynchrone. Chaque page est rendue dans un vrai navigateur, ce qui prend 10–30 secondes par URL — bien au-delà de la fenêtre de requête de 300 secondes pour tout job non trivial. Donc POST répond 202 avec un job_id, et un worker local au droplet vide le job hors bande. Vous interrogez GET /v2/ingest/{job_id} pour suivre la progression, ou vous enregistrez un webhook_url pour être averti quand il se termine.

À signaler d'emblée. /v2/ingest est un endpoint V2 avec son propre compteur de quota, ingest_pages. Les plans de conversion V1 n'incluent aucune allocation d'ingestion — il vous faut un plan incluant V2 pour l'appeler. Une page est facturée pour chaque URL dont l'étape de rendu, de chunking et de JSONL s'achève ; les pages qui échouent ou sont ignorées ne sont pas facturées. Voir la page tarifs pour les allocations par palier, et l'aperçu V2 pour comprendre en quoi les compteurs V2 diffèrent des conversions V1.


Endpoints

Méthode Chemin Objet
POST /v2/ingest Crée un job d'ingestion. Répond 202 avec un job_id.
GET /v2/ingest Liste des jobs de ce projet, du plus récent au plus ancien, avec pagination skip/limit.
GET /v2/ingest/{job_id} Statut de cycle de vie d'un job, avec un output_url fraîchement signé une fois terminé.
DELETE /v2/ingest/{job_id} Annule un job. Le worker voit le statut annulé et s'arrête entre deux pages.
POST /v2/ingest/{job_id}/retry-webhook Re-signe et re-POST le webhook de complétion pour un job terminé.
GET /v2/ingest/webhook-secret Révèle le secret de signature de webhook du projet (canal dashboard).
POST /v2/ingest/webhook-secret/rotate Effectue la rotation du secret de signature. Les anciennes signatures cessent de se vérifier immédiatement.

Content-Type : application/json sur chaque 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 qu'empruntent les exemples ci-dessous.

X-API-Key: sk_live_your_private_key

Les clés publiques avec un jeton bearer JWT fonctionnent aussi, en utilisant le même flux que tous les autres endpoints — générez un jeton avec votre clé pk_, puis envoyez-le sous la forme Authorization: Bearer <token>. Le flux complet, y compris le verrouillage de domaine et le rafraîchissement du jeton, figure dans le guide d'authentification.

Chaque clé d'API porte une liste blanche d'endpoints autorisés. Si /v2/ingest n'est pas sur la liste de la clé, la requête est rejetée avec 403. Une clé limitée à /v2/ingest atteint quand même les jobs qu'elle a créés — GET et DELETE /v2/ingest/{job_id} ainsi que POST /v2/ingest/{job_id}/retry-webhook sont toujours autorisés pour un job_id (la forme ing_… est reconnue explicitement). L'endpoint de liste statique et les deux routes de gestion webhook-secret n'héritent pas de ce contournement ; ils requièrent un jeton plus large ou limité au dashboard.


Fonctionnement de l'ingestion

Un job parcourt cinq phases, toutes durables et résistantes aux redémarrages. Si le processus worker redémarre en plein job, le job est ré-enfilé au démarrage et reprend à la page sur laquelle il s'était arrêté — les pages déjà terminées conservent leur sortie en attente et ne sont jamais re-rendues ni re-facturées.

  1. File d'attente. POST valide la requête, exécute un contrôle de quota rapide units=1 (le plan a l'ingestion activée et un peu de marge mensuelle), insère la ligne du job, et répond 202. Rien n'est persisté si la barrière de quota échoue — un 402 ne laisse aucune ligne derrière lui.
  2. Découverte. Pour les modes sitemap et crawl, le worker exécute la même passe de découverte que l'endpoint discover, plafonnée à max_pages, et applique un filtrage SSRF sur l'URL de départ. Pour le mode urls, la liste explicite est dédupliquée dans l'ordre ; aucune découverte ne s'exécute.
  3. Rendu et chunking. Chaque URL est rendue via le singleton Chrome headless partagé — le même pipeline de rendu que derrière l'endpoint perceive — puis le HTML rendu est converti en Markdown ajusté et découpé par le chunker sensible aux titres. Les rendus s'exécutent séquentiellement, une page à la fois.
  4. Mise en attente. Les chunks de chaque page sont écrits dans un objet JSONL par page dans le stockage, indexé de façon déterministe par (project, job, url). C'est ce qui rend un redémarrage peu coûteux : un job repris réutilise les pages en attente au lieu de les re-rendre.
  5. Assemblage. Une fois chaque page terminée, les objets par page sont concaténés dans le fichier final v2-ingest/{job_id}.jsonl, les objets en attente sont supprimés, le job bascule sur completed, et — si un webhook_url était défini — le webhook de complétion signé se déclenche.

Le compteur de quota est revérifié par page à l'intérieur du worker, pas seulement au moment de la soumission. Un job crawl dont le nombre de pages est inconnu d'avance s'arrête proprement à votre plafond mensuel : les pages déjà rendues sont facturées et conservées, et les pages restantes sont marquées skipped plutôt que de dépasser le budget.

Les rendus d'ingestion sont sans identifiants par conception. Contrairement à /v2/perceive, l'ingestion n'accepte pas auth, cookies, ni headers personnalisés — rien de secret n'est persisté pour la reprise durable, donc l'état du job sur disque ne porte jamais d'identifiants.


Paramètres de requête

Source et mode

Paramètre Type Défaut Description
mode string "urls" urls, sitemap, ou crawl. Sélectionne la façon dont l'ensemble d'URL est construit.
url string null URL de départ pour le mode sitemap/crawl. Doit commencer par http:// ou https://. Max 2 048 caractères. Requis pour ces modes ; rejeté en mode urls.
urls string[] null URL explicites à ingérer en mode urls. Non vide, max 1 000 entrées, chacune en http(s) et ≤ 2 048 caractères. Requis pour le mode urls ; rejeté en mode sitemap/crawl.

url et urls sont mutuellement exclusifs — exactement une source. Le mode urls requiert urls ; sitemap et crawl requièrent une url de départ. Envoyer le mauvais pour le mode donne un 422.

Découverte (modes sitemap / crawl)

Ces paramètres sont transmis à la passe de découverte et ignorés en mode urls.

Paramètre Type Défaut Description
max_pages integer 50 Plafond d'URL découvertes et ingérées. 1–1 000.
max_depth integer 2 Profondeur de liens du crawl depuis l'URL de départ. 1–5.
same_domain_only boolean true Restreint la découverte au domaine de l'URL de départ.
include_patterns string[] [] Motifs regex qu'une URL doit satisfaire pour être conservée. Max 50. Chacun est compilé à la soumission ; un motif erroné donne un 422.
exclude_patterns string[] [] Motifs regex qui écartent une URL correspondante. Max 50.
respect_robots boolean false Quand true, une URL interdite par le robots.txt du site est ignorée.

Rendu

Paramètre Type Défaut Description
wait_for string null Attend après la navigation un sélecteur CSS ou une expression JS avant la capture. Max 1 024 caractères.
wait_timeout_ms integer 30000 Durée maximale d'attente de wait_for, en millisecondes. 0–60 000.

Remarque. L'ingestion n'accepte pas auth, cookies, ni headers. Si une page a besoin d'identifiants pour s'afficher, l'ingestion n'est pas le bon outil — utilisez l'endpoint perceive, qui porte la surface complète des requêtes authentifiées, pour cette page unique.

Chunking (objet chunk)

Paramètre Type Défaut Contraintes Description
max_words integer 512 32–4 000 Plafond souple de mots par chunk. Sensible aux titres. Les blocs de code et les tableaux à pipes restent atomiques et peuvent dépasser cette valeur.
sentence_overlap integer 1 0–10 Phrases répétées entre chunks de prose consécutifs d'une même section. 0 désactive le chevauchement. Le chevauchement ne franchit jamais une limite de titre.

Le chunker découpe sur les titres #, ##, et ### — chaque chunk appartient à exactement une section et porte son chemin de titres complet. Les titres plus profonds (##########) restent inline comme du contenu. Les blocs de code délimités et les tableaux Markdown ne sont jamais découpés, même quand un seul bloc dépasse max_words ; les éléments de liste sont découpés entre éléments, jamais en plein élément.

Webhook

Paramètre Type Défaut Description
webhook_url string null Endpoint destiné à recevoir le callback de complétion signé en HMAC. Max 2 048 caractères. Schéma vérifié à la soumission ; filtré SSRF au moment de la livraison, pas à la soumission.

Réponse

POST, GET /v2/ingest/{job_id}, et DELETE renvoient tous le même objet IngestJobResponse.

Champ Type Description
job_id string ID opaque (ing_…). Utilisez-le avec les endpoints GET/DELETE et citez-le au support.
status string queued, discovering, processing, completed, failed, ou canceled.
mode string Le mode que vous avez soumis : urls, sitemap, ou crawl.
pages_discovered integer URL que le job ingérera (la liste explicite, ou le résultat de la découverte).
pages_processed integer URL dont le rendu → chunk → mise en attente s'est achevé.
pages_failed integer URL qui n'ont pas pu être rendues ou qui ont été ignorées (par ex. quota épuisé).
total_chunks integer Total de chunks écrits sur toutes les pages terminées — correspond au nombre de lignes du JSONL.
output_url string URL de téléchargement pré-signée pour le JSONL final. Présent uniquement lorsque status vaut completed ; expire après 15 minutes.
error_message string Défini lorsque status vaut failed (par ex. découverte rejetée, toutes les pages en échec).
webhook_url string La cible du webhook de complétion enregistrée pour ce job, le cas échéant.
webhook_delivered boolean true une fois que le webhook de complétion signé a reçu un 2xx.
created_at string Date de création du job (UTC).
completed_at string Date à laquelle le job a atteint un statut terminal (UTC).
warnings string[] Notes non fatales.

Remarque. POST et les GET/DELETE par job utilisent response_model_exclude_none, donc les champs encore null (comme output_url avant la complétion) sont omis du JSON plutôt qu'envoyés en tant que null.

La forme de l'enregistrement JSONL

Le fichier final est du JSON délimité par des sauts de ligne. Chaque ligne est un chunk :

{"id":"9f2b8c1ad4e5-0000","content":"Pricing is usage-based...","metadata":{"source_url":"https://example.com/pricing","title":"Pricing","headings_path":["Pricing","Plans"],"section":"Plans","word_count":118,"chunk_index":0}}
Champ Type Description
id string Déterministe par (source_url, chunk_index) : <md5(url)[:12]>-<index:04d>. Une ré-exécution produit des ids identiques.
content string Le texte du chunk récupérable. Correspond à Document.page_content dans LangChain.
metadata.source_url string La page d'où provient le chunk.
metadata.title string Le <title> de la page, à défaut le premier <h1>, plafonné à 512 caractères.
metadata.headings_path string[] Le chemin h1 → h2 → h3 sous lequel se trouve le chunk.
metadata.section string Le texte du titre le plus interne (la dernière entrée de headings_path).
metadata.word_count integer Nombre de mots de content, délimités par des espaces.
metadata.chunk_index integer L'index du chunk au sein de sa page.

Le fichier est en UTF-8, écrit avec ensure_ascii=false, donc l'unicode reste lisible. Comme content est une chaîne de premier niveau et que metadata est un objet frère, le même fichier se charge via LangChain JSONLoader(content_key="content", json_lines=True), LlamaIndex SimpleDirectoryReader, et tout import de base vectorielle orienté ligne sans remise en forme.


Cycle de vie du job et polling

Un job traverse ces états :

queued → discovering → processing → completed | failed | canceled
Statut Signification
queued Accepté et en attente du worker.
discovering Exécution de la passe de découverte sitemap/crawl (sitemap/crawl uniquement).
processing Rendu et chunking des pages. pages_processed et total_chunks montent en direct.
completed Le JSONL final est assemblé ; output_url est signé et prêt.
failed La découverte a été rejetée, ou toutes les pages ont échoué ou ont été ignorées. error_message explique.
canceled Un DELETE a atteint le job avant qu'il ne se termine.

Interrogez le statut avec le GET par job. C'est en lecture seule — il ne consomme aucun quota et re-signe l'output_url à partir de la clé d'objet stockée à chaque appel :

curl https://api.enconvert.com/v2/ingest/ing_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7 \
  -H "X-API-Key: sk_live_your_private_key"

Un job_id inconnu, ou appartenant à un autre projet, renvoie 404 — l'existence n'est jamais divulguée entre projets.

Lister les jobs

GET /v2/ingest renvoie les jobs de ce projet du plus récent au plus ancien, avec les paramètres de requête skip et limit. limit vaut 20 par défaut et est plafonné à 100. La réponse porte un drapeau has_more plutôt qu'un comptage total :

curl "https://api.enconvert.com/v2/ingest?skip=0&limit=20" \
  -H "X-API-Key: sk_live_your_private_key"
{
    "jobs": [
        {
            "job_id": "ing_3f9a...",
            "status": "completed",
            "mode": "crawl",
            "pages_discovered": 42,
            "pages_processed": 41,
            "pages_failed": 1,
            "total_chunks": 1187,
            "output_url": "https://spaces.example.com/...signed...",
            "webhook_configured": true,
            "webhook_delivered": true,
            "created_at": "2026-06-24T09:14:02.118Z",
            "completed_at": "2026-06-24T09:31:55.402Z"
        }
    ],
    "skip": 0,
    "limit": 20,
    "has_more": false
}

Les lignes de la liste réduisent webhook_url à un booléen webhook_configured — la liste ne renvoie jamais l'endpoint brut dans le tableau.

Annuler un job

DELETE /v2/ingest/{job_id} met le statut du job sur canceled. Le worker lit ce statut entre deux pages et s'arrête sans assembler de sortie. L'annulation est idempotente et insensible aux conditions de course : si l'assemblage est déjà acté, le DELETE ne correspond à rien et le job est renvoyé inchangé comme completed — un job terminé n'est jamais ramené de force à canceled.

curl -X DELETE \
  https://api.enconvert.com/v2/ingest/ing_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7 \
  -H "X-API-Key: sk_live_your_private_key"

Webhooks de complétion

Définissez webhook_url sur le POST et EnConvert envoie un seul POST signé en HMAC quand le job se termine. La charge utile est du JSON compact, trié par clé :

{"job_id":"ing_3f9a...","output_url":"https://spaces.example.com/...signed...","pages_processed":41,"status":"completed","total_chunks":1187}

La livraison réessaie jusqu'à trois fois après la première tentative, avec des délais de back-off de 1, 4, et 16 secondes — quatre POST au pire des cas. Chaque tentative est re-signée avec un nouvel horodatage, donc une chaîne de réessais lente ne dérive jamais hors de la fenêtre de fraîcheur du consommateur. Une réponse 2xx vaut succès ; un endpoint mort est enregistré comme une non-livraison (et lève une alerte dans le dashboard), il ne fait jamais échouer un job par ailleurs terminé.

Le webhook_url est filtré SSRF au moment de la livraison, pas à la soumission. Une URL qui se résout en une adresse privée, de loopback, ou de métadonnées est stockée inertement et seulement rejetée lorsque EnConvert tente de la POST.

Vérifier la signature

Chaque livraison porte deux en-têtes :

En-tête Valeur
X-Enconvert-Signature sha256=<hex> — le HMAC-SHA256 de <timestamp>.<raw body>.
X-Enconvert-Timestamp L'horodatage en secondes unix lié dans la signature.

L'entrée de signature est l'horodatage, un . littéral, puis le corps brut de la requête. Lier l'horodatage dans le MAC signifie qu'un consommateur qui rejette les horodatages périmés obtient gratuitement une protection contre le rejeu — la fenêtre de fraîcheur par défaut est de 300 secondes. Vérifiez dans votre handler :

import hashlib
import hmac
import time

SECRET = "whsec_your_signing_secret"   # from GET /v2/ingest/webhook-secret
TOLERANCE_SECONDS = 300


def verify(raw_body: bytes, signature_header: str, timestamp_header: str) -> bool:
    if not signature_header or not timestamp_header:
        return False
    try:
        ts = int(timestamp_header)
    except ValueError:
        return False
    if abs(time.time() - ts) > TOLERANCE_SECONDS:
        return False  # replayed or badly skewed clock

    provided = signature_header.removeprefix("sha256=")
    expected = hmac.new(
        SECRET.encode("utf-8"),
        f"{ts}.".encode("utf-8") + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, provided)

Gérer le secret de signature

GET /v2/ingest/webhook-secret révèle le secret du projet (en le créant au premier appel) ainsi que les noms d'en-têtes et la tolérance dont votre consommateur a besoin. Il est sensible et exposé uniquement via le canal dashboard authentifié :

{
    "secret": "whsec_...",
    "signature_header": "X-Enconvert-Signature",
    "timestamp_header": "X-Enconvert-Timestamp",
    "signature_scheme": "sha256",
    "replay_tolerance_seconds": 300,
    "rotated": false
}

POST /v2/ingest/webhook-secret/rotate émet un nouveau secret et met rotated sur true. Toute signature calculée avec le secret précédent cesse de se vérifier dès que la rotation est actée — effectuez la rotation après une fuite suspectée, puis mettez à jour votre consommateur.

Re-livrer un webhook

Si votre endpoint était hors service quand le job s'est terminé, POST /v2/ingest/{job_id}/retry-webhook re-signe et re-POST avec la même politique de réessais :

curl -X POST \
  https://api.enconvert.com/v2/ingest/ing_3f9a.../retry-webhook \
  -H "X-API-Key: sk_live_your_private_key"
{
    "job_id": "ing_3f9a...",
    "delivered": true,
    "attempts": 1,
    "status_code": 200,
    "detail": "Delivered (HTTP 200)."
}

Il renvoie 404 pour un job_id inconnu ou étranger, 400 quand aucun webhook_url n'est configuré (ou que l'URL stockée se résout désormais en une adresse privée/interne), et 409 quand le job n'a pas atteint completed.


Exemples de code

curl — liste explicite d'URL

curl -X POST https://api.enconvert.com/v2/ingest \
  -H "X-API-Key: sk_live_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "urls",
    "urls": [
      "https://example.com/docs/intro",
      "https://example.com/docs/quickstart",
      "https://example.com/docs/api"
    ]
  }'

curl — crawl avec chunking et un webhook

curl -X POST https://api.enconvert.com/v2/ingest \
  -H "X-API-Key: sk_live_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "crawl",
    "url": "https://example.com/docs",
    "max_pages": 200,
    "max_depth": 3,
    "include_patterns": ["/docs/"],
    "chunk": {"max_words": 700, "sentence_overlap": 2},
    "webhook_url": "https://your-app.example.com/hooks/ingest"
  }'

Python — soumettre, interroger, télécharger

import time

import requests

BASE = "https://api.enconvert.com"
HEADERS = {"X-API-Key": "sk_live_your_private_key"}

# 1. Submit (always 202).
job = requests.post(
    f"{BASE}/v2/ingest",
    headers=HEADERS,
    json={"mode": "crawl", "url": "https://example.com/docs", "max_pages": 100},
).json()
job_id = job["job_id"]

# 2. Poll until terminal.
while True:
    job = requests.get(f"{BASE}/v2/ingest/{job_id}", headers=HEADERS).json()
    if job["status"] in ("completed", "failed", "canceled"):
        break
    time.sleep(5)

# 3. Download the JSONL from its signed URL.
if job["status"] == "completed":
    jsonl = requests.get(job["output_url"]).text
    print(f"{job['total_chunks']} chunks across "
          f"{job['pages_processed']} pages")
    print(jsonl.splitlines()[0])

Node.js — soumettre et interroger

const BASE = "https://api.enconvert.com";
const HEADERS = {
    "Content-Type": "application/json",
    "X-API-Key": "sk_live_your_private_key"
};

// 1. Submit.
const submit = await fetch(`${BASE}/v2/ingest`, {
    method: "POST",
    headers: HEADERS,
    body: JSON.stringify({
        mode: "crawl",
        url: "https://example.com/docs",
        max_pages: 100
    })
});
let job = await submit.json();

// 2. Poll until terminal.
while (!["completed", "failed", "canceled"].includes(job.status)) {
    await new Promise((r) => setTimeout(r, 5000));
    const poll = await fetch(`${BASE}/v2/ingest/${job.job_id}`, {
        headers: { "X-API-Key": HEADERS["X-API-Key"] }
    });
    job = await poll.json();
}

// 3. Download the JSONL.
if (job.status === "completed") {
    const jsonl = await fetch(job.output_url).then((r) => r.text());
    console.log(`${job.total_chunks} chunks`);
    console.log(jsonl.split("\n")[0]);
}

Encadrement par le plan

L'ingestion V2 est mesurée par son propre compteur, ingest_pages — distinct des conversions V1 et des autres compteurs V2. Une page est facturée pour chaque URL dont l'étape de rendu, de chunking et de JSONL s'achève ; les pages qui échouent ou sont ignorées ne sont pas facturées.

Le contrôle à la soumission est une barrière rapide units=1 : votre plan doit avoir l'ingestion activée, avec un peu de marge mensuelle. Le vrai limiteur de dépense est le contrôle par page à l'intérieur du worker — un job crawl s'arrête proprement à votre plafond mensuel et marque les pages restantes skipped plutôt que de le dépasser.

Barrière Comportement
Plan sans ingestion activée 402 à la soumission — passez à un plan incluant V2.
Quota mensuel ingest_pages épuisé 402 à la soumission ; ou, si épuisé en plein job, les pages restantes sont marquées skipped.
Illimité (entreprise / admin) Une limite de 0 avec le drapeau activé signifie aucun plafond mensuel.

MAX_PAGES_PER_JOB (1 000) est un plafond par requête qui borne un seul job ; ce n'est pas votre allocation mensuelle. Le quota mensuel ingest_pages est le vrai limiteur de dépense. Les chiffres actuels par palier se trouvent sur la page tarifs ; voir aussi comment les opérations perceive sont mesurées pour le compteur V2 voisin.


Réponses d'erreur

Statut Condition
202 Accepted Le job a été créé et mis en file. C'est le résultat normal d'un POST.
401 Unauthorized Clé d'API / jeton JWT manquant ou invalide.
402 Payment Required L'ingestion n'est pas sur votre plan actuel, ou votre quota mensuel ingest_pages est épuisé.
403 Forbidden /v2/ingest n'est pas dans les endpoints autorisés de la clé d'API.
404 Not Found job_id inconnu, ou détenu par un autre projet.
409 Conflict retry-webhook appelé sur un job qui n'a pas atteint completed.
400 Bad Request retry-webhook appelé sans webhook_url configuré, ou son URL stockée se résout désormais en une adresse privée/interne.
422 Unprocessable Entity La source ne correspond pas au mode (urls sans urls, ou une url de départ en mode urls) ; un paramètre est hors plage ; ou une regex include_patterns/exclude_patterns ne se compile pas.
500 Internal Server Error Le job n'a pas pu être créé. Le message inclut le job_id à citer au support.

Un échec de rendu au niveau d'une page ne fait pas échouer la requête ni le job — il incrémente pages_failed, place l'erreur de la page dans sa propre ligne, et le job continue. Un job ne fails que lorsque la découverte est rejetée ou que toutes les pages échouent ou sont ignorées. La référence complète des codes de statut figure dans le guide des codes d'erreur.


Limites

Limite Valeur
URL par requête en mode urls 1 000
Longueur de url / de chaque entrée urls 2 048 caractères
max_pages (plafond de découverte) 1–1 000
max_depth 1–5
include_patterns / exclude_patterns 50 chacun
Longueur de wait_for 1 024 caractères
wait_timeout_ms 0–60 000 ms
chunk.max_words 32–4 000 (défaut 512)
chunk.sentence_overlap 0–10 (défaut 1)
Longueur de webhook_url 2 048 caractères
Plafond de pages par job (MAX_PAGES_PER_JOB) 1 000
limit de la liste GET /v2/ingest 1–100 (défaut 20)
Expiration de l'output_url signée 15 minutes
Tentatives de livraison du webhook 4 (initiale + 3 réessais)
Tolérance au rejeu du webhook 300 secondes
ingest_pages mensuel Dépend du plan — voir tarifs