{
  "openapi": "3.1.0",
  "info": {
    "title": "API Kapacity",
    "version": "1.0.0",
    "description": "L'API publique de Kapacity : vos contacts, vos tables, vos rendez-vous, vos formulaires, vos contrats et vos ventes, lisibles et modifiables par vos outils. Une clé se crée dans Intégrations, onglet API. Elle porte sur UN espace."
  },
  "servers": [
    {
      "url": "https://kapacity.app"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "La clé d'API, préfixée « kap_live_ »."
      }
    }
  },
  "paths": {
    "/api/v1/w/{workspace_id}/me": {
      "get": {
        "operationId": "me",
        "summary": "Votre espace, votre plan et l'état de votre quota.",
        "description": "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.\n\nAucun paramètre : si cet appel répond 200, votre clé et votre identifiant d'espace sont bons.\n\nLes en-têtes X-Kapacity-Quota-* accompagnent CHAQUE réponse de l'API, pas seulement celle-ci.",
        "tags": [
          "Votre espace"
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de votre espace, affiché dans Intégrations, onglet API.",
            "schema": {
              "type": "string"
            },
            "example": "b7f1c0de-0000-4000-8000-000000000000"
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/tables": {
      "get": {
        "operationId": "tables_liste",
        "summary": "Les tables du CRM de cet espace, et la base de chacune.",
        "description": "Le deuxième appel d'une intégration, après /me : il donne les identifiants de table dont tout le reste a besoin.\n\n🔴 DEUX SORTES DE TABLES. Les tables « native » sont vos pipelines : « leads » = Mon Pipe, « leads_setting » = Setting. Leur identifiant est ce mot, pas un UUID.\n\nLes tables « personnalisee » sont celles que votre équipe a créées (Ventes, Setting, Ressources…), identifiées par un UUID.\n\nLes deux s'utilisent EXACTEMENT de la même façon par la suite : mêmes adresses, mêmes réponses.\n\nOn ne crée ni table ni colonne par API : cette partie du CRM se lit, elle ne se façonne pas.",
        "tags": [
          "CRM : tables et lignes"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/tables/{id}": {
      "get": {
        "operationId": "table_colonnes",
        "summary": "Les colonnes d'une table : identifiant, nom, type, choix possibles.",
        "description": "🔴 L'erreur numéro un d'une intégration : dans Kapacity, une colonne est identifiée par un UUID, pas par son nom.\n\nOn 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.\n\nUne colonne « lecture_seule » (formule, cumul, lookup) est refusée en écriture, et l'API dit pourquoi.",
        "tags": [
          "CRM : tables et lignes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de la table, rendu par GET /tables : un UUID, ou « leads » / « leads_setting » pour vos pipelines.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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é"
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/tables/{id}/records": {
      "get": {
        "operationId": "records_liste",
        "summary": "Les lignes d'une table, page par page.",
        "description": "Les lignes sortent de la plus ANCIENNE à la plus récente : c'est ce qui rend une synchronisation reprenable.\n\nPagination par CURSEUR, jamais par numéro de page : une ligne créée entre deux appels ne fait sauter personne.\n\nTant que « curseur_suivant » n'est pas null, il reste des lignes.\n\nLes 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.\n\n🔎 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).\n\nPour 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).\n\nPour chercher sans savoir dans quelle colonne : recherche=Dupont.\n\nLe 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.",
        "tags": [
          "CRM : tables et lignes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de la table (UUID, ou « leads » / « leads_setting »).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "description": "De 1 à 200. 50 par défaut.",
            "schema": {
              "type": "integer"
            },
            "example": "100"
          },
          {
            "name": "curseur",
            "in": "query",
            "required": false,
            "description": "Le « curseur_suivant » de la réponse précédente.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "modifie_depuis",
            "in": "query",
            "required": false,
            "description": "Ne rend que les lignes modifiées depuis cette date (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-09-01T00:00:00Z"
          },
          {
            "name": "filtre",
            "in": "query",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "string"
            },
            "example": "Email:eq:jean@acme.fr"
          },
          {
            "name": "recherche",
            "in": "query",
            "required": false,
            "description": "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).",
            "schema": {
              "type": "string"
            },
            "example": "Dupont"
          },
          {
            "name": "champ",
            "in": "query",
            "required": false,
            "description": "Forme simple, équivalente à filtre=<champ>:eq:<vaut>.",
            "schema": {
              "type": "string"
            },
            "example": "Statut"
          },
          {
            "name": "vaut",
            "in": "query",
            "required": false,
            "description": "La valeur exacte attendue par « champ ».",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "colonnes",
            "in": "query",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "boolean"
            },
            "example": "1"
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "« 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.",
            "schema": {
              "type": "string"
            },
            "example": "lisible"
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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"
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      },
      "post": {
        "operationId": "record_creer",
        "summary": "Crée une ligne dans une table.",
        "description": "Une valeur invalide est IGNORÉE et expliquée dans « ignores », jamais convertie en effacement.\n\nSi AUCUNE valeur n'a pu être écrite, l'appel est refusé en 422 plutôt que de créer une ligne vide.\n\nUne création déclenche les automatisations « Quand un enregistrement est créé », comme une saisie à la main.\n\nDans « 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 ».",
        "tags": [
          "CRM : tables et lignes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de la table (UUID, ou « leads » / « leads_setting »).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automatisations",
            "in": "query",
            "required": false,
            "description": "false pour écrire SANS déclencher les automatisations. Le garde-fou anti-boucle à utiliser pour un import en masse.",
            "schema": {
              "type": "boolean"
            },
            "example": "false"
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "« 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.",
            "schema": {
              "type": "string"
            },
            "example": "lisible"
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "ligne": {
                    "id": "a1b2…",
                    "cree_le": "2026-09-22T10:00:00.000Z",
                    "modifie_le": null,
                    "champs": {
                      "f1a2…": "Acme"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "champs"
                ],
                "properties": {
                  "champs": {
                    "type": "object",
                    "description": "Les valeurs, rangées par NOM ou par identifiant de colonne."
                  }
                }
              },
              "example": {
                "champs": {
                  "Prénom": "Jean",
                  "Nom": "Dupont",
                  "Email": "jean@acme.fr"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/tables/{id}/records/bulk": {
      "post": {
        "operationId": "records_bulk",
        "summary": "Créer jusqu'à 100 lignes en un seul appel.",
        "description": "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.\n\n🔴 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.\n\n🔴 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.\n\n201 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.\n\nPosez 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.",
        "tags": [
          "CRM : tables et lignes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de la table, ou « leads » / « leads_setting ».",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automatisations",
            "in": "query",
            "required": false,
            "description": "1 pour déclencher les automatisations. COUPÉ par défaut ici.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "« 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.",
            "schema": {
              "type": "string"
            },
            "example": "lisible"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Rejouer le même appel rend la réponse du premier au lieu de créer des doublons.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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": []
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "lignes"
                ],
                "properties": {
                  "lignes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Jusqu'à 100 objets { champs: { … } }."
                  }
                }
              },
              "example": {
                "lignes": [
                  {
                    "champs": {
                      "Prénom": "Jean",
                      "Nom": "Dupont",
                      "Email": "jean@acme.fr"
                    }
                  },
                  {
                    "champs": {
                      "Prénom": "Léa",
                      "Nom": "Martin",
                      "Email": "lea@acme.fr"
                    }
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/records/{id}": {
      "get": {
        "operationId": "record_lire",
        "summary": "Une ligne et les colonnes de sa table.",
        "description": "La ligne porte sa table : l'adresse n'a pas besoin de la répéter.",
        "tags": [
          "CRM : tables et lignes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de la ligne.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "« 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.",
            "schema": {
              "type": "string"
            },
            "example": "lisible"
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      },
      "patch": {
        "operationId": "record_modifier",
        "summary": "Modifie les valeurs d'une ligne.",
        "description": "Pour VIDER une colonne, envoyez null (ou une chaîne vide, ou une liste vide).\n\nUne modification déclenche les automatisations « Quand un enregistrement est modifié ».",
        "tags": [
          "CRM : tables et lignes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de la ligne.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automatisations",
            "in": "query",
            "required": false,
            "description": "false pour ne rien déclencher.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "« 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.",
            "schema": {
              "type": "string"
            },
            "example": "lisible"
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "ligne": {
                    "id": "a1b2…",
                    "cree_le": "2026-09-02T10:00:00.000Z",
                    "modifie_le": "2026-09-22T11:00:00.000Z",
                    "champs": {
                      "f1a2…": "Acme SAS"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "champs"
                ],
                "properties": {
                  "champs": {
                    "type": "object",
                    "description": "Seules les colonnes citées changent, les autres restent."
                  }
                }
              },
              "example": {
                "champs": {
                  "Client": "Acme SAS",
                  "Montant": 350000
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "record_supprimer",
        "summary": "Supprime une ligne. Définitif.",
        "description": "Aucune corbeille : la ligne est perdue. Le journal d'activité garde la trace de la suppression et de la clé qui l'a faite.",
        "tags": [
          "CRM : tables et lignes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de la ligne.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "supprime": true,
                  "id": "a1b2…"
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/event-types": {
      "get": {
        "operationId": "event_types",
        "summary": "Les types de rendez-vous, avec leur lien public de réservation.",
        "description": "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.\n\nPoser 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.\n\n« 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.\n\n« 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.\n\n« 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).\n\n« 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.",
        "tags": [
          "Rendez-vous"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/bookings": {
      "get": {
        "operationId": "bookings",
        "summary": "Les rendez-vous, du plus ancien au plus récent.",
        "description": "Chaque identifiant vient avec son nom : un scénario n'a jamais à faire un second appel pour savoir qui est « u1… ».",
        "tags": [
          "Rendez-vous"
        ],
        "parameters": [
          {
            "name": "depuis",
            "in": "query",
            "required": false,
            "description": "Rendez-vous commençant après cette date.",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-09-22T00:00:00Z"
          },
          {
            "name": "jusqu_a",
            "in": "query",
            "required": false,
            "description": "Rendez-vous commençant avant cette date.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "statut",
            "in": "query",
            "required": false,
            "description": "Ne garde que ce statut : confirme, annule ou termine.",
            "schema": {
              "type": "string"
            },
            "example": "confirme"
          },
          {
            "name": "contact",
            "in": "query",
            "required": false,
            "description": "Ne garde que les rendez-vous de ce contact (son identifiant).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "description": "De 1 à 200. 50 par défaut.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "curseur",
            "in": "query",
            "required": false,
            "description": "Le « curseur_suivant » de la réponse précédente.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/forms": {
      "get": {
        "operationId": "forms",
        "summary": "Les formulaires de qualification et leur lien public.",
        "description": "",
        "tags": [
          "Formulaires"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "formulaires": [
                    {
                      "id": "f1…",
                      "titre": "Candidature",
                      "statut": "publie",
                      "questions": 12,
                      "lien_public": "https://kapacity.app/f/candidature"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/forms/{id}/responses": {
      "get": {
        "operationId": "form_responses",
        "summary": "Les réponses d'un formulaire, avec leur score.",
        "description": "Les réponses sortent avec le LIBELLÉ de chaque question, pas son identifiant : elles se lisent sans relire le formulaire.",
        "tags": [
          "Formulaires"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant du formulaire.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "depuis",
            "in": "query",
            "required": false,
            "description": "Réponses soumises après cette date.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "qualifie",
            "in": "query",
            "required": false,
            "description": "true ou false pour ne garder que les qualifiés ou les non qualifiés.",
            "schema": {
              "type": "boolean"
            },
            "example": "true"
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "description": "De 1 à 200.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "curseur",
            "in": "query",
            "required": false,
            "description": "Le curseur de la page suivante.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/contracts": {
      "get": {
        "operationId": "contracts",
        "summary": "Les contrats, leur statut et leurs dates clés.",
        "description": "Le corps du contrat n'est pas rendu ici, il est long : GET /contracts/{id} le donne.\n\nLes montants sont en CENTIMES.",
        "tags": [
          "Contrats"
        ],
        "parameters": [
          {
            "name": "depuis",
            "in": "query",
            "required": false,
            "description": "Contrats créés après cette date.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "statut",
            "in": "query",
            "required": false,
            "description": "brouillon, envoye, vu ou signe.",
            "schema": {
              "type": "string"
            },
            "example": "signe"
          },
          {
            "name": "contact",
            "in": "query",
            "required": false,
            "description": "Ne garde que les contrats de ce contact.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "description": "De 1 à 200.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "curseur",
            "in": "query",
            "required": false,
            "description": "Le curseur de la page suivante.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      },
      "post": {
        "operationId": "contracts_envoyer",
        "summary": "Envoie un contrat à un contact, au montant que vous indiquez, et rend son lien de signature.",
        "description": "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.\n\nLe 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.\n\nLe 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.\n\n« email » dit si l'e-mail est parti : { envoye: true, a: <adresse> }. 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.\n\n🔴 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.\n\nLe message part de la boîte du Sales du contact s'il en a relié une, sinon de la boîte de l'espace.\n\nLe contrat est FIGÉ à l'envoi : modifier le contact ensuite ne change pas le contrat.\n\nLa piste d'audit du contrat dit « créé » puis « envoyé » par votre clé, sous son nom.\n\nAu plan gratuit, 5 envois de contrat par mois : au-delà, 429 « limite_plan » avec un Retry-After jusqu'au mois suivant.\n\nUn modèle désactivé ou suspendu par le plan est refusé en 422 : GET /contract-templates le dit à l'avance.",
        "tags": [
          "Contrats"
        ],
        "parameters": [
          {
            "name": "automatisations",
            "in": "query",
            "required": false,
            "description": "false pour n'en déclencher aucune (ni le webhook « contrat.envoye »).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Rejouer le même appel rend le premier envoi au lieu d'envoyer un second contrat.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "modele_id",
                  "contact_id",
                  "montant_cents"
                ],
                "properties": {
                  "modele_id": {
                    "type": "string",
                    "description": "L'identifiant du MODÈLE de contrat à envoyer. Vous le trouvez avec GET /contract-templates (champ « id »)."
                  },
                  "contact_id": {
                    "type": "string",
                    "description": "L'identifiant du contact à qui l'envoyer, de Mon Pipe ou de Setting. Vous le trouvez avec GET /tables/leads/records?filtre=Email:eq:<son e-mail> (champ « id »), ou dans le webhook « contact.cree »."
                  },
                  "montant_cents": {
                    "type": "integer",
                    "description": "Le montant du contrat, en CENTIMES : 450000 pour 4 500 €. Il est écrit dans le contrat à la place de {{montant}}. Pour 0 €, écrivez 0."
                  },
                  "titre": {
                    "type": "string",
                    "description": "Le titre du contrat. Absent : « <modèle> · <nom du contact> »."
                  },
                  "envoyer_email": {
                    "type": "boolean",
                    "description": "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."
                  },
                  "message_email_id": {
                    "type": "string",
                    "description": "Un autre modèle d'e-mail de contrat que celui rattaché au modèle."
                  }
                }
              },
              "example": {
                "modele_id": "3e5a7c9b-2d4f-4e6a-8b1c-9d0e2f4a6b8c",
                "contact_id": "7fc74cfb-c67a-43d2-868f-f4a8ab028475",
                "montant_cents": 450000
              }
            }
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/contracts/{id}": {
      "get": {
        "operationId": "contract",
        "summary": "Un contrat, son corps et sa piste d'audit.",
        "description": "La piste d'audit est ce qui donne sa valeur probante à la signature : horodatage et adresse IP de chaque étape.\n\nElle est en ajout seul en base : l'API la lit, personne ne la réécrit.",
        "tags": [
          "Contrats"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant du contrat.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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.…"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/contract-templates": {
      "get": {
        "operationId": "contract_templates",
        "summary": "Les modèles de contrat, pour savoir lequel envoyer.",
        "description": "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.\n\nLe « id » de chaque modèle est le « modele_id » à passer à POST /contracts.\n\n« 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.\n\n« 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.\n\n« 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.",
        "tags": [
          "Contrats"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/products": {
      "get": {
        "operationId": "products",
        "summary": "Le catalogue de produits, avec prix et échéanciers.",
        "description": "Prix, acompte et coût sont en CENTIMES.",
        "tags": [
          "Ventes et paiements"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "produits": [
                    {
                      "id": "p1…",
                      "nom": "Accompagnement 6 mois",
                      "prix_cents": 500000,
                      "mensualites": 3,
                      "actif": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/payments": {
      "get": {
        "operationId": "payments",
        "summary": "Les paiements reçus, tels que le fournisseur les a constatés.",
        "description": "Lecture seule, et volontairement : un paiement est un fait constaté chez le fournisseur, pas une donnée qu'on saisit.\n\nLes montants sont en CENTIMES et HORS TAXES.",
        "tags": [
          "Ventes et paiements"
        ],
        "parameters": [
          {
            "name": "depuis",
            "in": "query",
            "required": false,
            "description": "Paiements reçus après cette date.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "jusqu_a",
            "in": "query",
            "required": false,
            "description": "Paiements reçus avant cette date.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "statut",
            "in": "query",
            "required": false,
            "description": "succeeded ou failed.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contact",
            "in": "query",
            "required": false,
            "description": "Ne garde que les paiements de ce contact.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "description": "De 1 à 200.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "curseur",
            "in": "query",
            "required": false,
            "description": "Le curseur de la page suivante.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/members": {
      "get": {
        "operationId": "members",
        "summary": "Les membres de l'espace et leur rôle.",
        "description": "Une colonne « membre » (Sales, Setter) s'écrit avec l'identifiant rendu ici, ou avec l'e-mail ou le nom exact.\n\n« equipes » donne les identifiants des équipes du membre, ceux que « GET /event-types » rend dans « equipes ».",
        "tags": [
          "Équipe"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "membres": [
                    {
                      "id": "u1…",
                      "nom": "Camille Royer",
                      "email": "camille@…",
                      "roles": [
                        "closer"
                      ],
                      "equipes": [
                        "t1…"
                      ],
                      "proprietaire": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/activity": {
      "get": {
        "operationId": "activity",
        "summary": "Le journal d'activité de l'espace : qui a fait quoi, quand.",
        "description": "🔴 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.\n\nLe plus récent d'abord, contrairement aux autres listes : un journal se lit à l'envers.\n\nLe genre « api » retrouve tout ce que vos intégrations ont écrit, avec le nom de la clé.",
        "tags": [
          "Journal d'activité"
        ],
        "parameters": [
          {
            "name": "depuis",
            "in": "query",
            "required": false,
            "description": "Après cette date.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "domaine",
            "in": "query",
            "required": false,
            "description": "contacts, tables, contrats, booking…",
            "schema": {
              "type": "string"
            },
            "example": "contacts"
          },
          {
            "name": "qui",
            "in": "query",
            "required": false,
            "description": "member, system, automation, public ou api.",
            "schema": {
              "type": "string"
            },
            "example": "api"
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "description": "De 1 à 200.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "curseur",
            "in": "query",
            "required": false,
            "description": "Le curseur de la page suivante.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/webhooks": {
      "get": {
        "operationId": "webhooks_liste",
        "summary": "Vos abonnements aux événements, et la liste des événements possibles.",
        "description": "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.\n\nLe secret de signature n'est JAMAIS rendu ici : il n'est lisible qu'une fois, à la création.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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é."
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      },
      "post": {
        "operationId": "webhooks_creer",
        "summary": "S'abonner à un ou plusieurs événements.",
        "description": "🔴 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.\n\nL'adresse doit être publique et en https. Une adresse privée est refusée, à la création comme à chaque envoi.\n\n🔴 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.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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_…"
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "evenements"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "L'adresse appelée, en https."
                  },
                  "evenements": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Les événements écoutés."
                  },
                  "nom": {
                    "type": "string",
                    "description": "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."
                  },
                  "entete": {
                    "type": "object",
                    "description": "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": {
                    "type": "boolean",
                    "description": "Ne pas vous renvoyer ce que votre propre clé a écrit. Le garde-fou anti-boucle."
                  },
                  "description": {
                    "type": "string",
                    "description": "Une note libre, plus longue que le nom (200 caractères au plus)."
                  }
                }
              },
              "example": {
                "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
              }
            }
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/webhooks/{id}": {
      "patch": {
        "operationId": "webhooks_modifier",
        "summary": "Renommer un abonnement, changer son adresse, ses événements ou son en-tête, ou le réactiver.",
        "description": "Seuls les champs envoyés changent : les autres restent tels quels.\n\nLe 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.\n\nRéactiver remet le compteur d'échecs à zéro : un abonnement réparé repart avec toutes ses chances.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de l'abonnement.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "webhook": {
                    "id": "w1…",
                    "nom": "Relance après signature",
                    "evenements": [
                      "contrat.envoye",
                      "contrat.signe"
                    ],
                    "actif": true,
                    "echecs_consecutifs": 0,
                    "entete_nom": "X-Api-Key"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [],
                "properties": {
                  "nom": {
                    "type": "string",
                    "description": "Le nouveau nom. Une chaîne vide ou null le retire."
                  },
                  "url": {
                    "type": "string",
                    "description": "La nouvelle adresse, en https."
                  },
                  "evenements": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "La nouvelle liste d'événements. Elle REMPLACE l'ancienne."
                  },
                  "entete": {
                    "type": "object",
                    "description": "Le nouvel en-tête, { nom, valeur } : la valeur est à redonner, elle n'est jamais relisible. null le retire."
                  },
                  "actif": {
                    "type": "boolean",
                    "description": "Suspendre les envois, ou les reprendre."
                  },
                  "ignorer_api": {
                    "type": "boolean",
                    "description": "Le garde-fou anti-boucle."
                  },
                  "description": {
                    "type": "string",
                    "description": "La note libre."
                  }
                }
              },
              "example": {
                "nom": "Relance après signature",
                "evenements": [
                  "contrat.envoye",
                  "contrat.signe"
                ],
                "entete": {
                  "nom": "X-Api-Key",
                  "valeur": "une-nouvelle-valeur"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "webhooks_supprimer",
        "summary": "Supprimer un abonnement, et son journal d'envois.",
        "description": "Définitif. Pour arrêter les envois sans perdre l'historique, passez « actif » à false.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de l'abonnement.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "supprime": true,
                  "id": "w1…"
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/webhooks/{id}/test": {
      "post": {
        "operationId": "webhooks_test",
        "summary": "Envoyer un événement d'exemple, pour brancher sans attendre un vrai.",
        "description": "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.\n\nLe corps porte « essai: true » : votre scénario peut s'arrêter là plutôt que de créer une vraie facture.\n\nLa réponse est un 202 : l'envoi part dans les secondes qui suivent. Son résultat se lit dans le journal des envois.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de l'abonnement.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "evenement",
            "in": "query",
            "required": false,
            "description": "Lequel simuler. Par défaut, le premier de l'abonnement.",
            "schema": {
              "type": "string"
            },
            "example": "contrat.signe"
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "envoi": {
                    "id": "d1…",
                    "event_id": "e1…",
                    "evenement": "contrat.signe",
                    "statut": "en_attente"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "webhooks_envois",
        "summary": "Le journal des envois : ce qui est parti, ce qui a été refusé, et pourquoi.",
        "description": "🔴 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.\n\nLe plus récent d'abord : on vient y chercher le dernier envoi, pas rejouer l'historique.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de l'abonnement.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "statut",
            "in": "query",
            "required": false,
            "description": "en_attente, envoi, envoye, echec ou abandonne.",
            "schema": {
              "type": "string"
            },
            "example": "echec"
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "description": "De 1 à 200.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "curseur",
            "in": "query",
            "required": false,
            "description": "Le curseur de la page suivante.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "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
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    },
    "/api/v1/w/{workspace_id}/webhooks/{id}/deliveries/{envoi}/replay": {
      "post": {
        "operationId": "webhooks_rejouer",
        "summary": "Renvoyer un événement déjà émis, par exemple après une panne de votre serveur.",
        "description": "Le corps rejoué est celui de l'ÉPOQUE, pas l'état d'aujourd'hui : c'est bien l'événement qui est rejoué.\n\nLe « webhook-id » reste le même : un destinataire qui déduplique reconnaît un doublon au lieu de traiter deux fois.\n\nUn envoi encore en cours de tentatives ne se rejoue pas : attendez son résultat.",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "L'identifiant de l'abonnement.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "envoi",
            "in": "path",
            "required": true,
            "description": "L'identifiant de l'envoi à rejouer.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "envoi": {
                    "id": "d2…",
                    "event_id": "e1…",
                    "evenement": "contrat.signe",
                    "statut": "en_attente"
                  },
                  "rejoue_depuis": "d1…"
                }
              }
            }
          },
          "401": {
            "description": "Clé d'API absente. Ajoutez l'en-tête « Authorization: Bearer kap_live_… » à votre requête."
          },
          "403": {
            "description": "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."
          },
          "404": {
            "description": "Cet élément n'existe pas dans cet espace."
          },
          "422": {
            "description": "La requête a été refusée : une valeur ne convient pas."
          },
          "429": {
            "description": "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."
          }
        }
      }
    }
  },
  "x-webhooks": {
    "contact.cree": {
      "summary": "Contact créé",
      "description": "Un contact entre dans Mon Pipe ou dans Setting, par l'écran, un formulaire, une réservation, un import ou l'API.",
      "example": {
        "evenement": "contact.cree",
        "emis_le": "2026-09-22T11:02:00.000Z",
        "provenance": "visiteur",
        "table": "leads",
        "objet": {
          "id": "l1…",
          "champs": {
            "first_name": "Jean",
            "last_name": "Dupont",
            "email": "jean@acme.test",
            "statut": "Nouveau"
          }
        }
      }
    },
    "contact.modifie": {
      "summary": "Contact modifié",
      "description": "Une valeur d'un contact change. Le corps porte l'état APRÈS la modification."
    },
    "ligne.creee": {
      "summary": "Ligne créée",
      "description": "Une ligne entre dans une table créée par l'équipe (Mes Ventes, Produits, une table à vous)."
    },
    "ligne.modifiee": {
      "summary": "Ligne modifiée",
      "description": "Une valeur d'une ligne change, quelle que soit la colonne et quelle que soit la main qui l'écrit."
    },
    "rendez_vous.pris": {
      "summary": "Rendez-vous pris",
      "description": "Un prospect réserve un créneau sur une page publique de réservation."
    },
    "rendez_vous.reprogramme": {
      "summary": "Rendez-vous reprogrammé",
      "description": "Le créneau d'un rendez-vous change."
    },
    "rendez_vous.annule": {
      "summary": "Rendez-vous annulé",
      "description": "Un rendez-vous est annulé, par le contact ou par l'équipe."
    },
    "formulaire.soumis": {
      "summary": "Formulaire soumis",
      "description": "Quelqu'un termine un formulaire de qualification. Le corps porte le score."
    },
    "contrat.envoye": {
      "summary": "Contrat envoyé",
      "description": "Un contrat part vers son signataire, depuis l'application ou par POST /contracts. « objet » est le CONTRAT, et « contact » le contact qui le signera.",
      "example": {
        "evenement": "contrat.envoye",
        "emis_le": "2026-09-23T14:00:00.000Z",
        "provenance": "api",
        "table": "contracts",
        "objet": {
          "id": "c1…",
          "champs": {
            "lead_id": "l1…",
            "title": "Accompagnement 6 mois · Jean Dupont",
            "status": "envoye",
            "value_cents": 500000,
            "signer_email": "jean@acme.test",
            "sent_at": "2026-09-23T14:00:00.000Z"
          }
        },
        "contact": {
          "id": "l1…",
          "champs": {
            "first_name": "Jean",
            "last_name": "Dupont",
            "email": "jean@acme.test",
            "stage": "contrat_envoye"
          }
        }
      }
    },
    "contrat.signe": {
      "summary": "Contrat signé",
      "description": "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é.",
      "example": {
        "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"
          }
        }
      }
    },
    "paiement.recu": {
      "summary": "Paiement reçu",
      "description": "Un paiement est constaté chez le fournisseur et rattaché à l'espace."
    }
  }
}