# Import des listes Mangools depuis le parcours d'entrée

**Date** : 2026-07-28
**Branche** : `feature/onboarding-mangools-lists`
**Statut** : direction validée, forme de la réponse de listage **vérifiée sur un compte réel**. Une
inconnue subsiste sur la réponse détaillée (voir la fin de la section suivante).

## Le problème

L'étape 3 du parcours d'entrée — peupler le réservoir de mots-clés — n'offre aujourd'hui que deux
chemins : déposer un fichier CSV, ou saisir un mot-clé à la main. Un utilisateur qui a déjà fait sa
recherche dans Mangools doit donc exporter un CSV puis le redéposer, alors que ses listes sont là,
à un appel d'API.

C'est la page blanche du parcours : le moment où l'on demande à quelqu'un qui découvre le produit
de produire lui-même la matière première.

## La forme réelle de la réponse — relevée, pas supposée

**La documentation publiée est tronquée.** Elle décrit `GET /kwfinder/lists` comme rendant
`{ name, user_id, keyword_ids, created_at }`, sans identifiant — ce qui rendait
`GET /kwfinder/lists/{list_id}` inutilisable, puisqu'il en exige un. Relevé sur un compte réel le
2026-07-28, la réponse contient bien davantage :

```json
[
  {
    "_id": "67db8efb553d96b7dff08eef",
    "name": "Advmfr",
    "user_id": "675679b76aee08cdea6d04e0",
    "creator": "675679b76aee08cdea6d04e0",
    "created_at": 1742442235000,
    "updated_at": 1742444431000,
    "__v": 0,
    "keyword_ids": ["3fbd0852…", "dd9d5a21…", …]
  }
]
```

Trois écarts avec la documentation, dont deux sont des pièges :

| | Documentation | Réel |
|---|---|---|
| Identifiant | absent | **`_id`** présent — le chantier est constructible |
| Horodatage | `1590868935` (secondes) | `1742442235000` (**millisecondes**) |
| Champs | 4 | 8, dont `updated_at` et `creator` |

**Les horodatages sont en millisecondes.** Les traiter comme des secondes, ce que la documentation
suggère, daterait chaque liste de 1970. À vérifier par un test sur la valeur réelle, pas sur
l'exemple publié.

**Le volume est connu dès le listage**, par `keyword_ids.length`. Sur le compte de référence, les
six listes vont de **21 à 2106 mots-clés** — l'écart est tel que le mode de traitement ne peut pas
être décidé au jugé.

**Conséquence de conception** : la réponse de listage porte le tableau complet des identifiants de
mots-clés, soit près de cent kilooctets pour six listes. On n'en utilise que la longueur. Ne pas la
faire transiter jusqu'au client — la projection (`_id`, `name`, nombre, dates) se fait côté serveur.

### La réponse détaillée, relevée le même jour

`GET /kwfinder/lists/{list_id}` rend `{ list, keywords }` — l'enveloppe de liste identique au
listage, plus les mots-clés. Chacun porte :

```json
{
  "_id": "08b7c71d5b0b37413c69a028a787f388",
  "kw": "jewelry wholesale manufacturer",
  "lid": 2840,
  "location": { "_id": 2840, "code": "us", "label": "United States" },
  "sv": 210, "cpc": 2.83, "ppc": 91, "seo": 29,
  "msv": [[2024, 0, 320], …],
  "ts": 1784185097, "seo_ts": 1784006551
}
```

**Le pays se lit directement.** `location.code` (`"us"`) passe en majuscules pour rejoindre le
format des `targetCountries` du site. Aucune table de correspondance à écrire, aucun appel
supplémentaire à `/mangools/locations`.

**Une liste est mono-pays en pratique**, mais ce n'est pas garanti par l'API : sur la liste de
référence, les 21 mots-clés partagent le même `lid`. Rien n'empêche un utilisateur d'y mélanger des
localisations. La règle retenue : dériver le pays des mots-clés eux-mêmes, et **si plusieurs
localisations coexistent dans une liste, le dire à l'utilisateur** au lieu d'en choisir une.

**⚠️ Les unités d'horodatage diffèrent à l'intérieur de la même réponse.** L'enveloppe de liste
porte `created_at`/`updated_at` en **millisecondes** ; les mots-clés portent `ts` et `seo_ts` en
**secondes**. Vérifié : `1784185070000` et `1784185097` désignent le même instant. Traiter les deux
avec le même utilitaire produirait des dates en 1970 d'un côté ou en l'an 58 000 de l'autre.

**Le mappeur existant couvre déjà les mots-clés.** `mapKeywordItem` (`client.ts:91`) mappe
exactement `_id`, `kw`, `lid`, `sv`, `seo`, `cpc`, `ppc`, `msv` — la forme des mots-clés d'une liste
est celle de `keyword-imports`. `list()` le réutilise tel quel ; seul `location` s'y ajoute, et
seule l'enveloppe de liste demande un mappeur neuf.

## Décisions validées

**D1 — Le jeton Mangools se connecte à l'étape 3, au moment d'en avoir besoin.** Il se connectait
jusqu'ici à l'étape 4 (enrichissement), c'est-à-dire *après* l'import. Choisir « importer depuis
Mangools » sans jeton affiche le champ de connexion **sur place**, puis la liste des listes. On ne
renvoie pas l'utilisateur à une étape ultérieure pour revenir ensuite.

**D2 — Un thème par liste, créable sur place.** Chaque liste sélectionnée reçoit un thème, choisi
parmi ceux du site ou créé en tapant son nom, avec le nom de la liste Mangools proposé par défaut.
La création sur place n'est pas un confort : `keyword_themes` est une table **par site**, un site
neuf n'en a aucun, et sans création le champ serait vide et inutile.

## Ce que ça change dans le parcours

L'étape 3 passe de deux chemins à trois : **importer depuis Mangools**, déposer un CSV, saisir à la
main. Le troisième reste le filet, visuellement en retrait, comme aujourd'hui.

**L'étape 4 disparaît d'elle-même pour ceux qui passent par Mangools**, et sans une ligne de code
dédiée. `GET /kwfinder/lists/{list_id}` rend les mots-clés **avec leurs métriques** — `sv` (volume),
`cpc`, `seo` (difficulté), `ppc`, `msv` et l'historique. Or `enrich-keywords` est dérivé de
`keywords.volume_primary IS NOT NULL` : l'import remplit donc les deux drapeaux d'un coup, et
`mountableSteps` ne monte plus l'étape d'enrichissement. Elle reste en place pour ceux venus par
CSV ou par saisie.

C'est la même propriété émergente que celle qui fait rejouer le parcours à chaque création de site :
l'état dérivé décide, aucun cas particulier à écrire.

## Le client Mangools

`src/lib/mangools/client.ts` n'expose aujourd'hui que quatre appels : `relatedKeywords`, `serps`,
`keywordImports`, `searchLocations`. Il faut lui en ajouter deux :

| Méthode | Appel | Rend |
|---|---|---|
| `lists()` | `GET /kwfinder/lists` | `_id`, nom, nombre de mots-clés (`keyword_ids.length`), dates de création et de mise à jour — **en millisecondes** |
| `list(listId)` | `GET /kwfinder/lists/{list_id}` | `{ list, keywords }` — les mots-clés avec métriques et `location`, mappés par le `mapKeywordItem` **existant** |

Les deux réutilisent le `request()` existant : elles héritent donc du throttle, de la reprise unique
sur 429 avec backoff borné, du délai de 20 s et de la traduction des statuts en `MangoolsError`.
**Ne pas contourner ce chemin** — c'est lui qui empêche un rafale d'appels de se faire limiter.

## Deux pièges à traiter explicitement

**Le pays vient des mots-clés, pas d'un repli.** L'enveloppe de liste ne porte aucune localisation ;
chaque mot-clé porte la sienne, en clair (`location.code`). La règle : le pays de l'import est
celui des mots-clés. S'il **diffère des pays cibles du site**, on le signale avant d'importer plutôt
que d'absorber l'écart — c'est ainsi qu'on évite de reproduire le couple « langue fr » + « pays US »,
que ce projet a déjà corrigé deux fois. Si une même liste mélange plusieurs localisations, on le dit
aussi : choisir à la place de l'utilisateur serait deviner.

**Une liste volumineuse ne passe pas par une Server Action.** Les Server Actions de ce projet
rejettent au-delà d'un mégaoctet, et l'import CSV emprunte déjà pour cette raison un *route handler*
dédié avec traitement en arrière-plan (`/keywords/import-heavy`). L'import Mangools doit suivre le
même chemin. Le nombre de mots-clés est connu **avant** l'appel détaillé, par la longueur de
`keyword_ids` : on peut donc décider du mode sans découvrir le volume en cours de route.

## Ce que l'utilisateur voit

1. **Sans jeton** — trois cartes de choix. « Importer depuis Mangools » ouvre un champ de jeton avec
   le lien vers la page qui le délivre, et la sortie de l'étape reste disponible.
2. **Jeton connecté, aucune liste** — on le dit sans prétendre qu'il n'en a pas : un échec d'appel et
   un compte réellement vide sont deux choses différentes, distinction déjà tenue par l'étape
   Search Console et à reprendre ici.
3. **Listes affichées** — nom, nombre de mots-clés, date. Cases à cocher. Pour chaque liste cochée,
   un champ de thème pré-rempli avec le nom de la liste.
4. **Import lancé** — retour immédiat, traitement en arrière-plan, et l'étape n'avance que si au
   moins un mot-clé a réellement été inséré. Un import à zéro ligne laisse l'écran en place avec son
   message : c'est le défaut corrigé sur l'import CSV, à ne pas réintroduire.

## Hors périmètre

- **`GET /kwfinder/suggested-keywords`** — rend des suggestions à partir d'une simple URL. C'est la
  vraie réponse à la page blanche pour un site sans aucun mot-clé, et elle mérite son propre
  chantier plutôt qu'un ajout ici.
- **`GET /kwfinder/limits`** — quotas restants. Permettrait d'avertir *avant* un import volumineux
  plutôt qu'après un refus. Amélioration nette, mais séparable.
- **`GET /kwfinder/competitor-keywords`** — mots-clés d'un domaine concurrent. Hors du parcours
  d'entrée.
- **SE-Ranking** — reste hors du parcours guidé : son accès est métré en crédits, et déclencher une
  dépense pendant la découverte du produit serait une embuscade. Décision prise et vérifiée en
  source lors du chantier précédent.

## Tests

- Les deux nouvelles méthodes du client : réponse nominale, tableau vide, 401, 429 avec reprise.
- **Les deux unités d'horodatage**, sur les valeurs réelles relevées et non sur l'exemple publié :
  `1784185070000` doit donner mars 2026, `1784185097` aussi.
- La résolution du pays : accord, désaccord avec les pays cibles du site, et liste mélangeant
  plusieurs localisations.
- Le seuil de bascule vers le traitement en arrière-plan, décidé sur `keyword_ids.length`.
- La création de thème sur place, et le cas du site sans aucun thème.
- L'import à zéro ligne insérée ne fait pas avancer l'étape.

Convention du dépôt : ni simulation de clic ni DOM. Les décisions vivent dans un module pur testé à
part — voir `gsc-step-logic.ts`, `keywords-step-logic.ts`, `enrich-step-logic.ts`.

## Annexe — surface de l'API Mangools v3

Relevé fourni le 2026-07-28. Les appels déjà utilisés par le produit sont marqués.

| Méthode | Chemin | Usage |
|---|---|---|
| GET | `/kwfinder/related-keywords` | **utilisé** |
| GET, POST | `/kwfinder/competitor-keywords` | backlog |
| POST | `/kwfinder/keyword-imports` | **utilisé** |
| GET | `/serpchecker/serps` | **utilisé** |
| GET | `/kwfinder/competitor-domain` | — |
| GET | `/kwfinder/suggested-keywords` | backlog, fort intérêt |
| POST | `/kwfinder/keywords` | export CSV |
| GET | `/siteprofiler/requests` | — |
| GET | `/kwfinder/trends` | — |
| GET | `/kwfinder/kd/url-metrics` | — |
| GET | `/kwfinder/kd/requests` | — |
| POST | `/kwfinder/gap-analysis` | — |
| GET | `/kwfinder/limits` | backlog |
| GET, POST | `/kwfinder/lists` | **ce chantier** |
| GET, PATCH, DELETE | `/kwfinder/lists/{list_id}` | **ce chantier** (GET) |
| POST, DELETE | `/kwfinder/lists/{list_id}/keyword` | — |
| GET | `/mangools/locations` | **utilisé** |
| GET | `/mangools/locations/{location}` | — |
