~/blog / migrer-api-pipedrive-v1-v2
⚙️ craft & architecture

Migrer une API tierce en plein vol : de Pipedrive v1 à v2 sans tout réécrire

Luc Del Beato 11 juin 2026 11 min de lecture

Un SaaS B2B en production, des dizaines de clients qui s'en servent tous les jours, et au cœur du métier : des appels directs à l'API Pipedrive. Puis Pipedrive sort une v2, plus stricte, sans tuer la v1. Le big-bang « on réécrit tout ce week-end » est exclu. Voici comment j'ai migré sans réécrire une ligne de logique métier.

TL;DR

Le contexte : une API CRM tissée dans tout le produit

Le produit est un SaaS B2B qui s'appuie sur Pipedrive comme CRM de fond. Pas « une intégration optionnelle » : le cœur du métier crée et met à jour des deals, des persons, des organizations et une bonne dizaine de custom fields propres au client. Et ces appels partent de partout : du backend pour les automatisations, mais aussi directement du frontend pour les actions utilisateur en temps réel.

Quand Pipedrive a publié son API v2 à côté de la v1, le message était clair : la v1 finira par disparaître. La v2 est mieux pensée, plus cohérente, et surtout beaucoup plus stricte. Le problème : on ne peut pas mettre le produit en maintenance le temps de tout réécrire. Les clients bookent, vendent, relancent leurs prospects en ce moment même. La migration doit se faire à chaud, par petits incréments, sans casser ce qui tourne.

La vraie contrainte n'était pas technique, elle était temporelle : migrer un système que personne n'a le droit d'arrêter. On ne change pas le moteur d'un avion en vol, on construit une nacelle autour.

Les pièges de la rigueur v2

La v1 était permissive. Trop. Elle « devinait » ce qu'on voulait dire : une chaîne vide pour un champ numérique ? Elle l'avalait. Une option de liste passée à plat ? Elle s'en arrangeait. La v2 refuse tout ça, et chaque tolérance perdue est un piège à découvrir :

Pris isolément, chacun est anodin. Pris ensemble, dispersés dans des dizaines de points d'appel répartis entre front et back, ils forment un champ de mines. La tentation naturelle, corriger chaque appel sur place, est exactement le mauvais réflexe.

La stratégie : une couche de normalisation à la frontière

Le principe directeur tient en une phrase : abstraire la différence de version à la frontière I/O, pas dans la logique métier. Le métier ne doit jamais savoir qu'il existe une v1 et une v2. Il continue de parler une seule forme, celle qu'il connaît déjà, et seul l'adaptateur, au bord du système, traduit dans les deux sens.

C'est un anti-corruption layer au sens DDD : un sas qui empêche les bizarreries d'un système externe de contaminer le vôtre. Concrètement, trois fonctions de traduction, toutes posées sur le seul chemin par lequel les données entrent et sortent de Pipedrive :

La version cible est lue depuis la config, par endpoint. L'adaptateur regarde « cet endpoint est-il en v1 ou en v2 ? » et applique la bonne transformation. On peut donc basculer un endpoint à la fois, valider en prod, puis passer au suivant, exactement l'incrémentalité qu'imposait la contrainte.

💡
Le détail qui change tout : en concentrant toute la connaissance « v1 vs v2 » dans l'adaptateur, on transforme une migration tentaculaire en un seul fichier à comprendre. Le jour où la v1 meurt vraiment, on supprime une branche du if, pas une chasse au trésor dans cinquante fichiers métier.

Avant / après : un payload que la v2 refuse

Voici le genre de payload que la v1 avalait sans broncher et que la v2 rejette d'un 400. À gauche le brut, à droite ce que la couche de normalisation produit :

// ❌ brut, refusé par la v2
{
  "title": "Deal Acme",
  "value": 4990, "currency": "EUR",        // monétaire à plat
  "a1b2c3_close_date": "2026-06-11T00:00:00Z", // date non stricte
  "d4e5f6_segment": "3",                    // multi-option à plat
  "vin": "",                                // chaîne vide → champ numérique
  "g7h8i9_notes": null                      // null rejeté
}

// ✅ normalisé, accepté par la v2
{
  "title": "Deal Acme",
  "value": { "value": 4990, "currency": "EUR" }, // forme monétaire
  "a1b2c3_close_date": "2026-06-11",             // YYYY-MM-DD strict
  "d4e5f6_segment": [3]                          // tableau d'IDs (entiers)
  // vin et g7h8i9_notes : OMIS, pas mis à null
}

La règle qui sous-tend la transformation : un champ qu'on ne peut pas remplir proprement, on l'omet. On ne le force pas à null, on ne lui colle pas une chaîne vide. Absence de clé = « ne touche pas à ce champ ». C'est la sémantique exacte qu'attend la v2.

L'anecdote : un vin: "" qui partait dans le vide

Le bug le plus instructif a été le plus bête. Quelque part dans le code, on envoyait vin: "" vers un champ de type texte court quand le véhicule n'avait pas de numéro de châssis. En v1 : aucun problème, champ vide, on passe. En v2 : 400, et comme l'erreur remontait au milieu d'un lot d'appels, elle se perdait dans le bruit.

Le correctif n'était pas « corriger ce champ », c'était exclure systématiquement les champs vides à la frontière. Et la façon de l'attraper avant qu'il ne casse en prod : confronter chaque champ envoyé à la doc officielle de migration v2 de Pipedrive. Pas deviner, vérifier, type attendu par type attendu.

📋
L'audit avant la bascule : j'ai construit un document de référence qui mappe chaque hash de custom field (les clés cryptiques type a1b2c3…) à son type v2 attendu, numérique, date, monétaire, set, enum. Rien ne part « à l'aveugle ». C'est le document qu'on lit en même temps que le code de normalisation pour garantir qu'ils sont d'accord.

La pagination par curseur, isolée elle aussi

Le passage de offset/limit à un curseur opaque est typiquement le genre de changement qui, s'il fuite dans le métier, le pourrit. Chaque endroit qui faisait for (start = 0; …; start += limit) aurait dû apprendre l'existence d'un curseur. Mauvaise idée.

À la place, fetchAllPagesV2() absorbe toute la mécanique : elle appelle l'endpoint, lit le curseur de la réponse, rappelle avec ce curseur, accumule, et s'arrête quand il n'y a plus de page suivante. Le métier reçoit une liste complète, exactement comme avant. Il ne sait même pas que la pagination a changé de nature.

Ce que je retiens

Cette discipline, isoler le changement à la frontière, laisser le cœur du système intact, auditer avant d'agir, c'est exactement la même que j'applique aux intégrations d'agents IA en production. Là aussi, un modèle ou un fournisseur change sous vos pieds ; et là aussi, c'est l'adaptateur qui doit encaisser le choc, jamais le métier.

// parlons-en

Une migration d'API risquée sur un SaaS en prod ?

Migrer un système que personne n'a le droit d'arrêter, ça se prépare. Si vous avez une API tierce qui durcit, une intégration tissée partout, ou juste un doute sur la stratégie, parlons-en avant le big-bang.

Discutons de votre migration
L
Luc Del Beato

Senior Lead Engineer, ~20 ans de web. Do-er passionné de résolution de problèmes, de belle architecture et d'automatisation ; les agents IA, c'est ma direction. Mon parcours →