Connectez votre CRM ou ERP à Iwana en quelques heures. Clé API pour un espace de travail, OAuth 2.0 pour un utilisateur qui vous autorise, toute l’API dès la première clé et des webhooks signés.
REST JSON Clés API OAuth 2.0 Webhooks HMAC Accès complet
Trois étapes pour votre première requête réussie. Tout se configure depuis le tableau de bord pro.
Créez une clé APIParamètres → Développeurs → Nouvelle clé. Copiez le secret immédiatement — il n’est affiché qu’une fois. Vous publiez un produit que plusieurs entreprises installeront ? Passez par OAuth, ci-dessous.
Testez l’authentificationAppelez GET /integrations/me pour vérifier l’organisation et les permissions actives.
Synchronisez vos donnéesLisez le catalogue et l’équipe, créez ou mettez à jour clients et rendez-vous, puis abonnez un webhook pour les mises à jour temps réel.
Deux façons d’atteindre un espace de travail, qui arrivent sur la même API, les mêmes routes /pro/* et les mêmes contrôles de permission. Une clé API nomme l’espace de travail ; un jeton OAuth nomme l’utilisateur, l’espace de travail et votre application. Ni l’un ni l’autre ne gère les identifiants (clés API, webhooks, plugins) ni n’invoque l’IA : ces actions restent dans l’application.
Clé API
Jeton OAuth
Qui l’émet
Un OWNER/ADMIN de l’espace, dans Paramètres → Développeurs
L’utilisateur, sur la page de consentement, pour un espace PRO
Ce qu’il désigne
L’espace de travail
L’utilisateur et l’espace et l’application
Permissions
Toute l’API décrite ici
Toute l’API, dans la limite du rôle de l’utilisateur, revérifiée à chaque requête
Durée de vie
Jusqu’à révocation
Accès 1 h · refresh 3 ans · jusqu’à révocation
Révoqué par
Admin de l’espace · équipe Iwana
Admin de l’espace (« Applications connectées ») · équipe Iwana (désactivation de l’app)
En-tête
Authorization: Bearer iw_live_…
Authorization: Bearer iwa_at_…
Authorization: Bearer iw_live_<prefix>_<secret>
Clés production
Préfixe iw_live_* — accès aux données réelles de l’organisation.
Clés de test
Préfixe iw_test_* — lit les données réelles de l’espace et refuse toute écriture (403 test_key_read_only) : de quoi construire une intégration de bout en bout sans toucher l’agenda. Il n’existe pas d’espace de démonstration séparé. Le refus tombe avant la lecture du corps — sur tout POST, PUT, PATCH et DELETE, aperçus en lecture seule compris — : la validation du corps (400 validation_failed) ne s’éprouve donc qu’avec une clé de production.
L’organisation est embarquée dans la clé ou le jeton : vous n’avez pas besoin d’envoyer x-workspace-id ni d’autres en-têtes de contexte.
OAuth 2.0 — Sign in with IWANA
Authorization Code avec secret client. PKCE (S256) est honoré quand le client l’envoie, sans être obligatoire. Les jetons sont opaques : pas d’OpenID Connect, pas de JWKS, pas d’id_token — l’identité vient de GET /oauth/userinfo. Le jeton ouvre toute l’API décrite ici, dans la limite du rôle de l’utilisateur : le jeton d’un VIEWER ne peut pas écrire.
Endpoint
Méthode
Authentification
https://iwana.fr/v3/oauth/authorize
navigateur
la session Iwana de l’utilisateur
https://iwana.fr/v3/api/v1/oauth/token
POST, form-encoded
client_id + client_secret (corps ou HTTP Basic)
https://iwana.fr/v3/api/v1/oauth/userinfo
GET
Bearer iwa_at_…
1. Enregistrer votre application
L’équipe Iwana crée l’application : nom, logo, jusqu’à cinq redirect_uri comparées à l’identique (http ou https, sans fragment). Vous recevez un client_id (iwa_…) et un client_secret (iwa_cs_…) — le secret n’est affiché qu’une fois et stocké haché. Toutes les applications sont confidentielles : le secret est requis au token endpoint, PKCE s’y ajoute mais ne le remplace pas.
Écrivez à [email protected] avec le nom de l’application, un logo et vos URL de retour.
2. Envoyer l’utilisateur vers Iwana
GET https://iwana.fr/v3/oauth/authorize
?client_id=iwa_…
&redirect_uri=https://crm.example/auth/iwana/callback
&response_type=code
&state=<random>
&code_challenge=<S256>&code_challenge_method=S256
Pas de paramètre scope : le jeton couvre toute l’API. Un client_id inconnu ou une redirect_uri non enregistrée affiche une page sans issue et ne redirige jamais. Un utilisateur sans espace PRO voit « réservé aux professionnels » ; avec plusieurs espaces, il choisit lequel autoriser. Un consentement déjà accordé saute la carte.
Le navigateur revient sur redirect_uri?code=iwa_ac_…&state=…, ou ?error=access_denied&state=… si l’utilisateur a refusé. Le code vit 60 secondes et ne sert qu’une fois.
3. Échanger le code
POST https://iwana.fr/v3/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=iwa_ac_…&redirect_uri=<same as step 2>
&client_id=iwa_…&client_secret=iwa_cs_…&code_verifier=…
Envoyez code_verifier seulement si vous avez envoyé un code_challenge à l’étape 2. Le client peut s’authentifier dans le corps ou en HTTP Basic.
4. Appeler l’API
GET https://iwana.fr/v3/api/v1/pro/clients
Authorization: Bearer iwa_at_…
Pas d’en-tête x-workspace-id : le jeton porte l’espace de travail. Les écritures faites ainsi sont attribuées à votre application dans le journal d’audit, et un rendez-vous créé ainsi porte source: API.
Renvoie un nouvel access_token ; le refresh_token reste le même. Après trois ans, ou une fois le grant révoqué, la réponse est invalid_grant et l’utilisateur doit se reconnecter. Rafraîchissez à l’heure, pas à chaque requête : le token endpoint a sa propre limite, bien plus basse.
sub + org_id est la paire à lier à votre propre fiche utilisateur.
Erreurs OAuth
Le token endpoint parle RFC 6749 : { "error": "invalid_grant", "error_description": "…" } avec invalid_request, invalid_client (401, avec WWW-Authenticate), invalid_grant, unsupported_grant_type. Un bearer refusé sur une route PRO est un 401 avec l’enveloppe habituelle { "error": { "code": "invalid_token" } }.
Durées de vie
Quoi
Vit
Puis
Code d’autorisation
60 s, un seul usage
invalid_grant
Access token
1 heure
refresh
Refresh token
3 ans, fixe
l’utilisateur se reconnecte
Grant
jusqu’à révocation
tous les jetons meurent d’un coup
Clé API
jusqu’à révocation
401
Révocation
Il n’y a pas d’endpoint de révocation : l’admin de l’espace coupe votre application depuis Paramètres → Développeurs → Applications connectées, et l’équipe Iwana peut désactiver l’application partout. Dans les deux cas, chaque jeton est refusé dès la requête suivante, y compris un access token fraîchement rafraîchi. Un membre retiré ou suspendu perd ses jetons de la même manière.
Avec une librairie générique
Tout client OAuth 2.0 standard fonctionne. Deux configurations prêtes à coller :
Les exemples ci-dessous omettent ce préfixe pour la lisibilité. Les identifiants (`id`, `clientId`, `orgId`…) sont des chaînes opaques sans préfixe : stockez-les tels quels, ne les analysez pas.
Vérifier votre clé
GET/integrations/me
Retourne orgId, acteur, permissions, locale et fuseau horaire. Idéal comme health-check d’intégration.
Ce qui vaut pour toutes les routes, et que la spécification OpenAPI décrit opération par opération.
Champs inconnus : refusés à l’entrée, ignorés à la sortie
Un corps de requête (ou une query aux paramètres déclarés) qui porte un champ non déclaré est refusé en 400 validation_failed — il n’est pas ignoré en silence ; de même pour un nombre qui n’en est pas un (limit=abc). À l’inverse, une réponse peut gagner des champs sans préavis : ignorez ceux que vous ne connaissez pas.
Codes de succès et réponses des suppressions
Une création répond 201. Les lectures, mises à jour, suppressions et les POST qui ne font que calculer — /clients/import/check, /companies/lists/preview, /communications/preview, /workflows/{id}/test, /map/route — répondent 200. Une suppression n’est jamais un 204 : elle répond un petit corps JSON, { "ok": true } sur la plupart des routes et { "id": "…", "deleted": true } sur /companies et /companies/lists ; la réponse de chaque opération dans la spécification le précise.
Nouvelles tentatives : Idempotency-Key
Sur les créations — POST /appointments, /clients, /contacts, /companies, /sites, /equipments, /quotes, /invoices (et from-quote, from-intervention, from-contract), /invoices/{id}/payments, /invoices/{id}/credit-note, /quotes/dpgf, les duplications de devis et de chantiers, /interventions, /projects, /tasks, /contracts — envoyez Idempotency-Key: <8 à 128 caractères>. La même clé sur la même route dans les 48 h renvoie la première réponse au lieu de créer un doublon. Une erreur n’est pas mémorisée : réessayez-la avec la même clé. Les autres routes ignorent l’en-tête.
Vos identifiants et les doublons
POST /clients crée une fiche à chaque appel sans id : il ne rapproche jamais une fiche existante par email ou téléphone. Le dédoublonnage est à votre charge : enregistrez la correspondance avec PUT /external-refs et consultez GET /external-refs avant de créer (réponse { "item": null } si rien ne correspond).
Mises à jour
Trois formes, indiquées sur chaque opération : un POST qui crée ou met à jour selon la présence de id ; un PATCH champ par champ { "field": "…", "value": … } (devis, factures, contrats, équipements, interventions, chantiers) ; un PATCH partiel classique (sites). Les montants nommés …Cents sont en centimes ; le prix d’une prestation est en euros.
Source et IA
Un rendez-vous écrit avec une clé ou un jeton porte source: API, sauf si le corps envoie sa propre source. Une clé n’appelle jamais l’IA : la génération de workflows et l’extraction de PDF répondent 403 scope_required et ne figurent pas dans la spécification. Une automatisation enregistrée avec une clé ne peut pas ajouter ni modifier d’étape Léo, y compris par un modèle : 403 scope_required, details.reason: ai_step_session_only. Les étapes Léo écrites par une personne restent telles quelles.
Modules ERP
Équipements, contrats, devis, factures, interventions et bibliothèque de prix demandent les modules ERP dans l’offre de l’espace ; sinon la réponse est 402 erp_required. Si l’offre ERP est désactivée sur toute la plateforme, la réponse est 403 offer_disabled (avec details.offer = "erp"), quel que soit l’abonnement.
Listes et pagination
Liste
Taille de page (défaut / max)
Forme
/appointments, /sites
200 / 500
{ items, total, limit, offset }
/clients, /contacts
50 / 100
{ items, total, limit, offset }
/companies
50 / 200
{ items, total }
/equipments, /interventions, /projects
200 / 500
{ items, total }
/contracts
300 / 500
{ items, total }
/quotes, /invoices
500 / 500
{ items, total }
/tasks
500, sans pagination
{ items, total, cap }
/activity-log, documents
limit + curseur before
{ items, nextBefore }
Catalogue, équipe, réglages
liste complète
tableau ou { items }
Une liste sans kind sur /clients ne renvoie que les particuliers : envoyez kind=ALL pour les entreprises aussi. Le schéma de réponse de chaque opération dit laquelle de ces formes elle renvoie.
Équipe
Membres de l’espace et équipes : l’identifiant d’adhésion d’un membre est ce qu’attendent les memberIds d’un rendez-vous. Une clé peut aussi inviter et organiser l’équipe.
GET/team
Membres (id d’adhésion = memberIds d’un rendez-vous), équipes et appartenances.
POST/team/members/invite
Invite un collègue dans l’espace (pas un client) : email d’invitation valable 14 jours. Rôle OWNER refusé.
POST/team/teams
Crée ou renomme une équipe ; POST /team/teams/{id}/members pour y ajouter un membre.
Rendez-vous
Les rendez-vous (type=APPOINTMENT) et les créneaux bloqués (type=UNAVAILABILITY, y compris ceux importés de Google ou Outlook) passent par les mêmes routes ; les listes renvoient les rendez-vous sauf si vous demandez type=UNAVAILABILITY. La source est marquée API lors d’un appel par clé ou par jeton, sauf si le corps envoie la sienne.
GET/appointments
Liste paginée (limit 200 par défaut, 500 au plus). Filtres : from, to (ISO date), status ou statuses (séparés par des virgules : PENDING, CONFIRMED, EXPIRED…), clientId, siteId, companyId, projectId, memberIds, serviceIds, search.
GET/appointments/{id}
Détail d’un rendez-vous avec client, prestation, site, équipe assignée et rapport d’intervention.
GET/appointments/{id}/history
Journal du rendez-vous : modifications, changements de statut, messages envoyés.
GET/appointments/counts
Compteurs par onglet : à venir, aujourd’hui, demandes, annulés, historique.
POST/appointments
Crée un rendez-vous, ou le modifie si le corps porte son id (un id inconnu répond 404, jamais une création). serviceId est requis pour créer, clientId facultatif ; startAt / endAt en ISO 8601 UTC, requis aussi pour une modification. Un champ omis garde sa valeur, null l’efface. Envoyez un en-tête Idempotency-Key pour qu’une nouvelle tentative ne crée pas de doublon.
PATCH/appointments/{id}/status
Change le statut (ex. CONFIRMED, COMPLETED, CANCELLED) sans réécrire tout l’objet. Émet appointment.updated, ou appointment.cancelled pour CANCELLED.
DELETE/appointments/{id}
Annule et retire le rendez-vous : ensuite GET répond 404 et les listes ne le renvoient plus. Pour garder un rendez-vous annulé visible, passez plutôt son statut à CANCELLED.
Plages de travail effectives par membre et par jour (from / to). Une période de plus de 62 jours (bornes incluses) est refusée en 400 range_too_long : découpez les longues périodes en plusieurs appels.
POST/availability/schedules
Crée ou modifie le planning d’un membre ; un chevauchement répond 409 schedule_conflict.
Le carnet d’adresses. /clients porte les particuliers (kind=INDIVIDUAL) et les entreprises (kind=COMPANY) ; /companies en est la vue entreprise, /contacts les personnes rattachées à un compte. Un client n’est pas un membre de l’équipe : pour un collègue, voir POST /team/members/invite.
GET/clients
Liste paginée (50 par défaut, 100 au plus) avec recherche sur nom, email et téléphone. Sans kind, seuls les particuliers : kind=ALL pour tout, kind=COMPANY pour les entreprises. view=list pour une réponse allégée.
GET/clients/{id}
Fiche complète : coordonnées, tags, champs personnalisés, nombre de contacts et de sites.
POST/clients
Crée une fiche, ou la met à jour si le corps porte son id. Sans id, crée toujours : aucun rapprochement par email ou téléphone. Dédoublonnez avec /external-refs. Un SIRET d’entreprise déjà utilisé répond 409 siret_already_used (details.clientId nomme la fiche). Idempotency-Key accepté.
DELETE/clients/{id}
Supprime la fiche (suppression logique : elle disparaît des listes et GET répond 404).
GET/clients/{id}/duplicates
Doublons possibles : même SIRET ou même nom. Lecture seule.
POST/clients/{id}/merge
Fond ce doublon dans targetId : rendez-vous, sites, devis… passent sur la cible, la fiche source part à la corbeille.
POST/clients/{id}/convert
Particulier ↔ entreprise, en gardant l’identifiant et tout ce qui y est rattaché.
POST/clients/import
Import en masse (20 000 lignes au plus) ; avec target, rapprochement sur email, téléphone, SIRET ou nom et choix skip / update / create.
POST/clients/{id}/portal-link
Lien vers l’espace client, créé une fois puis réutilisé. N’envoie rien.
POST/clients/{id}/documents
Dépose un document (data URL base64, 14 Mo au plus) ; GET pour la liste.
POST/contacts
Crée ou modifie une personne rattachée à un compte (clientId requis à la création).
POST/companies
Crée ou modifie une entreprise (name requis) ; un SIRET déjà utilisé répond 409 siret_already_used, comme sur /clients. GET /companies pour la liste (50 par défaut, 200 au plus).
GET/companies/lists
Listes d’entreprises et de contacts importées ou hébergées.
Un site est une adresse où le travail a lieu : un contact en a généralement plusieurs, et c’est le site — pas le contact — qui porte le code de portail, les coordonnées GPS et les personnes à joindre sur place. Un équipement est ce qui y est installé, un contrat d’entretien ce qui le couvre.
GET/sites
Adresses de l’espace (200 par défaut, 500 au plus). Filtrez par ?clientId=, ?clientIds= ou ?companyId=.
GET/sites/{id}
Un site avec ses contacts (propriétaire, agence, locataire…).
POST/sites
Crée un site (création seulement). Un propriétaire est requis : clientId ou companyId — sans lui, 400 owner_required ; line1 est facultatif ; lat/lng si vous les avez déjà.
PATCH/sites/{id}
Modifie un site : un champ omis garde sa valeur, null l’efface.
POST/sites/{id}/contacts
Remplace la liste des personnes du site.
GET/equipments
Équipements installés. Filtrez par ?siteId=, ?clientId= ou ?companyId=.
POST/equipments
Crée un équipement : label et clientId requis, siteId doit appartenir au même client.
PATCH/equipments/{id}
Modifie un champ : { field, value } ; un nom inconnu va dans customFields.
GET/contracts
Contrats d’entretien (300 par défaut, 500 au plus).
POST/contracts
Crée un contrat : clientId et startsOn requis ; lignes et équipements couverts ensuite.
POST/contracts/{id}/equipment/sync
Remplace l’ensemble des équipements couverts et leur cadence.
Un site appartient à un contact ou à une entreprise, et un équipement à un client et éventuellement à l’un de ses sites : créez le contact avant le site, et filtrez les équipements par ?siteId= pour retrouver ceux d’une adresse. Équipements et contrats demandent les modules ERP.
Chantiers & tâches
Un chantier regroupe phases, ouvrages, pièces, réserves, métrés, documents, intervenants et entreprises partenaires. Les tâches de /tasks sont la liste de choses à faire de l’espace, distincte des ouvrages d’un chantier.
GET/projects
Chantiers (200 par défaut, 500 au plus) ; counts=1 pour les compteurs par statut.
POST/projects
Crée un chantier en brouillon (référence CH-…). Idempotency-Key accepté.
PATCH/projects/{id}
Modifie un champ : { field, value }.
GET/projects/{id}/structure
Tout le contenu : phases, ouvrages, réserves, finances, devis, factures, rendez-vous, documents.
POST/projects/{id}/phases
Ajoute ou modifie une phase du planning ; même schéma pour /tasks, /rooms, /reserves, /measurements.
POST/projects/{id}/participants
Invite une entreprise ou une personne sur le chantier : l’invitation part par email et SMS.
GET/tasks
Tâches de l’espace par échéance (500 au plus, sans pagination).
POST/tasks
Crée ou modifie une tâche (title requis). En modification, envoyez la tâche entière.
Une quarantaine d’opérations au total sur les chantiers : la spécification OpenAPI les liste toutes, groupe « Chantiers ».
Catalogue (prestations, lieux, catégories, spécialités — à lire pour mapper vos IDs avant de créer des rendez-vous, et modifiable par une clé), règles métier, champs personnalisés, automatisations et messages envoyés aux clients.
GET/catalog/services
Prestations proposées avec variantes, catégorie et lieux : durée (durationMinutes), prix en euros. Liste complète, sans pagination.
POST/catalog/services
Crée ou modifie une prestation (name et durationMinutes requis). En modification, envoyez la prestation entière.
GET/catalog/locations
Lieux d’exercice (adresses, zones, en ligne).
GET/catalog/categories
Catégories de prestations.
GET/catalog/specialties
Qui peut réaliser chaque prestation : membres, équipes, taille d’équipe.
GET/business-rules
Définitions et valeurs des règles métier (délais, annulation…) ; POST pour en changer une.
GET/fields
Champs personnalisés d’un type de fiche (?scope=client, appointment…). Les valeurs vivent dans customFields, sous la clé key du champ.
GET/workflows/bootstrap
Liste des automatisations et des modèles publiés.
POST/workflows
Crée une automatisation en brouillon (depuis un modèle, une spec ou vide) ; PATCH …/status pour l’activer.
POST/workflows/{id}/test
Exécution à blanc sur une vraie fiche : montre ce que chaque étape ferait, sans rien envoyer.
POST/workflows/runs/{runId}/approve
Approuve une exécution en attente : la suite, envois compris, s’exécute.
GET/communications/history/{id}
Un message envoyé, en entier, avec ses dates de remise.
PATCH/communications/notifications/{id}
Active ou coupe une notification client.
La rédaction de workflows par l’IA n’est pas ouverte aux clés. Les envois de test de /communications/notifications/…/test partent réellement (un SMS est facturé) : envoyez to, une clé n’a pas d’adresse de compte.
Statistiques, journal & géo
Indicateurs du tableau de bord, journal d’activité de l’espace et outils d’adresse.
GET/dashboard/stats
KPI, séries et répartitions sur une période (range=7d, 30d, 90d, ytd, mtd ou custom).
GET/dashboard/home
Tuiles d’accueil : devis en cours, factures en attente, chiffre du mois.
GET/activity-log
Fil d’activité, le plus récent d’abord : limit (50 par défaut, 200 au plus), curseur before, filtre kinds.
GET/geo/suggest
Autocomplétion d’adresse (6 suggestions) ; /geo/resolve pour géocoder.
Les chiffres sont ceux du tableau de bord, recalculés à chaque appel : mettez-les en cache plutôt que de les relire en boucle.
Mapping IDs externes
Iwana ne dédoublonne pas à votre place : POST /clients sans id crée toujours. Pour une synchronisation idempotente, stockez la correspondance entre vos IDs et ceux d’Iwana, et consultez-la avant de créer.
provider et entityType sont du texte libre (hubspot, client…) : choisissez des valeurs et gardez-les.
Webhooks sortants
Abonnez une URL HTTPS depuis Paramètres → Développeurs. Iwana envoie un POST JSON à chaque événement, signé pour que vous puissiez vérifier l’authenticité. Le corps est toujours { event, orgId, data, occurredAt }. data reste court : l’id de l’enregistrement, les ids liés, le statut, des dates ISO et des montants en centimes — pas de nom, de note ni d’adresse, sauf l’email et le nom d’un contact dans client.created / client.updated. Relisez l’enregistrement par GET si vous avez besoin de tout.
Vérification HMAC — calculez SHA-256 HMAC du corps brut (JSON tel qu’envoyé) avec le secret affiché une seule fois à la création de l’abonnement. Comparez avec Iwana-Signature. Répondez 2xx en moins de 15 secondes. Chaque événement est livré une seule fois — pas de nouvelle tentative automatique aujourd’hui. Les échecs sont visibles dans Paramètres → Développeurs ; pour rattraper un événement manqué, relisez périodiquement les listes (GET /appointments?from=&to=, GET /clients, GET /invoices…).
Erreurs
Toute erreur a la même forme, et porte l’identifiant de la requête qui l’a produite. Citez requestId quand vous nous écrivez au sujet d’un appel : il est aussi renvoyé en en-tête X-Request-Id sur toutes les réponses, succès compris.
code est stable et destiné au code appelant ; message est pour un humain et peut changer sans préavis. Ne faites pas de logique sur le message.
HTTP
Signification
Exemples
401
Clé ou jeton absent, mal formé, expiré ou révoqué
unauthenticated, invalid_token
403
Action refusée : rôle de l’utilisateur (jeton OAuth), clé de test en écriture, ou clé créée avant l’accès complet — rouvrez-la dans Paramètres → Développeurs et enregistrez
forbidden, test_key_read_only
404
Ressource introuvable dans l’organisation
client_not_found
400
Validation (y compris un champ non déclaré dans le corps, ou un nombre qui n’en est pas un) ou règle métier
Conflit avec l’existant (un SIRET déjà utilisé, sur /clients comme sur /companies)
schedule_conflict, siret_already_used
429
Budget de l’espace de travail épuisé
rate_limited
Limites de débit
Le budget appartient à l’espace de travail, et toutes ses clés API et jetons OAuth dépensent le même. Les personnes qui utilisent le tableau de bord ont chacune le leur : une intégration qui boucle ne peut pas bloquer son propre client.
Fenêtre
Par espace, clés et jetons confondus
En-têtes
1 minute
600 requêtes
X-RateLimit-Limit, -Remaining, -Reset (secondes)
24 heures
50 000 requêtes
X-RateLimit-Limit-Day, -Remaining-Day, -Reset-Day
Ce sont les valeurs par défaut ; l’équipe Iwana peut les changer pour toute la plateforme, lisez donc les en-têtes X-RateLimit-Limit* plutôt que de coder les nombres en dur. Au-delà de l’une des fenêtres, la réponse est 429 avec l’enveloppe habituelle, error.code = "rate_limited", et un en-tête Retry-After en secondes. Un plancher par IP de 1 000 requêtes/minute s’applique aussi avant authentification, sous les mêmes noms d’en-têtes sans suffixe : un appel refusé par le contrôle de la clé ou des permissions (401, un 403 comme test_key_read_only, un 402) ne porte que ces en-têtes par IP — aucun -Day — et ne consomme pas le budget de l’espace.
Rythmez-vous sur X-RateLimit-Remaining et reculez sur 429. Rafraîchissez les access tokens à l’heure, pas à chaque requête.
Versioning
Version actuelle : v1 (/api/v1/pro/…). Les changements incompatibles seront publiés sous /v2/ avec une période de chevauchement d’au moins 12 mois sur v1.
Les ajouts rétro-compatibles (nouveaux champs optionnels, nouveaux endpoints) peuvent arriver sans bump de version majeure. Traitez les champs inconnus d’une réponse comme normaux : votre client ne doit pas échouer parce que nous en avons ajouté un. Dans une requête, c’est l’inverse : un champ non déclaré est refusé en 400 validation_failed.
OpenAPI
Toute cette page existe aussi sous forme lisible par une machine, et en plus complet : la spécification liste chaque opération qu’une clé peut appeler — près de 300 —, avec ses paramètres, son corps et sa réponse. Collez cette URL dans Postman, Insomnia, Swagger UI ou un générateur de SDK — vous n’avez pas à recopier les schémas à la main.
Le document est servi sans authentification : une spécification qu’il faut une clé pour lire est une spécification que personne n’évalue avant de s’inscrire. Il ne décrit que des formes — aucune donnée d’espace de travail n’y transite.
Prompts pour agents IA
Vous construisez l’intégration avec Claude, Cursor, Copilot ou un autre agent de code ? Ces prompts lui donnent le contrat exact de l’API pour qu’il n’ait pas à le deviner. Copiez, remplacez ce qui est entre chevrons, collez.
Commencez toujours par la fiche de contexte : elle tient dans un message système ou en tête de conversation, et chaque prompt de tâche s’appuie dessus.
Fiche de contexte — à coller avant toute tâche
Les faits que l’agent doit connaître pour ne rien inventer : URL, authentification, enveloppe d’erreur, limites, où lire le contrat complet.
Tu vas m'aider à intégrer l'API REST d'Iwana (SaaS de prise de rendez-vous pour artisans, France/UE). Voici le contrat ; ne suppose rien qui n'y figure pas — lis d'abord la spécification OpenAPI.
- Spécification OpenAPI (source de vérité pour chaque schéma) : https://iwana.fr/v3/api/v1/openapi.json
- Documentation humaine : https://iwana.fr/developers/api
- URL de base : https://iwana.fr/v3/api/v1/pro — toutes les routes ci-dessous sont relatives à ce préfixe.
- Authentification : en-tête "Authorization: Bearer <jeton>". Deux formes de jeton, mêmes routes, mêmes permissions :
- clé API d'espace de travail "iw_live_…", créée dans Paramètres → Développeurs ; une clé de test "iw_test_…" lit le même espace réel et se voit refuser toute écriture (403 test_key_read_only, avant la validation du corps) — il n'existe pas d'espace de démonstration ;
- access token OAuth 2.0 "iwa_at_…" obtenu par Authorization Code (autorisation : https://iwana.fr/v3/oauth/authorize ; token : POST https://iwana.fr/v3/api/v1/oauth/token en form-urlencoded ; identité : GET https://iwana.fr/v3/api/v1/oauth/userinfo). Access token 1 h, refresh token 3 ans et inchangé au refresh, PKCE S256 optionnel.
- Aucun en-tête x-workspace-id : le jeton porte l'espace de travail.
- Health-check : GET /integrations/me renvoie orgId, acteur, permissions, locale, fuseau.
- Une clé ou un jeton donne accès à toute l'API de la spécification (rendez-vous, clients, sites, équipements, contrats, catalogue, équipe, devis, factures, interventions, chantiers, tâches, workflows, réglages). Un 403 signifie que le rôle de l'utilisateur (jeton OAuth) ou une clé de test (lecture seule) refuse l'action. Une clé n'appelle jamais l'IA. Un 402 erp_required signifie que l'offre de l'espace n'a pas les modules ERP ; un 403 offer_disabled signifie que l'offre est désactivée sur toute la plateforme.
- Erreurs : toujours { "error": { "code": "…", "message": "…", "requestId": "…" } }. "code" est stable, "message" ne l'est pas : ne jamais brancher de logique sur "message". X-Request-Id est présent sur toutes les réponses.
- Limites : 600 requêtes/minute et 50 000/jour par espace de travail, partagées entre toutes ses clés et jetons. Lire X-RateLimit-Remaining et X-RateLimit-Remaining-Day ; sur 429, respecter Retry-After (secondes). Un 401/403 refusé avant le décompte du budget de l'espace ne porte que les en-têtes X-RateLimit-* par IP, sans les -Day.
- Nouvelles tentatives : envoyer un en-tête Idempotency-Key (8 à 128 caractères) sur les POST de création (rendez-vous, clients, contacts, entreprises, sites, équipements, contrats, devis, factures, paiements, interventions, chantiers, tâches) ; la même clé sur la même route dans les 48 h renvoie la première réponse au lieu de créer un doublon.
- Doublons : POST /clients sans id crée toujours, il ne rapproche jamais par email ou téléphone. Stocker la correspondance entre mes IDs et ceux d'Iwana via PUT /external-refs { provider, entityType, localId, externalId } et la résoudre avec GET /external-refs?provider=&entityType=&externalId= avant de créer ; sans correspondance la réponse est { "item": null } (200, pas 404).
- Listes : chaque opération a sa taille de page et sa forme ({ items, total } avec ou sans limit/offset, { items } ou tableau) : la lire dans la spécification, ne pas supposer.
- Dates en ISO 8601 UTC ; montants "…Cents" en centimes. Champ non déclaré dans un corps de requête : 400 validation_failed, donc n'envoyer que les champs du schéma. Champs inconnus dans une réponse : les ignorer, jamais échouer.
- Webhooks entrants : POST JSON signé ; en-têtes Iwana-Signature (sha256=<HMAC-SHA256 du corps brut>), Iwana-Event, Iwana-Delivery. Vérifier la signature sur le corps BRUT, répondre 2xx en moins de 15 s, traiter en asynchrone. Une seule tentative de livraison, sans nouvelle tentative automatique : prévoir une réconciliation périodique via les endpoints de liste.
- Ne jamais logger une clé, un secret client ou un jeton.
Mon système à connecter : <CRM / ERP / outil>.
1. Synchronisation clients et rendez-vous avec une clé API
Une synchro bidirectionnelle, idempotente, qui respecte les limites de débit.
En t'appuyant sur la fiche de contexte Iwana ci-dessus, implémente une synchronisation bidirectionnelle entre <mon système> et Iwana avec une clé API d'espace de travail.
Exigences :
1. Au démarrage, appelle GET /integrations/me et arrête-toi avec un message clair si les permissions ne contiennent pas ce dont la synchro a besoin.
2. Lis le catalogue (GET /catalog/services) et l'équipe (GET /team) une fois et mets-les en cache pour mapper mes IDs de prestations et d'intervenants.
3. Pour chaque contact de <mon système> : résous d'abord GET /external-refs?provider=<mon-provider>&entityType=client&externalId=<id> ; si item est null, POST /clients (avec un Idempotency-Key dérivé de mon id) puis PUT /external-refs ; sinon POST /clients avec l'id Iwana pour mettre à jour. Iwana ne rapproche pas par email : ne crée jamais deux fois le même contact.
4. Même schéma pour les rendez-vous (POST /appointments avec serviceId, clientId, startAt/endAt en ISO 8601 UTC, memberIds, customFields.<monId>, et Idempotency-Key). Pour une modification, renvoie toujours startAt et endAt.
5. Respecte le budget : lis X-RateLimit-Remaining, ralentis quand il descend sous 10 %, et sur 429 attends Retry-After avant de réessayer. Ne réessaie jamais un 400 ou un 403.
6. Journalise requestId de chaque erreur, jamais la clé.
Commence par lister les endpoints que tu vas appeler et dans quel ordre, puis implémente.
2. Implémenter « Sign in with IWANA » (OAuth 2.0)
Pour un produit que plusieurs entreprises installent : l’utilisateur autorise votre application, vous obtenez un jeton par espace de travail.
En t'appuyant sur la fiche de contexte Iwana ci-dessus, implémente « Sign in with IWANA » (Authorization Code avec secret client, PKCE S256 activé).
Contrat :
- Redirige l'utilisateur vers https://iwana.fr/v3/oauth/authorize?client_id=<CLIENT_ID>&redirect_uri=<URI enregistrée à l'identique>&response_type=code&state=<aléatoire>&code_challenge=<S256>&code_challenge_method=S256
- Au retour sur redirect_uri : vérifie state ; si ?error=access_denied, affiche un message et n'échange rien. Le code (iwa_ac_…) vit 60 s et ne sert qu'une fois.
- Échange : POST https://iwana.fr/v3/api/v1/oauth/token, Content-Type application/x-www-form-urlencoded, grant_type=authorization_code&code=…&redirect_uri=<même valeur>&client_id=…&client_secret=…&code_verifier=…
Réponse : { access_token: "iwa_at_…", token_type: "Bearer", expires_in: 3600, refresh_token: "iwa_rt_…", scope: "…" }
- Identité : GET https://iwana.fr/v3/api/v1/oauth/userinfo avec le bearer → { sub, email, name, org_id, org_name, role, scope }. Lie mon compte utilisateur à la paire (sub, org_id).
- Refresh : grant_type=refresh_token&refresh_token=…&client_id=…&client_secret=… → nouvel access_token, le refresh_token reste le même. Rafraîchis à l'expiration (1 h), pas à chaque requête.
- Erreurs du token endpoint : { error, error_description } avec invalid_request, invalid_client (401), invalid_grant, unsupported_grant_type. Sur invalid_grant au refresh, le grant a été révoqué ou a expiré : marque la connexion comme à refaire et renvoie l'utilisateur vers l'autorisation.
- Un 401 invalid_token sur une route de l'API avec un access token non expiré signifie que l'espace de travail a révoqué l'application : même traitement.
- Conserve access_token, refresh_token, expires_at, sub et org_id par connexion ; ne logge jamais un jeton ni le secret client.
Gère chaque cas ci-dessus : state invalide, access_denied, expiration du code, expiration du jeton et révocation.
3. Recevoir les webhooks et vérifier la signature
Un endpoint HTTPS qui vérifie le HMAC sur le corps brut, répond vite et traite en arrière-plan.
En t'appuyant sur la fiche de contexte Iwana ci-dessus, implémente un récepteur de webhooks Iwana.
Contrat de livraison :
- POST JSON sur mon URL HTTPS. En-têtes : Iwana-Signature: sha256=<HMAC-SHA256 hex du corps brut avec mon secret d'abonnement>, Iwana-Event: <type>, Iwana-Delivery: <uuid unique par tentative>.
- Corps : { "event": "appointment.created", "orgId": "…", "data": { "id": "…", "startAt": "…", "status": "…" }, "occurredAt": "…" }
- Événements : la liste à jour est dans x-iwana-webhook-events de https://iwana.fr/v3/api/v1/openapi.json (par exemple appointment.created, appointment.cancelled, client.updated) ; data porte toujours l'id de l'enregistrement, avec des ids liés, le statut, des dates ISO et des montants en centimes.
- Une seule tentative par événement, timeout 15 s, pas de nouvelle tentative automatique : un événement manqué doit être rattrapé par réconciliation.
Exigences :
1. Lis le corps BRUT (avant tout parsing JSON) et compare le HMAC en temps constant ; réponds 401 si la signature ne correspond pas.
2. Réponds 2xx immédiatement après avoir mis l'événement en file ; traite en asynchrone.
3. Traite chaque Iwana-Delivery une seule fois (stocke les IDs traités) par précaution.
4. Pour chaque événement, recharge l'objet complet via le GET correspondant (GET /appointments/{id}, GET /clients/{id}…) plutôt que de faire confiance au payload partiel ; ignore les types d'événement que tu ne traites pas.
5. Ne logge jamais le secret d'abonnement.
6. Ajoute une réconciliation périodique (GET /appointments?from=&to=, GET /clients) pour rattraper les événements manqués.
4. Construire un client à partir de la spécification OpenAPI
Ne recopiez pas les schémas : l’agent lit la spécification publique et en construit le client.
En t'appuyant sur la fiche de contexte Iwana ci-dessus, construis un client pour l'API PRO d'Iwana à partir de sa spécification OpenAPI.
1. Télécharge https://iwana.fr/v3/api/v1/openapi.json et ne garde que les chemins qui commencent par /pro/ : ils sont relatifs à l'URL de base https://iwana.fr/v3/api/v1 (les routes OAuth https://iwana.fr/v3/api/v1/oauth/token et https://iwana.fr/v3/api/v1/oauth/userinfo n'y figurent pas, elles sont décrites plus haut).
2. Dérive chaque type de requête et de réponse de la spécification plutôt que de les écrire à la main, pour pouvoir régénérer le client quand la spécification change.
3. Le client doit : injecter "Authorization: Bearer <jeton>", lire X-RateLimit-Remaining et attendre Retry-After sur 429, transformer l'enveloppe { error: { code, message, requestId } } en une erreur typée portant code et requestId, et ignorer les champs inconnus.
4. Vérifie le client contre l'API réelle avec GET /integrations/me et une clé iw_test_ que je te fournis.
Montre-moi d'abord la liste des opérations retenues, puis le client.
Prêt à intégrer ?
Créez votre première clé dans le tableau de bord, ou écrivez-nous pour enregistrer une application OAuth.
Nous utilisons des cookies strictement nécessaires au fonctionnement du service. Avec votre accord, nous activons aussi des cookies de supervision (Sentry) pour diagnostiquer les incidents. En savoir plus dans notre politique cookies.