# API Kapacity · référence complète Kapacity est un CRM de vente : contacts, tables personnalisables, rendez-vous, formulaires de qualification, contrats signés électroniquement, ventes et paiements. Cette API donne accès à tout cela pour un espace de travail. ## 1. Authentification Toutes les adresses commencent par `https://kapacity.app/api/v1/w/{workspace_id}`. Remplacez `{workspace_id}` par l'identifiant de l'espace, affiché dans Intégrations, onglet API. ```http GET https://kapacity.app/api/v1/w/{workspace_id}/me Authorization: Bearer kap_live_ ``` Une clé porte sur UN espace et sur UNE portée : - `lecture` : toutes les lectures (GET). - `lecture_ecriture` : les lectures et les écritures (POST, PATCH, DELETE). Une clé de lecture qui tente une écriture reçoit **403 `portee`**. Ce n'est pas une panne : il faut une autre clé. ## 2. Quotas, et ce qui arrive quand on les dépasse Trois limites, toutes comptées sur l'espace, toutes méthodes confondues : - par MOIS (selon le plan de l'espace) ; - par SECONDE, pour qu'une boucle emballée ne fasse de mal à personne ; - par HEURE. Chaque réponse porte `X-Kapacity-Quota-Limit`, `X-Kapacity-Quota-Remaining` et `X-Kapacity-Quota-Reset`. Un dépassement répond **429**, avec `Retry-After` en secondes. 🔴 **Ce qu'une intégration doit faire d'un 429 : attendre `Retry-After`, puis réessayer.** Jamais boucler immédiatement : c'est ce qui transforme un pic en panne. ## 3. Les refus, un code par cas Toute erreur rend `{ "error": "…", "code": "…" }`. Le `code` est stable, le message peut changer : **aiguillez sur le code, jamais sur le texte.** | code | HTTP | ce que ça veut dire | | --- | --- | --- | | `cle_absente` | 401 | Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête. | | `cle_invalide` | 401 | Clé d'API inconnue. Vérifiez que vous utilisez bien la clé affichée à sa création. | | `cle_expiree` | 403 | Cette clé d'API a expiré. Créez-en une nouvelle depuis Intégrations, onglet API. | | `cle_revoquee` | 403 | Cette clé d'API a été révoquée. Créez-en une nouvelle depuis Intégrations, onglet API. | | `espace_incorrect` | 403 | Cette clé n'appartient pas à l'espace demandé. Vérifiez l'identifiant d'espace dans l'adresse. | | `portee` | 403 | Cette clé est en lecture seule : elle ne peut pas modifier de données. Créez une clé « Lecture et écriture » pour cette intégration. | | `proprietaire` | 403 | Cette partie est réservée au propriétaire de l'espace : utilisez une clé créée par lui. | | `introuvable` | 404 | Cet élément n'existe pas dans cet espace. | | `validation` | 422 | La requête a été refusée : une valeur ne convient pas. | | `quota_mensuel` | 429 | Le quota d'appels du mois est atteint pour cet espace. Il repart au premier jour du mois prochain, et le plan Pro en comprend davantage. | | `debit` | 429 | Trop d'appels en une seconde. Espacez vos requêtes, puis réessayez. | | `debit_heure` | 429 | Trop d'appels sur la dernière heure. Réessayez un peu plus tard. | | `disjoncteur` | 429 | Écritures suspendues sur cet enregistrement : il a été modifié trop souvent en quelques secondes par cette clé. C'est le garde-fou anti-boucle. | | `limite_plan` | 429 | Une limite du plan de cet espace est atteinte. | | `api_fermee` | 403 | L'API n'est pas encore ouverte sur cet espace. | ## 4. Règles générales - **Pagination par curseur.** Une liste rend `curseur` ; le rappeler dans `?curseur=…` donne la suite. `null` signifie qu'on a tout lu. Il n'y a PAS de numéro de page : entre deux appels, une ligne créée décalerait tout et ferait sauter un enregistrement. - **Les montants sont en CENTIMES**, partout, en lecture comme en écriture. 350000 = 3 500 €. - **Les dates sont en ISO 8601, en temps universel** (`2026-09-22T14:30:00.000Z`). - **Une colonne se désigne par son NOM ou par son identifiant.** Le nom est comparé sans tenir compte des accents, de la casse ni de la ponctuation : « Email », « e-mail » et « E-Mail » visent la même colonne. - **Une valeur invalide est IGNORÉE et expliquée, jamais convertie en effacement.** La réponse porte `ignores`. Seuls `null`, `""` et `[]` effacent vraiment. - **Une écriture déclenche les automatisations**, comme une saisie à la main. `?automatisations=false` écrit sans rien déclencher : à utiliser pour un import en masse, ou pour recopier une donnée qui vient déjà de Kapacity. ## 5. Lire un exemple de requête Chaque endpoint ci-dessous montre la REQUÊTE à envoyer, puis la RÉPONSE que Kapacity rend. - La PREMIÈRE ligne donne la méthode puis l'adresse complète. GET lit, POST crée ou envoie, PATCH modifie, DELETE supprime. - Les lignes suivantes sont les EN-TÊTES. « Authorization: Bearer » est toujours là ; « Content-Type: application/json » l'accompagne dès qu'il y a un body. - Après la ligne vide vient le BODY JSON : c'est là que se mettent les valeurs marquées « dans le body JSON » dans le tableau des paramètres. Une requête GET n'a pas de body : ses options vont après le « ? » de l'adresse. - Les identifiants des exemples (les longues suites de chiffres et de lettres) sont des EXEMPLES : remplacez-les par les vôtres, que les autres adresses vous donnent. « {workspace_id} » est l'identifiant de votre espace, affiché dans Intégrations, onglet API. - Les montants sont en CENTIMES : 450000 = 4 500 €. - Dans n8n : un nœud « HTTP Request », la méthode et l'adresse de la première ligne, l'en-tête Authorization (ou « Header Auth »), et pour un POST ou un PATCH, « Send Body » en JSON avec le body de l'exemple. ## 6. Pas à pas Les parcours les plus demandés, appel par appel, dans l'ordre. ### Envoyer un contrat à un contact Un deal est gagné dans votre outil : le contrat part au client, au bon montant, et vous récupérez son lien de signature. **Étape 1 : Trouver le modèle de contrat** (`GET /contract-templates`) Listez vos modèles et repérez celui à envoyer. À faire une seule fois : un modèle garde son identifiant, vous pouvez le recopier dans votre scénario. ```http GET https://kapacity.app/api/v1/w/{workspace_id}/contract-templates Authorization: Bearer kap_live_… ``` À garder : Le « id » du modèle : c'est votre « modele_id ». **Étape 2 : Trouver le contact** (`GET /tables/{id}/records`) Cherchez le contact par son e-mail dans Mon Pipe (« leads ») ou dans Setting (« leads_setting »). S'il n'existe pas encore, créez-le avec POST /tables/leads/records : sa réponse donne aussi son identifiant. ```http GET https://kapacity.app/api/v1/w/{workspace_id}/tables/leads/records?filtre=Email:eq:jean@acme.fr Authorization: Bearer kap_live_… ``` À garder : Le « id » de la première ligne de « lignes » : c'est votre « contact_id ». Si « lignes » est vide, aucun contact n'a cet e-mail. **Étape 3 : Envoyer le contrat** (`POST /contracts`) Envoyez le modèle au contact, avec le montant en CENTIMES (450000 = 4 500 €). Le contrat est rempli avec les informations du contact, et l'e-mail rattaché au modèle part avec le lien pour signer. ```http POST https://kapacity.app/api/v1/w/{workspace_id}/contracts Authorization: Bearer kap_live_… Content-Type: application/json Idempotency-Key: contrat-jean-dupont-2026-09-23 { "modele_id": "3e5a7c9b-2d4f-4e6a-8b1c-9d0e2f4a6b8c", "contact_id": "7fc74cfb-c67a-43d2-868f-f4a8ab028475", "montant_cents": 450000 } ``` À garder : « contrat.lien_signature » (la page où le contact signe) et « email.envoye » (true si l'e-mail est parti ; sinon « email.raison » dit pourquoi). ### Être prévenu quand un contrat est signé Déclencher la suite (facture, accès à la formation, message à l'équipe) au moment où le client signe, sans interroger l'API en boucle. **Étape 1 : Donner à Kapacity l'adresse à appeler** (`POST /webhooks`) Abonnez une adresse à l'événement « contrat.signe ». Dans n8n, c'est l'adresse de production d'un nœud Webhook. À faire une seule fois. ```http POST https://kapacity.app/api/v1/w/{workspace_id}/webhooks Authorization: Bearer kap_live_… Content-Type: application/json { "nom": "Relance après signature", "url": "https://n8n.exemple.fr/webhook/abc", "evenements": [ "contrat.signe" ], "entete": { "nom": "X-Api-Key", "valeur": "une-valeur-que-vous-choisissez" }, "ignorer_api": true } ``` À garder : Le « secret » : il n'est montré qu'une fois, et sert à vérifier que l'appel vient bien de Kapacity. **Étape 2 : Recevoir la signature** (événement `contrat.signe`) À chaque signature, Kapacity appelle votre adresse avec ce corps. « objet » est le contrat signé, « contact » est le contact qui l'a signé. ```json { "evenement": "contrat.signe", "emis_le": "2026-09-22T14:20:00.000Z", "provenance": "visiteur", "table": "contracts", "objet": { "id": "c1…", "champs": { "lead_id": "l1…", "title": "Accompagnement 6 mois", "status": "signe", "value_cents": 500000, "signer_email": "jean@acme.test", "signed_at": "2026-09-22T14:20:00.000Z" } }, "contact": { "id": "l1…", "champs": { "first_name": "Jean", "last_name": "Dupont", "email": "jean@acme.test", "stage": "signe" } } } ``` À garder : « objet.id » (le contrat), « objet.champs.value_cents » (le montant, en centimes) et « contact.champs.email ». ## 7. Les endpoints ### Votre espace #### `GET /me` Votre espace, votre plan et l'état de votre quota. Le premier appel à faire quand on branche une intégration : il confirme que la clé fonctionne, sur quel espace elle porte, et combien d'appels il reste ce mois-ci. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `workspace_id` | dans l'adresse | chaine | oui | L'identifiant de votre espace, affiché dans Intégrations, onglet API. (ex. `b7f1c0de-0000-4000-8000-000000000000`) | - Aucun paramètre : si cet appel répond 200, votre clé et votre identifiant d'espace sont bons. - Les en-têtes X-Kapacity-Quota-* accompagnent CHAQUE réponse de l'API, pas seulement celle-ci. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/me Authorization: Bearer kap_live_… ``` Réponse : ```json { "espace": { "id": "b7f1c0de-0000-4000-8000-000000000000", "nom": "Top Closer Academy", "plan": "free" }, "cle": { "nom": "n8n", "portee": "lecture_ecriture", "proprietaire": true }, "quota": { "mois": 12, "limite_mois": 10000, "restant_mois": 9988, "reset_mois": "2026-10-01T00:00:00.000Z" } } ``` ### CRM : tables et lignes #### `GET /tables` Les tables du CRM de cet espace, et la base de chacune. Le deuxième appel d'une intégration, après /me : il donne les identifiants de table dont tout le reste a besoin. Portée minimale : `lecture`. - 🔴 DEUX SORTES DE TABLES. Les tables « native » sont vos pipelines : « leads » = Mon Pipe, « leads_setting » = Setting. Leur identifiant est ce mot, pas un UUID. - Les tables « personnalisee » sont celles que votre équipe a créées (Ventes, Setting, Ressources…), identifiées par un UUID. - Les deux s'utilisent EXACTEMENT de la même façon par la suite : mêmes adresses, mêmes réponses. - On ne crée ni table ni colonne par API : cette partie du CRM se lit, elle ne se façonne pas. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/tables Authorization: Bearer kap_live_… ``` Réponse : ```json { "tables": [ { "id": "leads", "nom": "Leads", "type": "native", "base": "pipe", "base_libelle": "Mon Pipe", "icone": null, "lignes": 42 }, { "id": "leads_setting", "nom": "Leads Setting", "type": "native", "base": "setting", "base_libelle": "Setting", "icone": null, "lignes": 8 }, { "id": "6b0c…", "nom": "Ventes", "type": "personnalisee", "base": "ventes", "base_libelle": "Mes Ventes", "icone": "card", "creee_le": "2026-09-01T09:12:00.000Z" } ], "bases": [ { "id": "pipe", "libelle": "Mon Pipe" } ] } ``` #### `GET /tables/{id}` Les colonnes d'une table : identifiant, nom, type, choix possibles. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de la table, rendu par GET /tables : un UUID, ou « leads » / « leads_setting » pour vos pipelines. | - 🔴 L'erreur numéro un d'une intégration : dans Kapacity, une colonne est identifiée par un UUID, pas par son nom. - On PEUT écrire par le nom (l'API le résout, sans tenir compte des accents, de la casse ni de la ponctuation), mais un outil sûr de lui relève d'abord ces identifiants. - Une colonne « lecture_seule » (formule, cumul, lookup) est refusée en écriture, et l'API dit pourquoi. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/tables/leads Authorization: Bearer kap_live_… ``` Réponse : ```json { "table": { "id": "6b0c…", "nom": "Mes Ventes", "base": "ventes", "icone": "card", "lignes": 128 }, "colonnes": [ { "id": "f1a2…", "nom": "Client", "type": "texte", "lecture_seule": false }, { "id": "f3b4…", "nom": "Montant", "type": "monetaire", "lecture_seule": false }, { "id": "f5c6…", "nom": "Statut", "type": "selection", "lecture_seule": false, "options": [ { "id": "o1", "libelle": "Signé" } ] } ] } ``` #### `GET /tables/{id}/records` Les lignes d'une table, page par page. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de la table (UUID, ou « leads » / « leads_setting »). | | `limite` | après le « ? » de l'adresse | entier | non | De 1 à 200. 50 par défaut. (ex. `100`) | | `curseur` | après le « ? » de l'adresse | chaine | non | Le « curseur_suivant » de la réponse précédente. | | `modifie_depuis` | après le « ? » de l'adresse | date | non | Ne rend que les lignes modifiées depuis cette date (ISO 8601). (ex. `2026-09-01T00:00:00Z`) | | `filtre` | après le « ? » de l'adresse | chaine | non | Cherche une ligne : « colonne:operateur:valeur ». Répétable, les conditions se cumulent en ET. Opérateurs : eq, ne, contient, commence, gt, gte, lt, lte, vide, non_vide. L'opérateur est facultatif et vaut eq. (ex. `Email:eq:jean@acme.fr`) | | `recherche` | après le « ? » de l'adresse | chaine | non | Cherche ce texte dans toutes les colonnes de texte de la table (et, pour un contact, son nom, son e-mail, sa société et son téléphone). (ex. `Dupont`) | | `champ` | après le « ? » de l'adresse | chaine | non | Forme simple, équivalente à filtre=:eq:. (ex. `Statut`) | | `vaut` | après le « ? » de l'adresse | chaine | non | La valeur exacte attendue par « champ ». | | `colonnes` | après le « ? » de l'adresse | booleen | non | Ajoute le catalogue des colonnes à la réponse. Absent par défaut : il est long, et GET /tables/{id} le donne une fois pour toutes. (ex. `1`) | | `format` | après le « ? » de l'adresse | chaine | non | « lisible » pour des valeurs prêtes à lire : un montant en EUROS au lieu de centimes, une sélection par son LIBELLÉ, un membre par son NOM. Vaut aussi en écriture (un montant s'écrit alors en euros) et dans un filtre (montant en euros, sélection par son libellé). Absent : les valeurs brutes, comme avant. (ex. `lisible`) | - Les lignes sortent de la plus ANCIENNE à la plus récente : c'est ce qui rend une synchronisation reprenable. - Pagination par CURSEUR, jamais par numéro de page : une ligne créée entre deux appels ne fait sauter personne. - Tant que « curseur_suivant » n'est pas null, il reste des lignes. - Les valeurs sont BRUTES : un montant est en CENTIMES, une sélection est l'identifiant de son option. Avec « format=lisible » : euros, libellés et noms des membres. - 🔎 CHERCHER UNE LIGNE : filtre=Email:eq:jean@acme.fr. Plusieurs « filtre » se cumulent en ET. Un montant se compare en CENTIMES (filtre=Montant:gt:100000). - Pour une SÉLECTION, filtrez par l'identifiant de l'option (rendu par GET /tables/{id}). Avec « format=lisible », par son libellé, et un montant en euros (filtre=Montant:gt:1000). - Pour chercher sans savoir dans quelle colonne : recherche=Dupont. - Le nom d'une colonne se donne comme on veut : « E-mail », « Email » et « email » désignent la même. Accents, casse et ponctuation sont ignorés. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/tables/leads/records?filtre=Email:eq:jean@acme.fr Authorization: Bearer kap_live_… ``` Réponse : ```json { "lignes": [ { "id": "a1b2…", "cree_le": "2026-09-02T10:00:00.000Z", "modifie_le": "2026-09-20T14:31:00.000Z", "champs": { "f1a2…": "Acme", "f3b4…": 350000 } } ], "curseur_suivant": "MjAyNi0wOS0wMlQxMDowMDowMC4wMDBafGExYjI" } ``` #### `POST /tables/{id}/records` Crée une ligne dans une table. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de la table (UUID, ou « leads » / « leads_setting »). | | `champs` | dans le body JSON | objet | oui | Les valeurs, rangées par NOM ou par identifiant de colonne. (ex. `{ "Client": "Acme", "Montant": 350000 }`) | | `automatisations` | après le « ? » de l'adresse | booleen | non | false pour écrire SANS déclencher les automatisations. Le garde-fou anti-boucle à utiliser pour un import en masse. (ex. `false`) | | `format` | après le « ? » de l'adresse | chaine | non | « lisible » pour des valeurs prêtes à lire : un montant en EUROS au lieu de centimes, une sélection par son LIBELLÉ, un membre par son NOM. Vaut aussi en écriture (un montant s'écrit alors en euros) et dans un filtre (montant en euros, sélection par son libellé). Absent : les valeurs brutes, comme avant. (ex. `lisible`) | - Une valeur invalide est IGNORÉE et expliquée dans « ignores », jamais convertie en effacement. - Si AUCUNE valeur n'a pu être écrite, l'appel est refusé en 422 plutôt que de créer une ligne vide. - Une création déclenche les automatisations « Quand un enregistrement est créé », comme une saisie à la main. - Dans « leads » ou « leads_setting », un contact a besoin d'un « Prénom » ou d'un « Nom » : sans l'un ni l'autre, l'appel est refusé plutôt que de créer un contact « Sans nom ». Requête : ```http POST https://kapacity.app/api/v1/w/{workspace_id}/tables/leads/records Authorization: Bearer kap_live_… Content-Type: application/json { "champs": { "Prénom": "Jean", "Nom": "Dupont", "Email": "jean@acme.fr" } } ``` Réponse : ```json { "ligne": { "id": "a1b2…", "cree_le": "2026-09-22T10:00:00.000Z", "modifie_le": null, "champs": { "f1a2…": "Acme" } } } ``` #### `POST /tables/{id}/records/bulk` Créer jusqu'à 100 lignes en un seul appel. Sans lui, importer 500 contacts coûte 500 appels, soit 5 % du quota mensuel d'un espace gratuit pour un seul import. Avec lui, cinq appels. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de la table, ou « leads » / « leads_setting ». | | `lignes` | dans le body JSON | liste | oui | Jusqu'à 100 objets { champs: { … } }. (ex. `[{ "champs": { "Prénom": "Jean" } }]`) | | `automatisations` | après le « ? » de l'adresse | booleen | non | 1 pour déclencher les automatisations. COUPÉ par défaut ici. | | `format` | après le « ? » de l'adresse | chaine | non | « lisible » pour des valeurs prêtes à lire : un montant en EUROS au lieu de centimes, une sélection par son LIBELLÉ, un membre par son NOM. Vaut aussi en écriture (un montant s'écrit alors en euros) et dans un filtre (montant en euros, sélection par son libellé). Absent : les valeurs brutes, comme avant. (ex. `lisible`) | | `Idempotency-Key` | en en-tête | chaine | non | Rejouer le même appel rend la réponse du premier au lieu de créer des doublons. | - 🔴 Une ligne refusée n'arrête PAS les autres : chaque entrée rend son sort dans « resultats », avec son rang. Un import de 500 lignes qui s'arrête à la 37e est un import à refaire à la main. - 🔴 Les automatisations sont COUPÉES par défaut, contrairement à l'écriture à l'unité : 100 lignes importées, ce serait 100 séquences d'e-mails envoyées à des gens qui viennent d'être importés. « ?automatisations=1 » les rallume. - 201 dès qu'une ligne est passée, 422 si tout a été refusé : on peut aiguiller sur le code sans lire le détail. - Posez un « Idempotency-Key » : si le réseau coupe avant la réponse, le rejeu rend le résultat du premier appel. La réponse rejouée porte l'en-tête X-Kapacity-Idempotent-Replay. Requête : ```http POST https://kapacity.app/api/v1/w/{workspace_id}/tables/leads/records/bulk Authorization: Bearer kap_live_… Content-Type: application/json Idempotency-Key: import-salon-2026-lot-1 { "lignes": [ { "champs": { "Prénom": "Jean", "Nom": "Dupont", "Email": "jean@acme.fr" } }, { "champs": { "Prénom": "Léa", "Nom": "Martin", "Email": "lea@acme.fr" } } ] } ``` Réponse : ```json { "creees": 2, "refusees": 1, "automatisations": false, "resultats": [ { "i": 0, "ok": true, "id": "l1…" }, { "i": 1, "ok": true, "id": "l2…" }, { "i": 2, "ok": false, "erreur": "Aucune valeur n'a pu être écrite." } ], "lignes": [] } ``` #### `GET /records/{id}` Une ligne et les colonnes de sa table. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de la ligne. | | `format` | après le « ? » de l'adresse | chaine | non | « lisible » pour des valeurs prêtes à lire : un montant en EUROS au lieu de centimes, une sélection par son LIBELLÉ, un membre par son NOM. Vaut aussi en écriture (un montant s'écrit alors en euros) et dans un filtre (montant en euros, sélection par son libellé). Absent : les valeurs brutes, comme avant. (ex. `lisible`) | - La ligne porte sa table : l'adresse n'a pas besoin de la répéter. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/records/7fc74cfb-c67a-43d2-868f-f4a8ab028475 Authorization: Bearer kap_live_… ``` Réponse : ```json { "ligne": { "id": "a1b2…", "cree_le": "2026-09-02T10:00:00.000Z", "modifie_le": null, "champs": { "f1a2…": "Acme" } }, "table": { "id": "6b0c…", "nom": "Mes Ventes" }, "colonnes": [ { "id": "f1a2…", "nom": "Client", "type": "texte", "lecture_seule": false } ] } ``` #### `PATCH /records/{id}` Modifie les valeurs d'une ligne. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de la ligne. | | `champs` | dans le body JSON | objet | oui | Seules les colonnes citées changent, les autres restent. | | `automatisations` | après le « ? » de l'adresse | booleen | non | false pour ne rien déclencher. | | `format` | après le « ? » de l'adresse | chaine | non | « lisible » pour des valeurs prêtes à lire : un montant en EUROS au lieu de centimes, une sélection par son LIBELLÉ, un membre par son NOM. Vaut aussi en écriture (un montant s'écrit alors en euros) et dans un filtre (montant en euros, sélection par son libellé). Absent : les valeurs brutes, comme avant. (ex. `lisible`) | - Pour VIDER une colonne, envoyez null (ou une chaîne vide, ou une liste vide). - Une modification déclenche les automatisations « Quand un enregistrement est modifié ». Requête : ```http PATCH https://kapacity.app/api/v1/w/{workspace_id}/records/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d Authorization: Bearer kap_live_… Content-Type: application/json { "champs": { "Client": "Acme SAS", "Montant": 350000 } } ``` Réponse : ```json { "ligne": { "id": "a1b2…", "cree_le": "2026-09-02T10:00:00.000Z", "modifie_le": "2026-09-22T11:00:00.000Z", "champs": { "f1a2…": "Acme SAS" } } } ``` #### `DELETE /records/{id}` Supprime une ligne. Définitif. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de la ligne. | - Aucune corbeille : la ligne est perdue. Le journal d'activité garde la trace de la suppression et de la clé qui l'a faite. Requête : ```http DELETE https://kapacity.app/api/v1/w/{workspace_id}/records/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d Authorization: Bearer kap_live_… ``` Réponse : ```json { "supprime": true, "id": "a1b2…" } ``` ### Rendez-vous #### `GET /event-types` Les types de rendez-vous, avec leur lien public de réservation. Portée minimale : `lecture`. - Envoyez le « lien_public » à votre prospect : il choisit son créneau, et le rendez-vous, le contact et l'invitation d'agenda se créent tout seuls. - Poser un rendez-vous directement par API n'existe pas : le round-robin, les disponibilités et l'anti-double-réservation se décident côté Kapacity. - « parts » donne la répartition voulue entre les assignés de même priorité, en pourcentage (somme 100 pour chaque niveau de priorité) : c'est le réglage du type, pas ce qui a été reçu. - « priorites » donne le niveau de chaque assigné, de 1 à 5 (5 la plus haute, 3 quand rien n'est réglé) : sur un créneau, les assignés libres du niveau le plus haut reçoivent tous les rendez-vous, et un niveau plus bas ne prend le relais que lorsque tous ceux du dessus sont pris. Vide quand le type tourne par équipe. - « repartition » dit comment l'équipe tourne : « tour » (chacun son tour), « charge » (le moins chargé, en suivant les parts) ou « hasard » (tirage au sort pondéré par les parts, « Par % de chance » à l'écran). - « attribution_par » vaut « equipes » quand le type tourne par équipe : « equipes » donne alors chaque équipe cochée, sa « part » en pourcentage (somme 100) et ses « membres » actuels, « assignes » tous ces membres, et « parts » (par membre) reste vide. Un rendez-vous part d'abord à une équipe selon sa part, puis à l'un de ses membres libres, à parts égales. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/event-types Authorization: Bearer kap_live_… ``` Réponse : ```json { "types_de_rendez_vous": [ { "id": "e1f2…", "nom": "Appel de qualification", "duree_min": 30, "lieu": "Google Meet", "attribution": "round_robin", "attribution_par": "membres", "repartition": "charge", "repartition_libelle": "Le moins chargé", "assignes": [ "u1…", "u2…", "u3…" ], "parts": { "u1…": 67, "u2…": 33, "u3…": 100 }, "priorites": { "u1…": 5, "u2…": 5, "u3…": 3 }, "equipes": [], "actif": true, "questions": 3, "lien_public": "https://kapacity.app/b/mon-espace/appel-de-qualification" } ] } ``` #### `GET /bookings` Les rendez-vous, du plus ancien au plus récent. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `depuis` | après le « ? » de l'adresse | date | non | Rendez-vous commençant après cette date. (ex. `2026-09-22T00:00:00Z`) | | `jusqu_a` | après le « ? » de l'adresse | date | non | Rendez-vous commençant avant cette date. | | `statut` | après le « ? » de l'adresse | chaine | non | Ne garde que ce statut : confirme, annule ou termine. (ex. `confirme`) | | `contact` | après le « ? » de l'adresse | chaine | non | Ne garde que les rendez-vous de ce contact (son identifiant). | | `limite` | après le « ? » de l'adresse | entier | non | De 1 à 200. 50 par défaut. | | `curseur` | après le « ? » de l'adresse | chaine | non | Le « curseur_suivant » de la réponse précédente. | - Chaque identifiant vient avec son nom : un scénario n'a jamais à faire un second appel pour savoir qui est « u1… ». Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/bookings?depuis=2026-09-22T00:00:00Z&statut=confirme Authorization: Bearer kap_live_… ``` Réponse : ```json { "rendez_vous": [ { "id": "b1c2…", "debut": "2026-09-23T09:00:00.000Z", "fin": "2026-09-23T09:30:00.000Z", "statut": "confirme", "type": { "id": "e1f2…", "nom": "Appel de qualification" }, "assigne": { "id": "u1…", "nom": "Camille Royer" }, "contact": { "id": "l1…", "nom": "Jean Dupont" }, "invite": { "nom": "Jean Dupont", "email": "jean@acme.fr", "telephone": "+33612345678" }, "lien_visio": "https://meet.google.com/abc-defg-hij" } ], "curseur_suivant": null } ``` ### Formulaires #### `GET /forms` Les formulaires de qualification et leur lien public. Portée minimale : `lecture`. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/forms Authorization: Bearer kap_live_… ``` Réponse : ```json { "formulaires": [ { "id": "f1…", "titre": "Candidature", "statut": "publie", "questions": 12, "lien_public": "https://kapacity.app/f/candidature" } ] } ``` #### `GET /forms/{id}/responses` Les réponses d'un formulaire, avec leur score. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant du formulaire. | | `depuis` | après le « ? » de l'adresse | date | non | Réponses soumises après cette date. | | `qualifie` | après le « ? » de l'adresse | booleen | non | true ou false pour ne garder que les qualifiés ou les non qualifiés. (ex. `true`) | | `limite` | après le « ? » de l'adresse | entier | non | De 1 à 200. | | `curseur` | après le « ? » de l'adresse | chaine | non | Le curseur de la page suivante. | - Les réponses sortent avec le LIBELLÉ de chaque question, pas son identifiant : elles se lisent sans relire le formulaire. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/forms/f1e2d3c4-b5a6-4978-8695-a4b3c2d1e0f9/responses?qualifie=true Authorization: Bearer kap_live_… ``` Réponse : ```json { "formulaire": { "id": "f1…", "titre": "Candidature" }, "reponses": [ { "id": "r1…", "soumise_le": "2026-09-21T18:02:00.000Z", "score": 86, "qualifie": true, "complete": true, "repondant": { "nom": "Jean Dupont", "email": "jean@acme.fr" }, "contact": { "id": "l1…", "nom": "Jean Dupont" }, "reponses": { "Votre budget": "Plus de 5 000 €", "Votre objectif": "Doubler mon chiffre" } } ], "curseur_suivant": null } ``` ### Contrats #### `GET /contracts` Les contrats, leur statut et leurs dates clés. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `depuis` | après le « ? » de l'adresse | date | non | Contrats créés après cette date. | | `statut` | après le « ? » de l'adresse | chaine | non | brouillon, envoye, vu ou signe. (ex. `signe`) | | `contact` | après le « ? » de l'adresse | chaine | non | Ne garde que les contrats de ce contact. | | `limite` | après le « ? » de l'adresse | entier | non | De 1 à 200. | | `curseur` | après le « ? » de l'adresse | chaine | non | Le curseur de la page suivante. | - Le corps du contrat n'est pas rendu ici, il est long : GET /contracts/{id} le donne. - Les montants sont en CENTIMES. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/contracts?statut=signe Authorization: Bearer kap_live_… ``` Réponse : ```json { "contrats": [ { "id": "c1…", "titre": "Accompagnement 6 mois", "statut": "signe", "montant_cents": 500000, "contact": { "id": "l1…", "nom": "Jean Dupont" }, "signataire": { "nom": "Jean Dupont", "email": "jean@acme.fr" }, "envoye_le": "2026-09-19T10:00:00.000Z", "signe_le": "2026-09-20T08:14:00.000Z" } ], "curseur_suivant": null } ``` #### `GET /contracts/{id}` Un contrat, son corps et sa piste d'audit. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant du contrat. | - La piste d'audit est ce qui donne sa valeur probante à la signature : horodatage et adresse IP de chaque étape. - Elle est en ajout seul en base : l'API la lit, personne ne la réécrit. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/contracts/b423fdff-34f8-4a04-a66f-68034f0d77fe Authorization: Bearer kap_live_… ``` Réponse : ```json { "contrat": { "id": "c1…", "titre": "Accompagnement 6 mois", "statut": "signe", "montant_cents": 500000, "empreinte": "9f2c…" }, "piste_audit": [ { "etape": "cree", "libelle": "Contrat créé", "le": "2026-09-19T09:58:00.000Z", "ip": null }, { "etape": "signe", "libelle": "Signé", "le": "2026-09-20T08:14:00.000Z", "ip": "81.…" } ] } ``` #### `GET /contract-templates` Les modèles de contrat, pour savoir lequel envoyer. C'est d'ici que vient le « modele_id » qu'attend POST /contracts. Chaque modèle dit s'il est utilisable maintenant et quel message e-mail partira avec lui. Portée minimale : `lecture`. - Le « id » de chaque modèle est le « modele_id » à passer à POST /contracts. - « utilisable » vaut false pour un modèle désactivé ou suspendu par le plan gratuit, et « raison » dit lequel des deux : POST /contracts le refuserait. - « message_email » est l'e-mail qui partira au contact avec le lien de signature. S'il vaut null, aucun e-mail ne partira : le contrat sera quand même envoyé, et son lien rendu. - « montant_defaut_cents » est le montant réglé sur le modèle, à titre indicatif : POST /contracts vous demande toujours le montant du contrat. En CENTIMES. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/contract-templates Authorization: Bearer kap_live_… ``` Réponse : ```json { "modeles": [ { "id": "3e5a7c9b-2d4f-4e6a-8b1c-9d0e2f4a6b8c", "titre": "Accompagnement 6 mois", "montant_defaut_cents": 500000, "utilisable": true, "raison": null, "message_email": { "id": "8a6c4e2f-0b9d-4f7b-a5c3-1e9f7d5b3a1c", "nom": "Votre contrat à signer" }, "cree_le": "2026-09-10T09:00:00.000Z" } ] } ``` #### `POST /contracts` Envoie un contrat à un contact, au montant que vous indiquez, et rend son lien de signature. Vous dites QUEL modèle de contrat, à QUEL contact, et pour QUEL montant : les trois vont dans le body JSON. Kapacity fait alors exactement ce que fait le bouton « Envoyer le contrat » de l'application : le contrat est rempli avec les informations du contact (nom, e-mail, société) et le montant, il part à la signature, le contact passe à « Contrat envoyé », et l'e-mail rattaché au modèle lui est envoyé avec le lien pour signer. La réponse vous rend ce lien (« lien_signature »), pour l'envoyer aussi par un autre canal si vous le voulez. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `modele_id` | dans le body JSON | chaine | oui | L'identifiant du MODÈLE de contrat à envoyer. Vous le trouvez avec GET /contract-templates (champ « id »). | | `contact_id` | dans le body JSON | chaine | oui | L'identifiant du contact à qui l'envoyer, de Mon Pipe ou de Setting. Vous le trouvez avec GET /tables/leads/records?filtre=Email:eq: (champ « id »), ou dans le webhook « contact.cree ». | | `montant_cents` | dans le body JSON | entier | oui | Le montant du contrat, en CENTIMES : 450000 pour 4 500 €. Il est écrit dans le contrat à la place de {{montant}}. Pour 0 €, écrivez 0. (ex. `450000`) | | `titre` | dans le body JSON | chaine | non | Le titre du contrat. Absent : « · ». | | `envoyer_email` | dans le body JSON | booleen | non | false pour ne PAS envoyer l'e-mail : le contrat part à la signature, et vous transmettez vous-même le « lien_signature » (WhatsApp, SMS…). true par défaut. (ex. `false`) | | `message_email_id` | dans le body JSON | chaine | non | Un autre modèle d'e-mail de contrat que celui rattaché au modèle. | | `automatisations` | après le « ? » de l'adresse | booleen | non | false pour n'en déclencher aucune (ni le webhook « contrat.envoye »). | | `Idempotency-Key` | en en-tête | chaine | non | Rejouer le même appel rend le premier envoi au lieu d'envoyer un second contrat. | - Le LIEN DE SIGNATURE est dans « contrat.lien_signature ». Il est TOUJOURS rendu, que l'e-mail parte ou non : c'est la page où le contact lit et signe son contrat. - Le montant est OBLIGATOIRE et s'écrit en CENTIMES : 450000 = 4 500 €, 99900 = 999 €. Sans lui, l'appel est refusé (422) : un contrat ne part jamais à un prix que vous n'avez pas écrit. - « email » dit si l'e-mail est parti : { envoye: true, a: }. Sinon { envoye: false, raison } explique pourquoi (pas de message rattaché au modèle, pas d'adresse chez le contact, pas de boîte d'envoi reliée, ou « envoyer_email »: false). Le contrat, lui, est envoyé dans tous les cas. - 🔴 Posez un « Idempotency-Key » (un texte unique par envoi, par exemple l'identifiant du deal) : si le réseau coupe et que votre scénario rejoue l'appel, le contact recevrait sinon DEUX contrats. Avec lui, le second appel rend le premier envoi. - Le message part de la boîte du Sales du contact s'il en a relié une, sinon de la boîte de l'espace. - Le contrat est FIGÉ à l'envoi : modifier le contact ensuite ne change pas le contrat. - La piste d'audit du contrat dit « créé » puis « envoyé » par votre clé, sous son nom. - Au plan gratuit, 5 envois de contrat par mois : au-delà, 429 « limite_plan » avec un Retry-After jusqu'au mois suivant. - Un modèle désactivé ou suspendu par le plan est refusé en 422 : GET /contract-templates le dit à l'avance. Requête : ```http POST https://kapacity.app/api/v1/w/{workspace_id}/contracts Authorization: Bearer kap_live_… Content-Type: application/json Idempotency-Key: contrat-jean-dupont-2026-09-23 { "modele_id": "3e5a7c9b-2d4f-4e6a-8b1c-9d0e2f4a6b8c", "contact_id": "7fc74cfb-c67a-43d2-868f-f4a8ab028475", "montant_cents": 450000 } ``` Réponse : ```json { "contrat": { "id": "b423fdff-34f8-4a04-a66f-68034f0d77fe", "titre": "Accompagnement 6 mois · Jean Dupont", "statut": "envoye", "montant_cents": 450000, "contact": { "id": "7fc74cfb-c67a-43d2-868f-f4a8ab028475", "nom": "Jean Dupont", "email": "jean@acme.fr" }, "signataire": { "nom": "Jean Dupont", "email": "jean@acme.fr" }, "lien_signature": "https://kapacity.app/c/ab29cbefa0874ba5b2cc1c0758aa9fcd6f276503a714411391a2e8017d780ca6", "cree_le": "2026-09-23T11:50:37.723277+00:00", "envoye_le": "2026-09-23T11:50:37.498Z" }, "email": { "envoye": true, "a": "jean@acme.fr" }, "automatisations": true } ``` ### Ventes et paiements #### `GET /products` Le catalogue de produits, avec prix et échéanciers. Portée minimale : `lecture`. - Prix, acompte et coût sont en CENTIMES. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/products Authorization: Bearer kap_live_… ``` Réponse : ```json { "produits": [ { "id": "p1…", "nom": "Accompagnement 6 mois", "prix_cents": 500000, "mensualites": 3, "actif": true } ] } ``` #### `GET /payments` Les paiements reçus, tels que le fournisseur les a constatés. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `depuis` | après le « ? » de l'adresse | date | non | Paiements reçus après cette date. | | `jusqu_a` | après le « ? » de l'adresse | date | non | Paiements reçus avant cette date. | | `statut` | après le « ? » de l'adresse | chaine | non | succeeded ou failed. | | `contact` | après le « ? » de l'adresse | chaine | non | Ne garde que les paiements de ce contact. | | `limite` | après le « ? » de l'adresse | entier | non | De 1 à 200. | | `curseur` | après le « ? » de l'adresse | chaine | non | Le curseur de la page suivante. | - Lecture seule, et volontairement : un paiement est un fait constaté chez le fournisseur, pas une donnée qu'on saisit. - Les montants sont en CENTIMES et HORS TAXES. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/payments?depuis=2026-09-01T00:00:00Z Authorization: Bearer kap_live_… ``` Réponse : ```json { "paiements": [ { "id": "pay1…", "recu_le": "2026-09-20T08:20:00.000Z", "montant_cents": 180000, "devise": "eur", "statut": "succeeded", "fournisseur": "whop", "payeur": { "nom": "Jean Dupont", "email": "jean@acme.fr" }, "contact": { "id": "l1…", "nom": "Jean Dupont" }, "rattache_a_une_vente": true } ], "curseur_suivant": null } ``` ### Équipe #### `GET /members` Les membres de l'espace et leur rôle. Portée minimale : `lecture`. - Une colonne « membre » (Sales, Setter) s'écrit avec l'identifiant rendu ici, ou avec l'e-mail ou le nom exact. - « equipes » donne les identifiants des équipes du membre, ceux que « GET /event-types » rend dans « equipes ». Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/members Authorization: Bearer kap_live_… ``` Réponse : ```json { "membres": [ { "id": "u1…", "nom": "Camille Royer", "email": "camille@…", "roles": [ "closer" ], "equipes": [ "t1…" ], "proprietaire": false } ] } ``` ### Journal d'activité #### `GET /activity` Le journal d'activité de l'espace : qui a fait quoi, quand. Portée minimale : `lecture`. **Réservé aux clés créées par le propriétaire de l'espace.** | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `depuis` | après le « ? » de l'adresse | date | non | Après cette date. | | `domaine` | après le « ? » de l'adresse | chaine | non | contacts, tables, contrats, booking… (ex. `contacts`) | | `qui` | après le « ? » de l'adresse | chaine | non | member, system, automation, public ou api. (ex. `api`) | | `limite` | après le « ? » de l'adresse | entier | non | De 1 à 200. | | `curseur` | après le « ? » de l'adresse | chaine | non | Le curseur de la page suivante. | - 🔴 RÉSERVÉ aux clés créées par le PROPRIÉTAIRE de l'espace : « Logs Workspace » lui est réservé dans l'application, l'API ne fait pas exception. - Le plus récent d'abord, contrairement aux autres listes : un journal se lit à l'envers. - Le genre « api » retrouve tout ce que vos intégrations ont écrit, avec le nom de la clé. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/activity?qui=api Authorization: Bearer kap_live_… ``` Réponse : ```json { "activite": [ { "id": "a1…", "le": "2026-09-22T11:02:00.000Z", "action": "lead.create", "phrase": "a ajouté le contact « Jean Dupont »", "acteur": { "genre": "api", "id": null, "nom": "API · n8n" }, "objet": { "nature": "lead", "id": "l1…", "libelle": "Jean Dupont" } } ], "curseur_suivant": null } ``` ### Webhooks #### `GET /webhooks` Vos abonnements aux événements, et la liste des événements possibles. Un abonnement dit « appelle cette adresse quand ceci se passe ». C'est ce qui remplace un scénario qui interroge l'API toutes les cinq minutes : il consommerait du quota pour rien et réagirait en retard. Portée minimale : `lecture`. - Le secret de signature n'est JAMAIS rendu ici : il n'est lisible qu'une fois, à la création. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/webhooks Authorization: Bearer kap_live_… ``` Réponse : ```json { "webhooks": [ { "id": "w1…", "url": "https://n8n.exemple.fr/webhook/abc", "evenements": [ "contrat.signe" ], "actif": true, "ignorer_api": true, "description": "Scénario de closing", "cree_le": "2026-09-22T09:00:00.000Z", "dernier_envoi_le": "2026-09-22T11:30:00.000Z", "echecs_consecutifs": 0 } ], "evenements_possibles": [ { "id": "contrat.signe", "libelle": "Quand un contrat est signé", "description": "Un contrat revient signé." } ] } ``` #### `POST /webhooks` S'abonner à un ou plusieurs événements. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `url` | dans le body JSON | chaine | oui | L'adresse appelée, en https. (ex. `https://n8n.exemple.fr/webhook/abc`) | | `evenements` | dans le body JSON | liste | oui | Les événements écoutés. (ex. `["contrat.signe", "rendez_vous.pris"]`) | | `nom` | dans le body JSON | chaine | non | Son nom, pour savoir qui est quoi dans la liste d'Intégrations et dans le journal (80 caractères au plus). Sans nom, il s'affiche par son adresse. (ex. `Relance après signature`) | | `entete` | dans le body JSON | objet | non | Un en-tête ajouté à chaque envoi, { nom, valeur }, pour que votre outil refuse tout ce qui ne vient pas de Kapacity (dans n8n : « Header Auth »). La valeur n'est plus jamais relisible. | | `ignorer_api` | dans le body JSON | booleen | non | Ne pas vous renvoyer ce que votre propre clé a écrit. Le garde-fou anti-boucle. | | `description` | dans le body JSON | chaine | non | Une note libre, plus longue que le nom (200 caractères au plus). | - 🔴 Le « secret » n'est rendu QU'ICI, une seule fois : notez-le. Il sert à vérifier notre signature, sans lui n'importe qui pourrait se faire passer pour Kapacity auprès de votre serveur. - L'adresse doit être publique et en https. Une adresse privée est refusée, à la création comme à chaque envoi. - 🔴 Si votre scénario ÉCRIT dans Kapacity et écoute les mêmes événements, mettez « ignorer_api » à true : sinon il se réveille lui-même, indéfiniment. Requête : ```http POST https://kapacity.app/api/v1/w/{workspace_id}/webhooks Authorization: Bearer kap_live_… Content-Type: application/json { "nom": "Relance après signature", "url": "https://n8n.exemple.fr/webhook/abc", "evenements": [ "contrat.signe" ], "entete": { "nom": "X-Api-Key", "valeur": "une-valeur-que-vous-choisissez" }, "ignorer_api": true } ``` Réponse : ```json { "webhook": { "id": "w1…", "nom": "Relance après signature", "url": "https://n8n.exemple.fr/webhook/abc", "evenements": [ "contrat.signe" ], "actif": true, "entete_nom": "X-Api-Key" }, "secret": "whsec_…" } ``` #### `PATCH /webhooks/{id}` Renommer un abonnement, changer son adresse, ses événements ou son en-tête, ou le réactiver. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de l'abonnement. | | `nom` | dans le body JSON | chaine | non | Le nouveau nom. Une chaîne vide ou null le retire. | | `url` | dans le body JSON | chaine | non | La nouvelle adresse, en https. | | `evenements` | dans le body JSON | liste | non | La nouvelle liste d'événements. Elle REMPLACE l'ancienne. | | `entete` | dans le body JSON | objet | non | Le nouvel en-tête, { nom, valeur } : la valeur est à redonner, elle n'est jamais relisible. null le retire. | | `actif` | dans le body JSON | booleen | non | Suspendre les envois, ou les reprendre. | | `ignorer_api` | dans le body JSON | booleen | non | Le garde-fou anti-boucle. | | `description` | dans le body JSON | chaine | non | La note libre. | - Seuls les champs envoyés changent : les autres restent tels quels. - Le secret ne se change pas : pour en changer, supprimez l'abonnement et recréez-le. C'est ce qui garantit qu'il n'existe qu'à un seul endroit. - Réactiver remet le compteur d'échecs à zéro : un abonnement réparé repart avec toutes ses chances. Requête : ```http PATCH https://kapacity.app/api/v1/w/{workspace_id}/webhooks/5c7e9a1b-3d5f-4b7d-9f1a-2c4e6a8b0d2f Authorization: Bearer kap_live_… Content-Type: application/json { "nom": "Relance après signature", "evenements": [ "contrat.envoye", "contrat.signe" ], "entete": { "nom": "X-Api-Key", "valeur": "une-nouvelle-valeur" } } ``` Réponse : ```json { "webhook": { "id": "w1…", "nom": "Relance après signature", "evenements": [ "contrat.envoye", "contrat.signe" ], "actif": true, "echecs_consecutifs": 0, "entete_nom": "X-Api-Key" } } ``` #### `DELETE /webhooks/{id}` Supprimer un abonnement, et son journal d'envois. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de l'abonnement. | - Définitif. Pour arrêter les envois sans perdre l'historique, passez « actif » à false. Requête : ```http DELETE https://kapacity.app/api/v1/w/{workspace_id}/webhooks/5c7e9a1b-3d5f-4b7d-9f1a-2c4e6a8b0d2f Authorization: Bearer kap_live_… ``` Réponse : ```json { "supprime": true, "id": "w1…" } ``` #### `POST /webhooks/{id}/test` Envoyer un événement d'exemple, pour brancher sans attendre un vrai. L'essai passe par la MÊME file que les vrais envois : même signature, mêmes en-têtes, mêmes reprises. Un essai qui aboutit prouve donc que le vrai aboutira. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de l'abonnement. | | `evenement` | après le « ? » de l'adresse | chaine | non | Lequel simuler. Par défaut, le premier de l'abonnement. (ex. `contrat.signe`) | - Le corps porte « essai: true » : votre scénario peut s'arrêter là plutôt que de créer une vraie facture. - La réponse est un 202 : l'envoi part dans les secondes qui suivent. Son résultat se lit dans le journal des envois. Requête : ```http POST https://kapacity.app/api/v1/w/{workspace_id}/webhooks/5c7e9a1b-3d5f-4b7d-9f1a-2c4e6a8b0d2f/test?evenement=contrat.signe Authorization: Bearer kap_live_… ``` Réponse : ```json { "envoi": { "id": "d1…", "event_id": "e1…", "evenement": "contrat.signe", "statut": "en_attente" } } ``` #### `GET /webhooks/{id}/deliveries` Le journal des envois : ce qui est parti, ce qui a été refusé, et pourquoi. Portée minimale : `lecture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de l'abonnement. | | `statut` | après le « ? » de l'adresse | chaine | non | en_attente, envoi, envoye, echec ou abandonne. (ex. `echec`) | | `limite` | après le « ? » de l'adresse | entier | non | De 1 à 200. | | `curseur` | après le « ? » de l'adresse | chaine | non | Le curseur de la page suivante. | - 🔴 C'est ici que « je ne reçois rien » trouve sa réponse : le code HTTP et le début de la réponse de VOTRE serveur y sont écrits. - Le plus récent d'abord : on vient y chercher le dernier envoi, pas rejouer l'historique. Requête : ```http GET https://kapacity.app/api/v1/w/{workspace_id}/webhooks/5c7e9a1b-3d5f-4b7d-9f1a-2c4e6a8b0d2f/deliveries?statut=echec Authorization: Bearer kap_live_… ``` Réponse : ```json { "envois": [ { "id": "d1…", "event_id": "e1…", "evenement": "contrat.signe", "statut": "echec", "tentatives": 4, "code_http": 401, "reponse": "Unauthorized", "erreur": null, "duree_ms": 180, "prochaine_tentative_le": null, "envoye_le": null, "cree_le": "2026-09-22T11:30:00.000Z" } ], "curseur": null } ``` #### `POST /webhooks/{id}/deliveries/{envoi}/replay` Renvoyer un événement déjà émis, par exemple après une panne de votre serveur. Portée minimale : `lecture_ecriture`. | paramètre | où | type | requis | description | | --- | --- | --- | --- | --- | | `id` | dans l'adresse | chaine | oui | L'identifiant de l'abonnement. | | `envoi` | dans l'adresse | chaine | oui | L'identifiant de l'envoi à rejouer. | - Le corps rejoué est celui de l'ÉPOQUE, pas l'état d'aujourd'hui : c'est bien l'événement qui est rejoué. - Le « webhook-id » reste le même : un destinataire qui déduplique reconnaît un doublon au lieu de traiter deux fois. - Un envoi encore en cours de tentatives ne se rejoue pas : attendez son résultat. Requête : ```http POST https://kapacity.app/api/v1/w/{workspace_id}/webhooks/5c7e9a1b-3d5f-4b7d-9f1a-2c4e6a8b0d2f/deliveries/9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a/replay Authorization: Bearer kap_live_… ``` Réponse : ```json { "envoi": { "id": "d2…", "event_id": "e1…", "evenement": "contrat.signe", "statut": "en_attente" }, "rejoue_depuis": "d1…" } ``` ## 8. Webhooks : être prévenu au lieu d'interroger 🔴 **Ne faites PAS interroger l'API toutes les cinq minutes par un scénario.** Cela brûle le quota à ne rien trouver et réagit en retard. Abonnez-vous : Kapacity appelle votre adresse au moment où la chose se passe. Les événements disponibles : | événement | quand | | --- | --- | | `contact.cree` | Un contact entre dans Mon Pipe ou dans Setting, par l'écran, un formulaire, une réservation, un import ou l'API. | | `contact.modifie` | Une valeur d'un contact change. Le corps porte l'état APRÈS la modification. | | `ligne.creee` | Une ligne entre dans une table créée par l'équipe (Mes Ventes, Produits, une table à vous). | | `ligne.modifiee` | Une valeur d'une ligne change, quelle que soit la colonne et quelle que soit la main qui l'écrit. | | `rendez_vous.pris` | Un prospect réserve un créneau sur une page publique de réservation. | | `rendez_vous.reprogramme` | Le créneau d'un rendez-vous change. | | `rendez_vous.annule` | Un rendez-vous est annulé, par le contact ou par l'équipe. | | `formulaire.soumis` | Quelqu'un termine un formulaire de qualification. Le corps porte le score. | | `contrat.envoye` | Un contrat part vers son signataire, depuis l'application ou par POST /contracts. « objet » est le CONTRAT, et « contact » le contact qui le signera. | | `contrat.signe` | Un contrat revient signé. C'est l'événement que la plupart des scénarios attendent. « objet » est le CONTRAT, et « contact » le contact qui l'a signé. | | `paiement.recu` | Un paiement est constaté chez le fournisseur et rattaché à l'espace. | Chaque envoi porte ces en-têtes : - `webhook-id` : L'identifiant de l'ÉVÉNEMENT. Deux envois du même événement le partagent : c'est avec lui qu'on déduplique. - `webhook-timestamp` : L'horodatage de la signature, en secondes. Au-delà de cinq minutes d'écart, refusez le message. - `webhook-signature` : « v1, » puis la signature HMAC-SHA256, en base64, de la chaîne « id.horodatage.corps ». - `X-Kapacity-Event` : Le nom de l'événement, pour aiguiller sans ouvrir le corps. **Vérifier la signature** (à faire : sinon n'importe qui peut se faire passer pour Kapacity) : ```js const attendue = crypto .createHmac('sha256', Buffer.from(secret.replace('whsec_', ''), 'base64url')) .update(`${headers['webhook-id']}.${headers['webhook-timestamp']}.${corpsBrut}`) .digest('base64'); // comparer à headers['webhook-signature'], qui vaut « v1, » ``` Le corps brut, tel qu'il est reçu : ne le ré-encodez pas avant de signer, un espace de plus change la signature. **La livraison est « au moins une fois ».** Quatre tentatives au plus (1, puis 5, puis 25 minutes) tant que votre serveur répond 5xx ou ne répond pas ; un 4xx est définitif. Le même événement peut donc arriver deux fois : dédupliquez sur `webhook-id`. 🔴 **Si votre scénario ÉCRIT dans Kapacity et écoute les mêmes événements**, créez son abonnement avec `ignorer_api: true`, sinon il se réveille lui-même, indéfiniment. ## 9. Ce qui n'existe PAS dans cette API À lire avant d'inventer un appel : - **Créer ou modifier une table, une colonne, un statut** : Décision de Nicolas du 22 septembre 2026 : c'est du DDL logique. L'API lit la structure, elle ne la façonne pas. Une intégration qui aurait besoin d'une colonne la demande à un administrateur. - **Visioconférence, enregistrements, transcriptions** : Module masqué derrière FEATURES.visio : rien de ce qui n'est pas en service ne s'expose. - **Pages opt-in, modèles d'e-mail, tutoriels** : Surfaces d'édition, sans usage d'automatisation identifié. À rouvrir si un client le demande, jamais par anticipation. - **Panel d'administration de la plateforme, page Associés** : Écrans internes de Kapacity, hors de l'espace d'un client. Ils n'ont rien à faire dans une API de tenant. - **Finances & Charges : les charges, leurs factures et leurs montants HT, TVA et TTC** : Données financières réservées aux administrateurs de l'espace (lecture comprise). Aucun usage d'automatisation identifié à l'ajout des factures et des montants HT et TTC (2 octobre 2026) : à rouvrir si un client le demande. - **Les factures de l'abonnement Kapacity et les informations de facturation de la société** : Documents comptables émis par Kapacity à l'espace (et non données du client sur ses propres ventes), réservés aux administrateurs, édités au premier téléchargement dans les Paramètres (0182, 2 octobre 2026). Aucun usage d'automatisation identifié : à rouvrir si un client demande à récupérer ses factures par l'API. - **Les inscrits gratuits versés dans l'espace de vente de Kapacity, et les commissions de notre équipe** : Mécaniques INTERNES à Kapacity (22 septembre et 2 octobre 2026) : elles n'existent que dans notre propre espace de vente, jamais dans celui d'un client. Les leads qu'elles créent se lisent par l'API de cet espace comme les autres. - **Créer, modifier ou supprimer une clé d'API** : Une clé ne se fabrique pas par une clé : ce serait une escalade de droits silencieuse. La création reste dans l'application, réservée au propriétaire et aux administrateurs. - **Inviter un membre ou lui créer son compte** : Même raison qu'une clé : une clé qui fabrique des comptes fabrique des accès. Faire entrer quelqu'un reste dans la page Équipe, réservé aux administrateurs et aux rôles qui ont le droit « Inviter un membre » (23 septembre 2026), dans un rôle qui n'a pas plus de droits que le leur. L'API lit les membres, elle n'en ajoute pas. - **Construire le parcours d'un formulaire : routes, tranches de score, cartes de l'onglet Parcours** : Surface d'édition (24 septembre 2026), comme l'éditeur de formulaire tout entier : la logique s'écrit dans l'application, où elle se voit en cartes et se vérifie. L'API lit les réponses, dont le score est déjà calculé sur le chemin suivi. - **Vérifier qu'un contact existe déjà avant de l'ajouter (fenêtre « Ce contact existe peut-être déjà »)** : Une mise en garde faite pour quelqu'un devant l'écran, qui choisit « Annuler » ou « Créer quand même ». Un scénario, lui, cherche le contact par sa liste (filtre par e-mail) avant d'écrire, et l'écriture groupée de l'API est déjà idempotente (28 septembre 2026). - **Acheter un pack de crédits IA** : Un paiement engage l'entreprise et se fait sur la page Whop, par un administrateur connecté : une clé ne paie pas. Le solde des crédits se lit dans l'application (24 septembre 2026). - **Droits et pages par rôle, colonne Admin comprise** : Régler ce que chaque rôle peut faire, c'est régler les accès : même raison qu'une clé ou une invitation. Le tableau reste dans la page Équipe, aux administrateurs, et sa colonne Admin au seul propriétaire de l'espace (24 septembre 2026). - **« Setup mon workspace avec l'IA » (nom, logo, formulaires et pages préparés à partir du site)** : Un geste d'accueil fait UNE seule fois par espace, offert, par un administrateur connecté qui coche la déclaration de droits sur son site : rien à automatiser. Ce qu'il crée (formulaires, réponses, contacts) se lit ensuite par l'API comme le reste (1er octobre 2026). - **Trier une grille du CRM en cliquant sur le nom d'une colonne** : C'est un réglage d'AFFICHAGE, pas une donnée : il vit dans l'adresse de la page et n'écrit rien. L'API rend les lignes dans un ordre stable (de la plus ancienne à la plus récente) pour qu'une synchronisation reprenne où elle s'est arrêtée ; l'intégration trie de son côté (28 septembre 2026). - **Consentement des visiteurs aux traceurs d'une page opt-in** : Le choix du visiteur vit dans SON navigateur, jamais chez Kapacity : il n'y a rien à lire ni à écrire. Les identifiants du pixel Meta et de Google Analytics se règlent dans l'éditeur de la page (29 septembre 2026). - **Disponibilités d'un membre : plages de la semaine et exceptions au jour le jour** : Chacun règle SES disponibilités, connecté, dans Agenda & RDV : une clé d'espace n'est pas un membre. Et l'API ne pose pas de rendez-vous (les disponibilités se décident côté Kapacity) ; une intégration qui voudrait bloquer un agenda passe par Google Agenda, déjà relu à chaque réservation (29 septembre 2026, 0175).