# Scrapers API · documentation

> Cette page s'adresse à un assistant de code (Claude Code ou autre) comme à un humain. Tout ce qu'il faut pour
> lancer un scrape et récupérer le résultat est ici. L'API est **asynchrone** : on lance une tâche, on attend
> qu'elle finisse, on télécharge le CSV.

## Les scrapers

| Scraper | Ce qu'il sort | Sa puissance |
|---|---|---|
| `google_maps` | Tous les établissements d'une activité dans une zone : téléphone, site, note, avis, adresse, concurrents à 500 m | Une ville entière quadrillée, plus de 16 000 restaurants à Paris en une tâche. Téléphone sur 95 % des fiches, site sur 79 % |
| `france_travail` | Les offres d'emploi publiques : entreprise, salaire, métier ROME, secteur, effectif, SIREN | 1 000 offres avec fiche complète en 15 s, toute la France département par département |
| `indeed` | Les offres d'emploi Indeed France : entreprise, site, effectif, chiffre d'affaires, dirigeant, salaire, ATS | 1 000 offres en 7 s, la France entière (120 000 offres) en 5 min |
| `welcome_to_the_jungle` | Les offres, ou les entreprises qui recrutent : domaine, salaire, pile technique, ATS, rythme d'embauche | Domaine trouvé sur 93 % des entreprises, 1 000 offres enrichies en 40 s |

Les offres Indeed et Welcome to the Jungle arrivent avec des signaux déjà calculés : poste qui traîne, salaire face au
marché, logiciels cités dans l'offre.

## En bref

- **Base** : `https://api.178-104-155-46.sslip.io`. Toutes les routes commencent par `/v1/`. Spécification OpenAPI : `https://api.178-104-155-46.sslip.io/openapi.json`.
- **Clé** : en-tête `Authorization: Bearer sk_…` sur chaque appel `/v1/`. Ne la mettez jamais dans une URL, un
  fichier versionné ou un message public.
- **Déroulé** :
  1. `POST /v1/jobs` avec le scraper, ses paramètres et un plafond de lignes → `202`, un `id`.
  2. `GET /v1/jobs/{id}` toutes les 15 à 30 s, jusqu'à `statut` = `termine`, `partiel` ou `echec`.
  3. `GET /v1/jobs/{id}/resultat` → le CSV (ou `?format=json`).
- **Durée** : de quelques secondes à quelques minutes selon le volume. Au-delà de la durée maximale de la clé, ce qui
  est déjà sorti est rendu, statut `partiel`.

```bash
export API=https://api.178-104-155-46.sslip.io
export CLE=sk_...

# 1. lancer
curl -s -X POST "$API/v1/jobs" -H "Authorization: Bearer $CLE" -H "content-type: application/json" \
  -d '{"scraper":"google_maps","params":{"requetes":["plombier lyon"]},"max_lignes":100}'
# → {"id":"9f2c…","statut":"en_attente",…}

# 2. suivre
curl -s "$API/v1/jobs/9f2c…" -H "Authorization: Bearer $CLE"

# 3. récupérer
curl -s "$API/v1/jobs/9f2c…/resultat" -H "Authorization: Bearer $CLE" -o plombiers-lyon.csv
```

## Limites

Les limites dépendent de la clé : `GET /v1/quota` les donne toutes, dans `limites`.

| Limite | Valeur |
|---|---|
| Lignes par jour | votre quota, remis à zéro à minuit UTC |
| Lignes par tâche (`max_lignes`) | 500 par défaut, jusqu'à `limites.lignes_par_tache`, plafonné à ce qui reste du quota du jour |
| Tâches en attente ou en cours | `limites.taches_en_file` |
| Scrapes en même temps | `limites.scrapes_simultanes`, les autres attendent leur tour |
| Appels par minute | `limites.appels_par_minute` |
| Durée d'une tâche | `limites.duree_max_min` minutes, puis résultat partiel |
| Corps de requête | 32 Ko |

Le quota est **réservé** au lancement (`max_lignes`) et **débité** de ce qui est réellement sorti à la fin. Demandez
donc le plafond dont vous avez besoin, pas plus : une tâche à 5 000 bloque 5 000 lignes de quota tant qu'elle tourne.

## Routes

| Méthode | Route | Rôle |
|---|---|---|
| GET | `/docs` | la documentation (sans clé) ; `/docs.md` en texte, `/openapi.json` en OpenAPI 3.1 |
| GET | `/v1/scrapers` | les scrapers disponibles |
| GET | `/v1/quota` | quota du jour, utilisé, réservé, restant |
| POST | `/v1/jobs` | lancer une tâche |
| GET | `/v1/jobs` | vos 50 dernières tâches |
| GET | `/v1/jobs/{id}` | état d'une tâche |
| GET | `/v1/jobs/{id}/resultat` | le CSV (`?format=json` : tableau d'objets) |

### Lancer une tâche — `POST /v1/jobs`

```json
{ "scraper": "france_travail", "params": { … }, "max_lignes": 500 }
```

Réponse `202` :

```json
{ "id": "9f2c…", "scraper": "france_travail", "statut": "en_attente", "max_lignes": 500, "lignes": null,
  "cree_le": "…", "debut_le": null, "fini_le": null, "erreur": null, "resultat": null }
```

`statut` : `en_attente` → `en_cours` → `termine` (fini) | `partiel` (arrêté avant la fin, résultat partiel
disponible) | `echec` (aucun résultat ; `erreur` dit pourquoi). Quand `resultat` n'est pas `null`, c'est la route du CSV.

Un paramètre inconnu ou hors bornes est refusé en `400` avec un message qui nomme le champ : corrigez et relancez.

### Codes d'erreur

| Code | Sens |
|---|---|
| 400 | paramètres invalides (le message dit lequel) |
| 401 | clé absente ou invalide |
| 404 | route ou tâche inconnue |
| 409 | résultat demandé avant la fin de la tâche |
| 429 | quota du jour épuisé, trop de tâches en cours, ou trop d'appels |
| 500 | erreur interne : réessayez un peu plus tard |

---

## Les scrapers

### `google_maps` — établissements par activité et lieu

Tous les établissements d'une activité, d'une rue à une ville entière, avec téléphone, site, note et position face
aux concurrents du quartier. Une ligne par établissement, sans doublon.

`params.requetes` : 1 à 20 recherches, chacune sous l'une de ces formes :

| Forme | Exemple | Quand |
|---|---|---|
| texte « activité + lieu » | `"boulangerie lyon 7"` | le plus simple ; jusqu'à ~240 résultats par recherche |
| autour d'un point | `{ "q": "plombier", "lat": 45.764, "lng": 4.8357, "rayon_m": 15000 }` | rayon de 500 à 50 000 m (5 000 par défaut) |
| toute une ville | `{ "q": "restaurant", "ville": "Paris", "pas_m": 800 }` | exhaustif sur une ville ; `pas_m` 400 à 3 000 (800 par défaut), plus petit = plus complet et plus lent |

Pour être exhaustif sur une grande ville, utilisez la forme « toute une ville » plutôt qu'un texte.

```json
{ "scraper": "google_maps", "max_lignes": 2000,
  "params": { "requetes": [ { "q": "salle de sport", "ville": "Lyon" }, "salle de sport villeurbanne" ] } }
```

Colonnes : `nom`, `categorie`, `categories`, `adresse`, `code_postal`, `ville`, `dans_ville`, `telephone`,
`telephone_intl`, `website`, `domaine`, `reseau_social`, `note`, `avis`, `statut`, `lat`, `lng`, `rang_local`,
`rang_recherche`, `groupe_taille` (nombre d'établissements de la même enseigne dans le résultat), `groupe_cle`,
`concurrents_500m`, `note_moy_concurrents_500m`, `ecart_note_concurrents`, `meilleur_concurrent_500m`, `place_id`,
`cid`, `maps_url`.

### `france_travail` — offres d'emploi France Travail

Le premier site d'emploi public de France, offre par offre, avec la fiche complète de l'employeur. Une ligne par offre.

`params.recherches` : 1 à 10 recherches `{ lieux, motsCles?, emission?, typeContrat? }`.

| Champ | Valeurs |
|---|---|
| `lieux` (obligatoire) | un code ou une liste de codes : département `69D` (Corse `2AD`, `2BD` ; outre-mer `971D`…), région `84R`, commune (code INSEE) `75056`, France entière en une recherche `01P`, ou `France` (département par département, le plus exhaustif) |
| `motsCles` | texte libre, ex. `"comptable"` |
| `emission` | offres publiées depuis 1, 3, 7, 14 ou 31 jours |
| `typeContrat` | `CDI`, `CDD`, `MIS` (intérim), `SAI` (saisonnier), `LIB` (libéral), `FRA` (franchise) |

`params.detail` (par défaut `true`) : fiche complète de chaque offre (descriptif, salaire, effectif, secteur, site).
`false` = beaucoup plus rapide, colonnes de la liste seulement.
`params.siren` (par défaut `false`) : ajoute le SIREN quand le nom de l'entreprise correspond exactement au registre.

```json
{ "scraper": "france_travail", "max_lignes": 1000,
  "params": { "recherches": [ { "lieux": ["75D", "92D"], "motsCles": "comptable", "emission": 7 } ] } }
```

Colonnes : `id`, `intitule`, `entreprise`, `departement`, `commune`, `contrat`, `temps_travail`, `publie_le`,
`origine`, `rome_code`, `salaire_texte`, `salaire_annuel_moyen`, `qualification`, `secteur_activite`,
`effectif_texte`, `descriptif`, `competences`, `site_entreprise`, `domaine`, `modifie_le`, `page_employeur_url`,
`offres_ouvertes_entreprise`, `siren`.

### `indeed` — offres d'emploi Indeed France

Le plus gros volume d'offres du marché, enrichi de la fiche entreprise et des signaux de recrutement. Une ligne par
offre.

`params.requetes` : 1 à 20 recherches `{ quoi?, ou?, rayon_km?, jours? }` (au moins `quoi` ou `ou`).

| Champ | Valeurs |
|---|---|
| `quoi` | mot-clé du poste, ex. `"commercial"` ; vide = tous les postes |
| `ou` | ville, département ou région, ex. `"Lyon"` ; vide = toute la France |
| `rayon_km` | 0 à 100 (25 par défaut) |
| `jours` | offres des N derniers jours, 1 à 60 |

```json
{ "scraper": "indeed", "max_lignes": 500,
  "params": { "requetes": [ { "quoi": "développeur", "ou": "Toulouse", "jours": 7 } ] } }
```

Colonnes : `key`, `url`, `titre`, `entreprise`, `entreprise_url_indeed`, `website`, `domaine`, `effectif_indeed`,
`ca_indeed`, `dirigeant_indeed`, `secteur_indeed`, `ville`, `code_postal`, `departement`, `adresse`, `pays`,
`date_publication`, `age_jours`, `reposte_jours`, `contrat`, `temps_travail`, `teletravail`, `salaire_min`,
`salaire_max`, `salaire_unite`, `salaire_estime`, `salaire_annuel`, `attributs`, `lien_candidature`, `ats`,
`offres_entreprise_scrape`, puis les signaux (voir plus bas), `description`, `requete`, `recupere_le`.

### `welcome_to_the_jungle` — offres ou entreprises Welcome to the Jungle

Les startups et scale-ups qui recrutent, avec leur stack, leur ATS et leur rythme d'embauche.

`params.mode` : `offres` (défaut, une ligne par offre) ou `entreprises` (une ligne par entreprise qui recrute).
`params.enrichir` (défaut `true`) : site web, pile technique, ATS, rythme de recrutement. `false` = plus rapide.

`params.requetes` : 1 à 20 recherches, texte (`"data analyst"`) ou objet :

| Champ | Valeurs |
|---|---|
| `q` | mots-clés |
| `pays` | code ISO : `FR`, `US`, `GB`… |
| `ville`, `region`, `departement` | valeur telle que sur le site, sans accent, souvent en anglais |
| `contrat` | `full_time`, `internship`, `temporary`, `freelance`, `apprenticeship`, `other` |
| `remote` | `no`, `partial`, `punctual`, `unknown` |
| `metier` | famille de métier : `tech-engineering`, `sales-customer-service`, `marketing-communication`… |
| `secteur`, `langue` | filtres libres |
| `entreprise` | slug ou URL de la page entreprise (`"doctolib"`) : toutes ses offres |
| `depuis_jours` | offres publiées depuis N jours (1 à 365) |
| `salaire_min` | salaire annuel minimum |
| `index` | `fr` (défaut) ou `en` |

Chaque champ texte accepte une liste (= OU). Une valeur inconnue ne renvoie aucune ligne.

```json
{ "scraper": "welcome_to_the_jungle", "max_lignes": 300,
  "params": { "mode": "entreprises", "requetes": [ { "q": "sdr", "pays": "FR", "depuis_jours": 30 } ] } }
```

Colonnes, mode `offres` : `id`, `titre`, `url`, `entreprise`, `entreprise_slug`, `entreprise_url`, `effectif`,
`annee_creation`, `labels`, `secteurs`, `metier_categorie`, `metier_sous_categorie`, `contrat`, `teletravail`,
`salaire_min`, `salaire_max`, `salaire_annuel_min`, `experience_min_ans`, `niveau_etudes`, `publie_le`, `ville`,
`departement`, `region`, `pays`, `resume`, `missions`, `avantages`, `profil`, `site_web`, `domaine`,
`offres_ouvertes_entreprise`, `offres_30j_entreprise`, `pile_technique`, `salaire_annuel`, puis les signaux.

Colonnes, mode `entreprises` : `entreprise`, `entreprise_url`, `site_web`, `domaine`, `effectif`, `annee_creation`,
`secteurs`, `siege_ville`, `siege_pays`, `nb_bureaux`, `offres_ouvertes`, `offres_30j`, `offres_7j`,
`offres_pour_100_salaries`, `metiers_ouverts`, `contrats_ouverts`, `derniere_offre_le`, `titres_exemples`,
`outils_declares`, `pile_technique`, `nb_outils`, `ats`, `lien_candidature_exemple`, `url_offre_exemple`.

### Signaux (Indeed et Welcome to the Jungle, mode `offres`)

`jours_en_ligne`, `republiee`, `poste_qui_traine` (oui/non) et `poste_qui_traine_preuve`, `salaire_median_marche`,
`ecart_marche_pct`, `position_marche` (au-dessus / dans / en dessous du marché), `echelle_marche`
(département, région ou France), `outils_cites` et `outils_categories` (logiciels nommés dans l'offre),
`changement_outil`.

---

## Conseils pour l'assistant

- Commencez petit (`max_lignes` 20 à 50) pour vérifier que les paramètres donnent ce qu'on veut, puis élargissez.
- Avant un gros lancement, vérifiez `GET /v1/quota`.
- Ne relancez pas en boucle une tâche en `echec` : changez les paramètres ou attendez quelques minutes.
- Le CSV est en UTF-8, séparateur virgule, champs entre guillemets quand il le faut (les descriptions contiennent des
  retours à la ligne : lisez-le avec un vrai lecteur CSV).
