Conception d'API publique pratique pour les créateurs SaaS débutants : choisissez le versionnage, la pagination, les limites de taux, la doc et un petit SDK que vous pouvez livrer vite.

/v1/.... Quand un client vous envoie une requête en échec, vous voyez la version immédiatement. C’est aussi plus facile d’exécuter v1 et v2 côte à côte.\n\n### Qu’est‑ce qu’un changement cassant, vraiment ?\n\nUn changement est cassant si un client bien conçu peut cesser de fonctionner sans modifier son code. Exemples courants :\n\n- Renommer un champ (par exemple customer_id en customerId)\n- Changer le type d’un champ (string → number) ou sa signification\n- Supprimer un endpoint ou un champ de réponse sur lequel les clients comptent\n- Durcir une validation (avant optionnel, maintenant requis)\n- Changer les exigences d’auth ou les permissions par défaut\n\nUn changement sûr est celui que les anciens clients peuvent ignorer. Ajouter un nouveau champ optionnel est généralement sûr. Par exemple, ajouter plan_name à une réponse GET /v1/subscriptions ne cassera pas les clients qui lisent seulement status.\n\nRègle pratique : ne supprimez pas et ne réaffectez pas des champs dans la même version majeure. Ajoutez de nouveaux champs, conservez les anciens, et retirez‑les seulement lorsque vous êtes prêt à déprécier toute la version.\n\n### Une politique de dépréciation que vous pouvez suivre\n\nGardez‑la simple : annoncez les dépréciations tôt, retournez un avertissement clair dans les réponses, et fixez une date de fin. Pour une première API, une fenêtre de 90 jours est souvent réaliste. Pendant ce temps, maintenez v1, publiez une courte note de migration et assurez‑vous que le support peut indiquer une phrase claire : v1 fonctionne jusqu’à cette date ; voici ce qui change en v2.\n\nSi vous utilisez une plateforme comme Koder.ai, traitez les versions d’API comme des snapshots : envoyez des améliorations dans une nouvelle version, gardez l’ancienne stable, et ne la supprimez qu’après avoir donné aux clients le temps de migrer.\n\n## Des modèles de pagination prévisibles\n\nLa pagination est l’endroit où la confiance se gagne ou se perd. Si les résultats sautent entre deux requêtes, les gens cessent de faire confiance à votre API.\n\nUtilisez page/limit quand l’ensemble de données est petit, la requête simple, et que les utilisateurs consultent souvent la page 3 de 20. Utilisez la pagination par curseur lorsque les listes peuvent grossir, que de nouveaux éléments arrivent souvent, ou que l’utilisateur peut trier et filtrer beaucoup. La pagination par curseur garde la séquence stable même quand de nouveaux enregistrements apparaissent.\n\nQuelques règles pour maintenir la pagination fiable :\n\n- Définissez toujours un tri par défaut (par exemple : created_at desc).\n- Ajoutez un tie‑breaker (par exemple : id) pour que l’ordre soit déterministe.\n- Traitez la pagination comme partie du contrat : changer le tri plus tard est un changement cassant.\n- Retournez le minimum nécessaire pour continuer : items plus le curseur suivant (ou la page suivante).\n\nLes totaux sont délicats. Un total_count peut être coûteux sur de grandes tables, surtout avec des filtres. Si vous pouvez le fournir à faible coût, incluez‑le. Sinon, omettez‑le ou rendez‑le optionnel via un flag de requête.\n\nHere are simple request/response shapes.\n\njson\n// Page/limit\nGET /v1/invoices?page=2\u0026limit=25\u0026sort=created_at_desc\n\n{\n \"items\": [{\"id\":\"inv_1\"},{\"id\":\"inv_2\"}],\n \"page\": 2,\n \"limit\": 25,\n \"total_count\": 142\n}\n\n// Cursor-based\nGET /v1/invoices?limit=25\u0026cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDozMDowMFoiLCJpZCI6Imludl8xMDAifQ==\n\n{\n \"items\": [{\"id\":\"inv_101\"},{\"id\":\"inv_102\"}],\n \"next_cursor\": \"eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDoyNTowMFoiLCJpZCI6Imludl8xMjUifQ==\"\n}\n\n\n## Limites de taux et retries amicaux\n\nLes limites de taux protègent moins par sévérité que par disponibilité. Elles protègent votre application des pics de trafic, votre base de données des requêtes coûteuses trop fréquentes, et votre facture d’infrastructure des mauvaises surprises. Une limite est aussi un contrat : les clients savent à quoi ressemble une utilisation normale.\n\nCommencez simple et ajustez ensuite. Choisissez quelque chose qui couvre l’usage typique avec une marge pour des courtes rafales, puis observez le trafic réel. Si vous n’avez aucune donnée, une valeur sûre est par clé API : 60 requêtes par minute plus une petite tolérance de burst. Si un endpoint est beaucoup plus coûteux (recherche, exports), appliquez‑lui une limite plus stricte ou une règle de coût séparée plutôt que de pénaliser toutes les requêtes.\n\nQuand vous appliquez les limites, facilitez la tâche des clients pour qu’ils fassent les bons choix. Retournez un 429 Too Many Requests et incluez quelques en‑têtes standards :\n\n- X-RateLimit-Limit : le maximum autorisé dans la fenêtre\n- X-RateLimit-Remaining : combien il en reste\n- X-RateLimit-Reset : quand la fenêtre se réinitialise (timestamp ou secondes)\n- Retry-After : combien de temps attendre avant de réessayer\n\nLes clients doivent traiter le 429 comme une condition normale, pas comme une erreur à combattre. Un schéma de retry poli maintient les deux côtés heureux :\n\n- Attendre le Retry-After quand il est présent\n- Sinon utiliser un backoff exponentiel (par exemple 1s, 2s, 4s)\n- Ajouter un peu d’aléa (jitter) pour éviter que beaucoup de clients ne retentent en même temps\n- Limiter le temps d’attente maximal (par exemple 30–60s)\n\nExemple : si un client exécute une synchronisation nocturne qui sollicite fortement votre API, son job peut répartir les requêtes sur une minute et ralentir automatiquement en cas de 429 au lieu d’échouer complètement.\n\n## Erreurs, codes de statut et écritures sûres\n\nSi vos erreurs d’API sont difficiles à lire, les tickets de support s’accumulent vite. Choisissez une forme d’erreur unique et respectez‑la partout, y compris pour les 500. Un standard simple : code, message, details et un request_id que l’utilisateur peut coller dans un chat de support.\n\nVoici un petit format prévisible :\n\njson\n{\n \"error\": {\n \"code\": \"validation_error\",\n \"message\": \"Some fields are invalid.\",\n \"details\": {\n \"fields\": [\n {\"name\": \"email\", \"issue\": \"must be a valid email\"},\n {\"name\": \"plan\", \"issue\": \"must be one of: free, pro, business\"}\n ]\n },\n \"request_id\": \"req_01HT...\"\n }\n}\n\n\nUtilisez les codes HTTP de la même manière à chaque fois : 400 pour une saisie invalide, 401 quand l’auth est manquante ou invalide, 403 quand l’utilisateur est authentifié mais non autorisé, 404 quand une ressource est introuvable, 409 pour les conflits (doublon ou mauvais état), 429 pour les limites, et 500 pour les erreurs serveur. La cohérence vaut mieux que l’originalité.\n\nRendez les erreurs de validation faciles à corriger. Les indices au niveau des champs doivent pointer vers le nom exact du paramètre utilisé dans vos docs, pas vers une colonne interne. S’il y a une contrainte de format (date, monnaie, enum), indiquez ce que vous acceptez et montrez un exemple.\n\nLes retries sont le lieu où beaucoup d’APIs créent involontairement des doublons. Pour les POST importants (paiements, création de factures, envoi d’emails), supportez des clés d’idempotence pour que les clients puissent réessayer en toute sécurité.\n\n- Acceptez un en‑tête Idempotency-Key sur certains endpoints POST.\n- Stockez la clé avec le résultat pendant une courte période (par exemple 24 heures).\n- Sur une clé répétée, retournez la même réponse que pour la première requête.\n- Si la première requête est toujours en cours, retournez une réponse claire demandant de réessayer plutôt que de créer une seconde ressource.\n\nCet en‑tête évite beaucoup de cas d’angle douloureux quand les réseaux sont instables ou que les clients subissent des timeouts.\n\n## Scénario d'exemple : une petite API de facturation SaaS en pratique\n\nImaginez un SaaS simple avec trois objets principaux : projects, users et invoices. Un project a plusieurs users, et chaque project reçoit des factures mensuelles. Les clients veulent synchroniser les factures vers leur outil comptable et afficher la facturation de base dans leur propre app.\n\nUne v1 simple pourrait ressembler à ceci :\n\n\nGET /v1/projects/{project_id}\nGET /v1/projects/{project_id}/invoices\nPOST /v1/projects/{project_id}/invoices\n\n\nPuis survient un changement cassant. En v1, vous stockez les montants des factures comme un entier en cents : amount_cents: 1299. Plus tard, vous avez besoin de multi‑devise et de décimales, et vous voulez amount: "12.99" et currency: "USD". Si vous écrasez l’ancien champ, toutes les intégrations existantes cassent. Le versionnage évite la panique : conservez v1 stable, publiez /v2/... avec les nouveaux champs, et supportez les deux jusqu’à la migration des clients.\n\nPour lister les factures, utilisez une pagination prévisible. Par exemple : \n\n\nGET /v1/projects/p_123/invoices?limit=50\u0026cursor=eyJpZCI6Imludl85OTkifQ==\n\n200 OK\n{\n \"data\": [ {\"id\":\"inv_1001\"}, {\"id\":\"inv_1000\"} ],\n \"next_cursor\": \"eyJpZCI6Imludl8xMDAwIn0=\"\n}\n\n\nUn jour, un client importe des factures dans une boucle et atteint votre limite de taux. Plutôt que des échecs aléatoires, il reçoit une réponse claire :\n\n- 429 Too Many Requests\n- Retry-After: 20\n- un petit corps d’erreur comme { \"error\": { \"code\": \"rate_limited\" } }\n\nCôté client, l’application peut faire une pause de 20 secondes, puis reprendre à partir du même cursor sans tout retélécharger ni créer des doublons.\n\n## Plan pas à pas : un déploiement simple pour votre API v1\n\nUn lancement v1 se passe mieux si vous le traitez comme une petite release produit, pas comme un tas d’endpoints. L’objectif est simple : les gens peuvent s’appuyer dessus, et vous pouvez continuer à l’améliorer sans surprises.\n\n### Un plan pratique sur une semaine (même pour une toute petite équipe)\n\nCommencez par rédiger une page qui explique à quoi sert votre API et à quoi elle ne sert pas. Gardez la surface assez petite pour pouvoir l’expliquer à voix haute en une minute.\n\nSuivez cette séquence et n’avancez pas tant que chaque étape n’est pas suffisante :\n\n1. Rédigez une spec d’une page qui nomme vos ressources centrales et la première poignée d’endpoints que vous supporterez (pensez 5–10). Incluez le motif d’URL de base, la méthode d’auth et les en‑têtes requis.\n2. Pour chaque endpoint, écrivez une requête réaliste et une réponse réaliste. Utilisez les noms de champs que vous comptez livrer. Ces exemples deviennent vos premières docs et vos premiers tests.\n3. Ajoutez une courte section de règles : format des erreurs, fonctionnement de la pagination (si un endpoint de liste existe), quelles limites de taux, et quels champs sont stables vs susceptibles de changer.\n4. Faites un test interne avec un faux client. Faites comme si vous étiez un client construisant une intégration dans un nouveau repo. Chronométrez le temps pour obtenir le premier appel réussi, et notez où vous avez été confus.\n5. Publiez votre contrat v1 : ce qui est sûr (champs additifs), ce qui nécessite une nouvelle version, et combien de préavis vous donnerez pour les changements cassants.\n\nSi vous travaillez avec un flux qui génère du code (par exemple en utilisant Koder.ai pour scaffolder endpoints et réponses), faites quand même le test du faux client. Le code généré peut sembler correct tout en restant maladroit à l’usage.\n\nLe gain : moins d’emails au support, moins de hotfixes, et une v1 que vous pouvez réellement maintenir.\n\n## Livrer un petit SDK sans sur‑ingénierie\n\nUn premier SDK n’est pas un second produit. Considérez‑le comme une fine couche conviviale autour de votre API HTTP. Il doit faciliter les appels courants, mais ne doit pas masquer le fonctionnement de l’API. Si quelqu’un a besoin d’une fonctionnalité non couverte, il doit pouvoir retomber sur des requêtes HTTP brutes.\n\nChoisissez un langage pour commencer, en fonction de ce que vos clients utilisent réellement. Pour beaucoup d’APIs B2B, c’est souvent JavaScript/TypeScript ou Python. Livrer un SDK solide vaut mieux que trois SDK à moitié finis.\n\n### Ce que doit inclure un petit SDK\n\nUn bon ensemble de départ :\n\n- Gestion de l’auth (clé API ou token OAuth) en un seul endroit\n- Timeouts sensés et retries automatiques pour les requêtes sûres (GET), avec backoff\n- Helpers de pagination qui retournent un itérateur ou une fonction next page\n- Modèles request/response clairs (même des types basiques)\n- Une porte de secours pour les en‑têtes personnalisés et les appels HTTP bruts\n\nVous pouvez l’écrire à la main ou le générer depuis un OpenAPI spec. La génération est excellente quand votre spec est précise et que vous voulez du typage cohérent, mais elle produit souvent beaucoup de code. En début, un client minimal écrit à la main plus un fichier OpenAPI pour la doc suffit généralement. Vous pouvez passer à des clients générés plus tard sans casser les utilisateurs, tant que l’interface publique du SDK reste stable.\n\n### Versionnez le SDK séparément de l'API\n\nLa version de l’API doit suivre vos règles de compatibilité. La version du SDK doit suivre les règles de packaging.\n\nSi vous ajoutez des paramètres optionnels ou de nouveaux endpoints, c’est généralement une petite mise à jour du SDK. Réservez les versions majeures du SDK aux changements cassants dans le SDK lui‑même (méthodes renommées, changements de defaults), même si l’API est restée identique. Cette séparation maintient les montées de version calmes et réduit les tickets de support.\n\n## Erreurs courantes qui causent beaucoup de support\n\nLa plupart des tickets d’API ne viennent pas de bugs. Ils viennent de surprises. La conception d’API publique consiste principalement à être ennuyeux et prévisible pour que le code client continue de fonctionner mois après mois.\n\nLe moyen le plus rapide de perdre la confiance est de changer les réponses sans prévenir. Si vous renommez un champ, changez un type, ou commencez à renvoyer null là où vous renvoyiez une valeur, vous casserez des clients de manière difficile à diagnostiquer. Si vous devez vraiment changer le comportement, versionnez‑le, ou ajoutez un nouveau champ en conservant l’ancien pendant un certain temps avec un plan de retrait clair.\n\nLa pagination est un autre facteur récurrent. Les problèmes surviennent quand un endpoint utilise page/pageSize, un autre offset/limit, et un troisième curseurs, avec des valeurs par défaut différentes. Choisissez un modèle pour la v1 et appliquez‑le partout. Gardez aussi le tri stable, pour que la page suivante ne saute pas ou ne répète pas des éléments quand de nouveaux enregistrements arrivent.\n\nLes erreurs génèrent beaucoup d’échanges quand elles sont incohérentes. Un mode d’échec fréquent : un service retourne { "error": "..." } et un autre { "message": "..." }, avec des codes HTTP différents pour le même problème. Les clients se retrouvent alors à implémenter des handlers spécifiques à chaque endpoint.\n\nVoici cinq erreurs qui produisent les fils d’emails les plus longs :\n\n- Changements silencieux de champs (noms, types, signification) sans bump de version ni fenêtre de dépréciation\n- Règles de pagination qui varient selon l’endpoint ou changements de valeurs par défaut dans le temps\n- Formats d’erreur, codes de statut ou messages de validation différents entre endpoints\n- Absence de request IDs, empêchant de retrouver rapidement l’appel exact dans les logs\n- Limites de taux qui se déclenchent soudainement, sans en‑têtes, sans guide de retry clair et sans exemple de backoff\n\nUne habitude simple aide : chaque réponse devrait inclure un request_id, et chaque 429 devrait expliquer quand réessayer.\n\n## Checklist rapide et étapes suivantes\n\nAvant de publier quoi que ce soit, faites une passe finale axée sur la cohérence. La plupart des tickets de support surviennent parce que de petits détails diffèrent entre endpoints, docs et exemples.\n\nContrôles rapides qui attrapent le plus d’erreurs :\n\n- Nommage : noms de ressources cohérents, pluriels, et casse des champs (choisissez une convention et tenez‑vous y).\n- Exemples : chaque endpoint a un exemple réaliste de requête et de réponse, pagination incluse.\n- Erreurs : un format d’erreur clair, des codes d’erreur stables, et des messages utiles pour les échecs courants.\n- Limites : le comportement des rate limits est documenté et les réponses incluent les en‑têtes attendus.\n- Sécurité : idempotence pour les créations et gestion prévisible des timeouts.\n\nAprès le lancement, surveillez ce que les gens utilisent réellement, pas ce que vous espériez. Un petit tableau de bord et une revue hebdomadaire suffisent au début.\n\nSurveillez ces signaux en priorité :\n\n- Pics de 429 (qui est limité et pourquoi)\n- p95 de latence par endpoint (les endpoints lents masquent souvent des N+1)\n- Top endpoints et paramètres (votre vraie surface API)\n- Taux d’erreur par code (400 vs 401 vs 500)\n- Timeouts et retries (les clients peuvent boucler sans s’en rendre compte)\n\nCollectez les retours sans tout réécrire. Ajoutez un court chemin de report dans vos docs, et taggez chaque rapport avec l’endpoint, le request id et la version du client. Quand vous corrigez quelque chose, préférez les changements additifs : nouveaux champs, nouveaux paramètres optionnels, ou un nouvel endpoint, plutôt que de casser le comportement existant.\n\nÉtapes suivantes : rédigez une spec d’une page avec vos ressources, plan de versionnage, règles de pagination et format d’erreur. Puis produisez des docs et un petit SDK de démarrage couvrant l’auth et 2 à 3 endpoints centraux. Si vous voulez aller plus vite, vous pouvez esquisser la spec, les docs et un SDK de démarrage depuis un workflow piloté par chat avec des outils comme Koder.ai (son mode planning est pratique pour cartographier endpoints et exemples avant de générer du code).Commencez avec 5–10 points de terminaison qui correspondent aux actions réelles des clients.\n\nUne bonne règle : si vous ne pouvez pas expliquer une ressource en une phrase (ce que c’est, qui en est le propriétaire, comment elle est utilisée), gardez-la privée jusqu’à ce que l’usage vous indique le contraire.
Choisissez un petit ensemble de noms stables (ressources) que les clients utilisent déjà dans leur vocabulaire, et gardez ces noms stables même si votre base de données interne change.\n\nExemples courants pour un SaaS : users, organizations, projects et events — n’ajoutez le reste que s’il y a une demande claire.
Utilisez les significations standard et soyez cohérent :\n\n- GET = lecture (aucun effet de bord)\n- POST = création ou démarrage d’une action\n- PATCH = mise à jour partielle\n- DELETE = suppression ou désactivation\n\nLe principal avantage est la prévisibilité : les clients ne doivent pas deviner ce qu’une méthode fait.
Privilégiez la version dans l'URL, par exemple /v1/....\n\nC’est plus simple à voir dans les logs et les captures d’écran, plus facile à déboguer avec les clients, et pratique pour faire tourner v1 et v2 côte à côte lorsqu’il faut un changement incompatible.
Un changement est cassant si un client correct peut cesser de fonctionner sans modifier son code. Exemples fréquents :\n\n- Renommer un champ\n- Changer le type ou la signification d’un champ\n- Supprimer un champ ou un endpoint sur lequel les clients comptent\n- Rendre obligatoire un paramètre auparavant optionnel\n- Changer les règles d’authentification ou les permissions par défaut\n\nAjouter un champ optionnel est généralement sans danger.
Faites simple :\n\n- Annoncez la dépréciation tôt\n- Retournez un avertissement clair dans les réponses (et/ou les docs)\n- Fixez une date de fin ferme\n\nPour une première API, une fenêtre raisonnable est 90 jours : c’est souvent suffisant pour que les clients migrent sans paniquer.
Choisissez un modèle et appliquez‑le partout pour les endpoints de liste.\n\n- Utilisez page/limit quand les jeux de données sont petits et que les utilisateurs sautent souvent à la page 3.\n- Utilisez curseur quand les listes grandissent ou changent souvent.\n\nDéfinissez toujours un tri par défaut et un tie‑breaker (par exemple created_at + id) afin que l’ordre soit déterministe et que les résultats ne "bougent" pas entre deux requêtes.
Commencez par une limite simple par clé (par exemple 60 requêtes/minute plus un petit burst), puis ajustez selon le trafic réel.\n\nLors d’un blocage, retournez 429 et incluez :\n\n- X-RateLimit-Limit\n- X-RateLimit-Remaining\n- X-RateLimit-Reset\n- Retry-After\n\nCela rend les tentatives de nouvelle requête prévisibles et réduit les tickets de support.
Utilisez un seul format d’erreur partout (même pour les 500). Une forme pratique comprend :\n\n- code (identifiant stable)\n- message (lisible par un humain)\n- details (problèmes au niveau des champs)\n- request_id (pour le support)\n\nEt gardez l’usage des codes HTTP cohérent (400/401/403/404/409/429/500) afin que les clients puissent gérer les erreurs proprement.
Si vous générez beaucoup d'endpoints rapidement (par exemple avec Koder.ai), gardez la surface publique réduite et considérez-la comme un contrat à long terme.\n\nAvant le lancement :\n\n- Rédigez un exemple réaliste de requête + réponse par endpoint\n- Verrouillez la pagination et le format des erreurs\n- Ajoutez des clés d'idempotence pour les POST importants\n- Construisez un “fake client” en interne pour repérer les points confus\n\nEnsuite, publiez un petit SDK qui gère l’auth, les timeouts, les retries pour les requêtes sûres et la pagination — sans masquer le fonctionnement HTTP sous‑jacent.