Appuyez sur / pour rechercher

Toute la documentation
docs Extraire Crawler un site entier

S’exécute surWrit CloudDesktopAuto-hébergé

crawl ▸ sites entiers

Une page, ou toutes les pages.

La surface de crawl tient en trois appels qui grandissent avec le travail : une page, une carte des URL, ou toutes les pages d’un périmètre que vous fixez. Enregistrez le périmètre et il devient appelable — POST /api/crawl/definitions/{ref}/run répond depuis la dernière exécution si elle est assez fraîche, et recrawle sinon.

Un crawl reste dans le périmètre que vous fixez : même domaine par défaut, filtres de chemin sous votre contrôle, un budget de pages qu’il ne dépassera pas, et un délai entre les requêtes.

surfaces ▸ trois tailles

Trois appels, trois tailles de travail.

Prenez le plus petit qui répond à votre question. Une page seule vaut une page décomptée ; une carte est une liste d’URL ; un crawl parcourt le périmètre et remplit un dataset que vous pouvez lire, fouiller et exporter.

AppelCe qu’il faitCoût
POST /api/crawl/scrapeUne page, rendue en markdown avec un compte de caractères et de tokens.1 page
POST /api/crawl/mapLes URL qu’un site expose, sans charger chacune en entier.Décompté
POST /api/crawlDémarre un crawl sur le périmètre. Rend un job que vous interrogez.Par page
POST /v1/keyless/crawlQuelques pages du même domaine, sur un niveau, SANS compte. Rend les pages en ligne, pas un job.Gratuit, plafonné par jour
POST /api/crawl/previewLe périmètre qu’un crawl PRENDRAIT — include/exclude/profondeur effectifs, plus un échantillon d’URL gardées face à des URL écartées.Rien

Une page, deux paliers

Avec une clé wt_, l’appel est décompté de votre plan. Sans aucune clé, le palier keyless rend la même forme pour les pages publiques — voir plus bas. Le palier sans clé crawle aussi : POST /v1/keyless/crawl charge jusqu’à 5 pages du même domaine, sur un niveau, et les rend en ligne — sans flotte, sans persona, sans sortie résidentielle. Chaque page consomme la même allocation quotidienne qu’un appel à <code>/v1/keyless/scrape</code> : le plafond quotidien, pas celui par requête, est la vraie limite, et la réponse indique les deux.

cloud.ts

const cloud = new CloudApi({ apiKey: process.env.WRIT_API_KEY }); // wt_…
const page = await cloud.scrape("https://example.com");
const site = await cloud.map("https://example.com", { search: "pricing", limit: 20 });
console.log(cloud.tier); // "metered" | "keyless"

Ce que rend une page

L’appel une-page rend un corps plat, pas un handle de job :

verbL’opération qui a répondu.
url · titleLa page lue et son titre.
formatToujours "markdown" sur cet appel.
markdownLe corps de la page, nettoyé.
countschars, raw_tokens_est et clean_tokens_est — ce que sa lecture coûterait à un modèle.
tier"metered" quand une clé wt_ a authentifié l’appel.

Deux échecs à traiter : 422 scrape_unreachable quand la page ne peut pas être chargée, et 402 insufficient_credits quand la page dépasse votre plan et que le wallet ne peut pas la couvrir.

périmètre ▸ ce qui est chargé

Dites quoi crawler, et jusqu’où.

Seul url est obligatoire. Tout le reste resserre le périmètre, change la façon de lire une page, ou plafonne le travail. Les filtres de chemin sont des expressions régulières appliquées au chemin.

ChampRôle
urlObligatoire. L’URL de départ du crawl.
nameUn libellé pour le job, pour que la liste ressemble à votre travail et non à des URL.
executorregular | ai — regular par défaut. L’executor ai raisonne sur chaque page et pèse 5×.
extract_modemarkdown | schema — markdown par défaut.
extract_schemaLa forme des champs à extraire quand extract_mode vaut schema.
extract_promptConsigne en langage clair pour l’executor ai.
render_modeauto | http | browser. Un rendu navigateur pèse 2×.
ocr_modeauto | off | force — voir les documents plus bas. Une page OCR pèse 2×.
persona_idCrawler connecté, avec une identité de connexion enregistrée qui vous appartient.
use_residentialChemin réseau premium. Disponible sur les plans premium.
intentUn objectif en langage clair. Il dérive le périmètre et classe les URL à visiter en premier.
seed_urls[]Points de départ supplémentaires, en plus de url.
relevance_threshold0–1. À quel point une page doit coller à l’intent pour être gardée.
include_paths[] · exclude_paths[]Regex de chemin. Include resserre, exclude retranche.
max_depth0–20 liens depuis l’URL de départ.
page_budget1–50000, 1000 par défaut. L’arrêt net de ce crawl.
max_concurrent_shards1–64. La largeur du crawl.
shard_size1–200, 25 par défaut. Pages par unité de travail.
delay_ms0–60000, 250 par défaut. Pause entre les requêtes.
respect_robotstrue par défaut.
same_domain · allow_subdomainsLes deux à true par défaut.
content_spec{ preset, include_comments, exclude_selectors, include_selectors, keep } — la part de chaque page qui est gardée.

Deux façons de viser. Donnez include_paths et max_depth et vous avez décrit la forme exactement. Donnez intent à la place et vous avez décrit le but — le crawl en dérive un périmètre et ordonne la frontière selon la proximité de chaque URL, avec relevance_threshold comme seuil.

POST /api/crawl/preview rend le périmètre qu’un crawl prendrait réellement — motifs include et exclude effectifs, profondeur, et un échantillon d’URL gardées à côté d’URL écartées. Il ne charge rien et ne coûte rien. Lancez-le avant un gros budget.

Démarrer un crawl

Sur l’agent local, le même job démarre sur 127.0.0.1:8131 avec un token local, et le dataset qu’il remplit se relit par la surface de données.

crawl.ts

const job = await client.crawl.start({
  url: "https://example.com",
  max_depth: 3,
  page_budget: 500,
});
const status = await client.crawl.get(job.id);
const table = await client.data.workflowData(job.data_workflow_id);

avancement ▸ statut

Regardez-le travailler. Arrêtez-le quand vous voulez.

Un crawl est un job, pas une requête : il survit couramment à tout timeout HTTP raisonnable, donc vous recevez un handle et vous l’interrogez. GET /v1/crawl les liste (limit 1–500, 50 par défaut) sous une clé crawls ; GET /v1/crawl/{id} en lit un, ou 404 s’il n’est pas à vous.

statusCe que ça veut dire
queuedAccepté, en attente de démarrage.
mappingDétermine quelles URL sont dans le périmètre.
crawlingCharge les pages.
stoppingAnnulation prise en compte, termine ce qui est en vol.
completedTerminal. Tout le périmètre a été visité ou coupé par le budget.
failedTerminal. Lisez error pour la raison.
cancelledTerminal. Vous avez demandé l’arrêt.

Les compteurs d’un crawl

Chaque lecture d’un crawl porte les mêmes champs, donc un seul poller couvre tous les lieux d’exécution :

ChampContenu
id · name · seed_urlIdentité et point de départ.
include_paths · exclude_paths · max_depthLe périmètre, tel que résolu.
same_domain · allow_subdomains · respect_robotsLes règles de frontière en vigueur.
extract_mode · extract_schemaCe qui est extrait de chaque page.
persona_idL’identité de connexion utilisée, le cas échéant.
delay_ms · max_concurrent · page_budgetLe rythme et le plafond.
workflow_id · data_workflow_idOù atterrissent les lignes collectées.
pages_discovered · pages_done · pages_failed · pages_skippedLes quatre compteurs à tracer.
workers_active · current_depthSa largeur et sa profondeur à l’instant présent.
status · error · cancel_requested · is_terminalOù il en est, et s’il bougera encore.
created_at · updated_at · started_at · completed_atLa chronologie.

Une bizarrerie à connaître avant d’écrire le client : les champs booléens d’un crawl reviennent en entiers 0 et 1, pas en true / false JSON. Testez-les comme des nombres, ou convertissez à l’entrée.

POST /v1/crawl/{id}/cancel répond toujours 200, quel que soit l’état du job, et rend le crawl rafraîchi plus cancel_requested_now — vrai quand c’est votre appel qui a basculé l’état. Annuler un crawl déjà fini n’est pas une erreur : réessayer est sans risque.

enregistré ▸ appelable

Enregistrez un crawl, puis appelez-le comme une API.

Une définition est une configuration de crawl avec un nom et un slug. Elle transforme un job ponctuel en quelque chose qu’une clé peut appeler : /v1/crawl/definitions sur l’agent local, /api/crawl/definitions sur Writ Cloud. Les deux acceptent un slug ou un id comme {ref}.

ChampRôle
nameJusqu’à 200 caractères.
slugJusqu’à 120 caractères. Le nom qu’utilisent vos appels.
descriptionTexte libre, pour la prochaine personne qui lira la liste.
default_max_age_secondsLa fenêtre de fraîcheur à appliquer quand un appel ne dit rien.
configLa configuration de crawl à exécuter. Mêmes champs qu’un démarrage de crawl.
from_crawl_idOu : copier la configuration d’un crawl déjà exécuté.

Envoyez exactement un de config ou from_crawl_id. N’en envoyer aucun répond 400 — la définition n’aurait rien à exécuter.

L’exécuter

Le corps d’exécution tient en quatre champs, tous facultatifs :

max_age0 seconde ou plus. La fenêtre de fraîcheur pour cet appel. Accepté aussi en ?max_age= ou en Cache-Control max-age.
waitfalse par défaut. À true, l’appel HTTP bloque jusqu’au verdict du crawl.
timeout5–300 secondes, 120 par défaut. N’a de sens qu’avec wait.
limit1–500, 50 par défaut. Combien de lignes collectées reviennent en ligne.

Le contrat de fraîcheur

C’est la partie contre laquelle coder. Le code de statut dit ce qui s’est passé, et aucune réponse n’est une impasse :

RéponseCe qui s’est passé
200 · cached: trueLa dernière exécution était dans la fenêtre. Ses données reviennent en ligne. Rien n’a été crawlé, rien n’a été décompté.
202Manqué. Un crawl frais a démarré ; le corps porte le crawl et son status_url. Interrogez-le.
504wait: true a dépassé son timeout. Le crawl tourne encore et le corps porte toujours crawl_id et status_url — récupérez-le, ne réessayez pas.
Cache-Control: no-cache · max_age: 0Recrawle toujours, quoi que dise la valeur par défaut de la définition.

Chaque réponse porte un objet _cachehit, age_seconds et source_crawl_id — pour qu’un client puisse journaliser pourquoi il a reçu ce qu’il a reçu. Et GET /v1/crawl/definitions/{ref}/data est une lecture pure de la dernière exécution terminée, à tout âge : elle ne crawle jamais et ne facture jamais.

Enregistrer, appeler, relire

Trois appels : créer la définition, l’exécuter avec une fenêtre, lire ce qu’elle contient déjà.

save-and-run.sh

# 1. Save the crawl — name + slug + the config it should always run.
curl -X POST https://api.usewrit.app/api/crawl/definitions \
  -H "Authorization: Bearer $WRIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Docs index",
    "slug": "docs-index",
    "default_max_age_seconds": 86400,
    "config": {
      "url": "https://example.com/docs",
      "include_paths": ["^/docs/"],
      "max_depth": 3,
      "page_budget": 500
    }
  }'

# 2. Call it. Fresh enough? You get the data. Stale? It re-crawls.
curl -X POST https://api.usewrit.app/api/crawl/definitions/docs-index/run \
  -H "Authorization: Bearer $WRIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"max_age": 86400, "wait": true, "timeout": 120, "limit": 50}'

documents ▸ ocr

PDF, tableurs et pages scannées.

Un site est rarement du HTML seul. Les PDF, les fichiers Word, Excel et PowerPoint, les images et les pages scannées sont lus dans le cadre du crawl, avec OCR là où le texte n’est que des pixels. Un seul bouton le règle :

ocr_modeEffet
autoL’OCR intervient quand une page ou un document n’a pas de couche de texte lisible.
offJamais d’OCR. Les documents avec couche de texte restent lus.
forceOCR sur chaque page, même quand une couche de texte existe.

Où cela s’applique : les crawls exécutés sur la flotte Writ Cloud, et les crawls auto-hébergés. Un crawl cloud que vous routez vers votre propre machine reliée ne fait pas d’extraction de documents — cette voie lit des pages, pas des documents. Choisissez le lieu d’exécution en conséquence. Les pages OCR comptent 2×.

prix ▸ à la page

Une page vaut 0,0005 $. Certaines pèsent plus.

Le crawl se paie à l’usage, à la page. Dans les pages de crawl mensuelles de votre plan, cela ne coûte rien de plus ; au-delà, c’est prélevé sur le wallet au même tarif, et un wallet qui ne peut pas couvrir la page répond 402 insufficient_credits. Les déploiements auto-hébergés crawlent sur vos propres agents et ne portent aucun frais de page de notre part.

Comment la page a été luePoids
Page HTTP simple, ou document
Rendu navigateur
Page OCR
executor: "ai"

Ce que chaque plan inclut

Trois nombres distincts par plan. Lisez-les comme trois, pas comme un — ils répondent à trois questions différentes.

Plan Pages de crawl par mois Pages dans un crawl Crawls simultanés
Free 1 000 1 000 1
Starter 15 000 10 000 2
Pro 75 000 25 000 3
Growth 400 000 50 000 6
Scale 1 000 000 50 000 12
Enterprise 2 000 000 50 000 24

Ce sont des nombres différents. « Pages dans un crawl » est le plafond d’un seul job ; « pages de crawl par mois » est ce que votre plan inclut sur tous les jobs de la période. Sur Pro, c’est 25 000 et 75 000 — le même plan fait trois crawls pleine taille par mois avant toute facturation.

Ils échouent aussi différemment. Un page_budget plus grand que votre plafond par crawl est ramené au plafond et le crawl s’exécute — on ne vous refuse pas d’avoir demandé. Les deux barrières qui peuvent vraiment refuser sont la limite de simultanéité (un crawl de trop pendant que d’autres tournent) et l’allocation mensuelle une fois que le wallet ne couvre plus le dépassement.

Lire le compteur

GET /api/crawl/meta/usage rend votre position actuelle, pour qu’un client décide avant de dépenser :

pages_included_per_monthL’allocation du plan pour la période.
pages_used_this_period · pages_remainingOù vous en êtes.
per_job_page_capLe plafond appliqué à un seul crawl.
max_concurrent_crawlsCombien peuvent tourner en même temps.
overage_price_micros_per_pageCe que coûte une page au-delà de l’allocation.
browser_page_units · ocr_page_unitsLes multiplicateurs de poids, pour que votre estimation colle à la facture.

usage.sh

curl https://api.usewrit.app/api/crawl/meta/usage \
  -H "Authorization: Bearer $WRIT_API_KEY"

keyless ▸ sans compte

Sans compte ? Une page à la fois.

Le palier keyless lit des pages publiques sans compte ni clé. Un appareil est identifié par un en-tête d’identifiant client, et l’allocation est petite à dessein : elle existe pour qu’un SDK ou l’app desktop fonctionne avant l’inscription.

AppelCe qu’il fait
POST /v1/keyless/scrapeMarkdown complet d’une page publique. Coûte 1 requête et 1 page.
POST /v1/keyless/mapJusqu’à 200 URL d’un site. Coûte 1 requête, 0 page.
GET /v1/keyless/quotaCe qu’il reste. Ne dépense rien.
POST /v1/keyless/crawlToujours 402 api_key_required — crawler un site entier demande un compte.

Les plafonds

  • 10 requêtes et 20 pages par jour, par appareil.
  • 30 requêtes par jour et 10 par minute, par adresse IP.
  • Une carte rend au plus 200 URL.
  • Au-delà de l’un d’eux : 429 keyless_rate_limited.

Chaque appel keyless porte X-Writ-Client-Id — un identifiant d’appareil stable. Les SDKs et l’app desktop le posent pour vous ; un appel sans lui répond 400 client_id_required.

clés ▸ scopes

Trois scopes, volontairement inégaux.

Lire une page est une chose plus petite que crawler un site, donc c’est un scope plus petit. Une clé confiée à un partenaire pour des lectures une-page ne peut pas démarrer un crawl sur votre allocation.

ScopeCe qu’il donne
crawl:executeDémarrer et annuler des crawls ; créer, modifier et exécuter des crawls enregistrés.
crawl:readLister et lire les crawls, les crawls enregistrés et leurs données collectées.
scrape:executeLectures une-page, carte et preview. Séparé, et moindre.

mcp ▸ outils

Le même crawl, en outils MCP.

Le serveur MCP desktop expose la surface de crawl en outils, pour qu’un client IA lance et relance un crawl sans que vous écriviez le moindre HTTP :

OutilCe qu’il fait
writ_crawl_siteurl, extract (markdown | schema), extract_schema, max_pages, max_depth, include[], exclude[], same_domain, allow_subdomains, content{}, persona, save_as, max_age.
writ_crawl_statusOù en est un crawl en cours.
writ_saved_crawlsLes crawls enregistrés que vous pouvez appeler par leur nom.
writ_run_saved_crawlcrawl, max_age, limit — le contrat de fraîcheur, en outil.
writ_saved_crawl_dataCe qu’un crawl enregistré a déjà collecté. Ne crawle jamais.
writ_scrape · writ_mapUne page, ou la liste d’URL.

Deux comportements à connaître. Réutiliser un nom save_as met à jour ce crawl enregistré au lieu d’en créer un second — un client IA qui affine son crawl vous laisse donc une définition, pas douze. Et max_age n’a de sens qu’avec save_as : sans crawl enregistré, il n’y a pas d’exécution précédente à réutiliser.

crawl tools

// Crawl a site and save it under a callable name in one turn.
writ_crawl_site {
  "url": "https://example.com/docs",
  "extract": "markdown",
  "max_pages": 500,
  "max_depth": 3,
  "include": ["^/docs/"],
  "exclude": ["^/docs/legacy/"],
  "same_domain": true,
  "allow_subdomains": false,
  "content": { "preset": "article", "exclude_selectors": ["nav", "footer"] },
  "save_as": "docs-index"
}

// Re-using a save_as name UPDATES that saved crawl — it does not duplicate it.
// max_age only matters together with save_as.
writ_run_saved_crawl { "crawl": "docs-index", "max_age": 86400, "limit": 50 }
writ_saved_crawl_data { "crawl": "docs-index" }
writ_saved_crawls {}
writ_crawl_status { "crawl_id": 8811 }
writ_map  { "url": "https://example.com" }
writ_scrape { "url": "https://example.com/pricing" }

faq

Questions crawl, répondues.

Quelle différence entre le plafond de pages par crawl et l’allocation mensuelle ?
Ils répondent à deux questions différentes. Le plafond par crawl est le nombre maximal de pages qu’un seul job peut visiter — 25 000 sur Pro. L’allocation mensuelle est le nombre de pages de crawl que votre plan inclut sur tous les jobs de la période — 75 000 sur Pro. Un page_budget au-dessus du plafond est ramené au plafond et le crawl s’exécute quand même ; c’est l’allocation mensuelle qui, une fois épuisée, commence à prélever le wallet.
Comment éviter de payer un crawl que j’ai déjà lancé ?
Enregistrez le crawl comme définition et appelez-le avec max_age. Si la dernière exécution terminée est dans cette fenêtre, vous recevez 200 avec cached: true et les données en ligne — rien n’est crawlé, rien n’est décompté. Si vous ne voulez que ce qui existe déjà, appelez plutôt l’endpoint data de la définition : c’est une lecture pure, à tout âge.
Mon appel wait:true a rendu 504. Ai-je perdu les pages ?
Non. Un 504 ici signifie que le crawl a dépassé votre timeout, pas qu’il a échoué. Le corps porte toujours crawl_id et status_url : interrogez ce handle jusqu’à un statut terminal et récupérez les données. Relancer l’exécution démarrerait un second crawl et paierait deux fois.
Pourquoi les booléens d’un crawl reviennent-ils en 0 et 1 ?
C’est la forme documentée : des champs comme same_domain, respect_robots, cancel_requested et is_terminal sont sérialisés en entiers plutôt qu’en booléens JSON. Comparez-les numériquement, ou convertissez-les une fois à la frontière de votre client.
Les PDF et les pages scannées sont-ils inclus ?
Oui, sur la flotte Writ Cloud et sur les crawls auto-hébergés : PDF, fichiers Word, Excel et PowerPoint, images et pages scannées sont lus, avec OCR là où il n’y a pas de couche de texte, sous contrôle de ocr_mode. Un crawl cloud routé vers votre propre machine reliée ne fait pas d’extraction de documents. Les pages OCR comptent 2×.
Puis-je crawler un site entier sans compte ?
Non. Le palier keyless couvre une page publique et une carte d’URL ; POST /v1/keyless/crawl répond toujours 402 api_key_required. Crawler un site entier demande un compte, parce qu’il faut une allocation à décompter.

fin ▸ en lancer un

Prévisualisez le périmètre, puis lancez.

Le preview ne coûte rien et montre exactement quelles URL un crawl garderait. C’est la façon la moins chère d’être sûr avant un gros budget de pages.