Documentation

Le verdict de Kwoll, dans vos outils

La même politique que l’application, la même version de règles, le même vocabulaire de trois niveaux. Un connecteur de messagerie, un script de rapprochement bancaire ou votre supervision appellent Kwoll et reçoivent ce que verrait la personne devant l’écran.

Contrat v1.0.0 · description OpenAPI 3.1 : /api/v1/openapi.json

Ce qu’elle ne fait pas, et pourquoi

L’API rend les contrôles déterministes, les listes de menaces, les signalements et ce que Kwoll a déjà vu ailleurs. Elle n’appelle ni modèle de langage, ni recherche d’image inversée. Ces deux étages reposent sur un consentement donné pièce par pièce par une personne : un connecteur qui transmet mille messages par nuit n’a donné aucun consentement pour mille pièces.

Vos appels ne nourrissent pas non plus le corpus de campagnes. Une intégration de messagerie voit passer autant de lettres d’information que d’arnaques ; y verser ce flux fausserait ce que Kwoll détecte pour tout le monde.

Kwoll ne dit jamais qu’un message est sûr. none signifie « aucun signal déterminant », et quand incomplet vaut true, il signifie « je n’ai pas pu vérifier », jamais « je n’ai rien trouvé ». Les trois niveaux sont none, caution et danger.

S’authentifier

Une clé se crée sur vos clés d’API et ne s’affiche qu’une fois. Elle appartient à l’organisation, et au siège de la personne qui l’a créée : si cette personne quitte l’organisation, la clé cesse de fonctionner.

Authorization: Bearer kwoll_VOTRE_CLE

En en-tête, jamais en paramètre d’URL. Une clé dans une URL se retrouve dans les journaux d’accès, dans l’historique du navigateur et dans l’en-tête Referer de la requête suivante. L’API refuse d’ailleurs toute autre forme.

Deux plafonds s’appliquent à chaque appel : celui de la clé, puis celui de l’organisation. Le premier existe pour qu’un script en boucle sur une intégration ne vide pas le budget de tous les autres.

POST/api/v1/verifier

Le verdict d’un message. Portée verifier. Le texte est plafonné à 4 000 caractères, et c’est la fin qui est gardée : dans un e-mail transféré, l’original (donc l’arnaque) se trouve en bas, sous les en-têtes de transfert.

curl https://kwoll.be/api/v1/verifier \
  -H "Authorization : Bearer kwoll_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{"texte":"Bonjour, notre banque a changé. Merci de virer\n         la facture sur BE71 0961 2345 6769."}'
{
  "niveau": "danger",
  "incomplet": false,
  "politique": "2026.09.41",
  "regles": ["R-BANK-CHANGE"],
  "signaux": [
    { "code": "bank_change", "preuve": "changement de compte", "poids": 3 },
    { "code": "iban_country", "preuve": "BE", "poids": 1 }
  ]
}

Journalisez politique avec le verdict. C’est la version des règles qui ont tranché ; sans elle, un verdict relu six mois plus tard n’est plus explicable. Les signaux portent des codes stables à traduire chez vous : les libellés lisibles de Kwoll sont écrits pour quelqu’un qui vient de coller un message, et n’ont pas de sens dans un journal de supervision.

POST/api/v1/entites

Combien de comptes distincts, en dehors du vôtre, ont fait vérifier cet IBAN, ce numéro ou ce lien. Portée entites. Vingt entités par appel, et un seul appel de quota : un message contient souvent un compte, un numéro et un lien à la fois.

curl https://kwoll.be/api/v1/entites \
  -H "Authorization: Bearer kwoll_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{"entites":[{"type":"iban","valeur":"BE71 0961 2345 6769"},
                 {"type":"numero","valeur":"+32470123456"}]}'
{
  "reputations": [
    { "type": "iban", "valeur": "BE71 0961 2345 6769",
      "personnes": 7, "connue": true },
    { "type": "numero", "valeur": "+32470123456",
      "personnes": null, "connue": false }
  ],
  "ecartees": 0
}

personnes vaut null en dessous de 3 comptes distincts, et connue vaut alors false. Ce n’est pas un zéro : c’est « on ne dit rien ». Répondre « une personne a vérifié cet IBAN » apprendrait à un appelant qu’un individu s’est inquiété d’un compte précis, et permettrait, requête après requête, de reconstituer qui a vérifié quoi.

GET/api/v1/entites/{type}/{valeur}

La même réponse, pour une seule entité, dans la forme que l’on attend d’une API REST.

Préférez le POST ci-dessus pour un IBAN ou un numéro. Une valeur placée dans un chemin d’URL se retrouve dans les journaux d’accès de la plateforme, dans ceux de votre proxy d’entreprise et dans tout cache intermédiaire. Le corps d’une requête, lui, n’est journalisé nulle part. Cette forme reste commode pour un domaine ou un lien déjà public.

GET/api/v1/analyses

Les vérifications faites avec cette clé, sur les quatre-vingt-dix derniers jours, cinquante au plus par appel. Portée analyses.

curl "https://kwoll.be/api/v1/analyses?limite=20" \
  -H "Authorization: Bearer kwoll_VOTRE_CLE"

Vos appels à vous, jamais ceux de vos collaborateurs. Les vérifications d’une personne ne sont visibles que par elle, y compris pour un administrateur, y compris par une clé d’API : c’est écrit sur le compte de chacun, et ce serait faux autrement. Les chiffres à l’échelle de l’organisation viendront par des agrégats. Aucun contenu de message n’est rendu : vous l’avez déjà.

GET/api/v1/campagnes

Les regroupements de messages très proches reçus par plusieurs de vos collaborateurs. Exactement ce que montre votre tableau de bord. Portée campagnes.

curl "https://kwoll.be/api/v1/campagnes?jours=30" \
  -H "Authorization : Bearer kwoll_VOTRE_CLE"

{
  "jours": 30,
  "campagnes": [
    {
      "id": "3f2b8c1a-…",
      "etiquette": "Fausse expiration de mot de passe",
      "niveau": "danger",
      "collaborateurs": 4,
      "messages": 4,
      "derniereVue": "2026-08-21T09:00:00Z"
    }
  ],
  "note": "Un envoi légitime en masse produit exactement le même regroupement : ce qui est constaté est une répétition."
}

Les mêmes seuils qu’à l’écran, et ils ne se contournent pas par cette porte : au moins deux collaborateurs pour qu’un regroupement existe, et aucun chiffre du tout tant que moins de trois personnes ont fait vérifier un message. L’organisation est celle de la clé ; il n’y a aucun paramètre pour en désigner une autre.

GET/api/v1/campagnes/{id}/indicateurs

Les domaines, numéros et IBAN partagés par au moins deux collaborateurs dans cette campagne : ce qu’on transmet à un filtrage de messagerie. Portée campagnes.

Relevés dans le TEXTE des messages, jamais supposés par un modèle, et jamais tirés du message d’une seule personne. Une liste vide n’est pas une erreur : un même texte circule parfois avec un lien différent par personne.

Les refus

Un refus arrive toujours dans la même forme : { "erreur": "…" }, avec un code fermé. Ils se distinguent parce qu’ils appellent des conduites opposées.

401 cle_manquante
Aucun en-tête `Authorization : Bearer …`.
401 cle_invalide
La clé est inconnue, révoquée, ou la personne à qui elle appartient a quitté l’organisation. Un seul code pour les trois : les distinguer dirait à un appelant si une clé a existé.
403 portee_absente
La clé existe, mais n’a pas cette portée. Inutile de réessayer : créez une clé avec la portée qu’il faut.
429 quota_atteint
Le plafond du jour est atteint, pour cette clé ou pour l’organisation. Il se remet à zéro à minuit.
503 indisponible
Le compteur ou la base n’a pas répondu. Un plafond qui ne peut pas se vérifier refuse, il n’ouvre pas. Réessayez dans un instant.

Le client JavaScript

Un fichier, aucune dépendance : sdk/kwoll.ts dans le dépôt. Il se copie dans votre projet plutôt qu’il ne s’installe : il n’y a rien à mettre à jour, et rien qui puisse casser sans que vous l’ayez lu.

import { Kwoll, ErreurKwoll } from "./kwoll";

const kwoll = new Kwoll({ cle : process.env.KWOLL_CLE! });

try {
  const v = await kwoll.verifier(message);
  if (v.niveau === "danger") alerter(v.regles, v.politique);
} catch (e) {
  // Un plafond atteint se réessaie demain ; une portée manquante, jamais.
  if (e instanceof ErreurKwoll && e.temporaire) reporter();
  else throw e;
}

Il ne réessaie rien tout seul. Un client qui rejoue un 429 transforme un plafond atteint en tempête, et vous ne voyez jamais que vous avez dépassé votre forfait : vous voyez une API lente.

La description complète est servie en OpenAPI 3.1, et décrit la version en cours de déploiement : /api/v1/openapi.json.