L'API publique¶
Une clé, un en-tête, et trois points d'entrée. L'API est incluse à partir de l'offre Pro.
S'authentifier¶
curl https://api.veritext.fr/v1/api/usage \
-H "Authorization: Bearer vtx_live_votre_cle"
La clé va dans l'en-tête Authorization, jamais dans l'URL : les journaux de serveurs, de
mandataires et d'historiques de navigateur conservent l'URL complète. C'est ainsi que des jetons
finissent en clair dans des fichiers que personne ne surveille.
La clé n'est affichée qu'une fois
Nous n'en gardons qu'une empreinte. La réafficher est techniquement impossible — c'est exactement ce qu'on veut : une clé qu'on peut relire est une clé qu'une base volée livre. Si vous la perdez, révoquez-la et créez-en une autre.
Les portées¶
Une clé ne peut faire que ce que ses portées permettent. Une clé posée dans un script de vérification n'a aucune raison de pouvoir réécrire.
| Portée | Ce qu'elle ouvre |
|---|---|
provenance |
vérification d'origine |
detect |
vérification « précision » |
humanize |
réécriture |
usage |
lecture de votre consommation |
Les limites de débit¶
Chaque réponse porte trois en-têtes :
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119
X-RateLimit-Reset: 43
Au-delà, l'API répond 429 avec un en-tête Retry-After en secondes. Respectez-le : réessayer
immédiatement aggrave exactement ce que la limite cherche à éviter.
Le plafond dépend de l'offre : 120 requêtes par minute en Pro, 300 en Haute sensibilité.
Vérifier l'origine d'un texte¶
POST /v1/provenance/verify — publique, sans clé. Un enseignant ou un éditeur doit pouvoir
s'en servir sans avoir de compte chez nous.
Le texte ne quitte jamais votre machine. Vous le normalisez et le hachez localement, et vous n'envoyez que l'empreinte. L'API n'accepte d'ailleurs rien d'autre : 64 caractères hexadécimaux minuscules, et rien de plus.
La normalisation, exactement¶
Deux étapes, dans cet ordre, avant de calculer le SHA-256 :
- NFC — « é » écrit en un point de code et « é » écrit en deux donnent le même texte à l'œil et deux empreintes différentes sans cette étape. C'est le piège classique entre un texte tapé sur macOS et le même collé depuis Windows.
- Espaces réduits — toute suite d'espaces, tabulations et sauts de ligne devient une espace simple, et les bords sont rognés. Un copier-coller qui remet le texte en forme ne doit pas casser la vérification.
Ce qui n'est pas fait, volontairement : ni passage en minuscules, ni suppression de ponctuation. Changer la casse ou la ponctuation change le texte ; les ignorer ferait correspondre deux textes réellement différents, et l'empreinte ne prouverait plus rien.
import hashlib, unicodedata
def empreinte(texte: str) -> str:
normalise = " ".join(unicodedata.normalize("NFC", texte).split())
return hashlib.sha256(normalise.encode("utf-8")).hexdigest()
curl -X POST https://api.veritext.fr/v1/provenance/verify \
-H "Content-Type: application/json" \
-d '{"empreinte": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"}'
{
"connu": false,
"explication": "Cette empreinte ne figure pas dans notre registre. Cela signifie que ce texte n'est pas sorti tel quel de Veritext — pas qu'il a été écrit par une personne."
}
Ce qu'un « non » ne prouve pas
Une empreinte absente du registre signifie seulement que ce texte n'est pas sorti de Veritext. Elle ne dit rien de qui l'a écrit. Nous préférons l'écrire ici que de vous laisser le déduire de travers.
Pourquoi nous acceptons de répondre¶
Ce point d'entrée est un « oracle d'appartenance » : il dit oui ou non pour une empreinte donnée. C'est voulu et documenté. Il ne révèle rien d'un texte que l'on ne possède pas déjà, puisqu'il faut le posséder pour calculer son empreinte.
Il est limité en débit par empreinte, pour qu'il ne devienne pas un moyen commode d'en tester en masse.
Réécrire un texte, sans que nous puissions le lire¶
POST /v1/humanize. Le texte voyage dans une enveloppe chiffrée pour le worker : ni notre
proxy, ni notre API, ni notre base ne peuvent l'ouvrir. TLS ne suffirait pas — il est terminé bien
avant.
La séquence¶
1. GET /v1/processing/key → une clé publique éphémère, 10 minutes, SIGNÉE
2. vous vérifiez la signature avec l'empreinte publiée sur veritext.fr/securite
3. vous chiffrez le texte pour cette clé (HPKE, RFC 9180)
4. POST /v1/humanize {enveloppe} → 202 { "tache": "…", "etat": "en_cours" }
5. GET /v1/humanize/{tache} → 202 tant que ça travaille, puis 200 avec le résultat
6. DELETE /v1/humanize/{tache} → 204 quand vous avez bien reçu (facultatif)
L'étape 2 n'est pas facultative. Sans elle, un intermédiaire qui répondrait à votre place avec sa propre clé lirait tout. C'est l'attaque que la signature existe pour fermer, et le SDK la vérifie pour vous.
Pourquoi deux échanges au lieu d'un¶
Parce qu'une réécriture longue dépasse la minute, et que l'intermédiaire qui nous protège coupe à 100 secondes. En un seul échange, vous recevriez une erreur de passerelle après que le calcul a été fait : travail consommé, réponse perdue, et vous facturé pour un échec. C'est la seule combinaison qu'aucun remboursement ne rattrape proprement, donc nous l'avons supprimée plutôt que réduite.
Conséquence pratique : aucun de nos appels ne dure plus que le temps d'écrire une ligne.
Le sondage, en trois règles¶
| Quand redemander | l'en-tête Retry-After le dit, et sondage_apres_s aussi. Inutile de tourner plus vite : le résultat n'arrivera pas plus tôt |
| Vous pouvez relire | tant que vous n'avez pas acquitté, le GET rend le même résultat. Un transfert coupé — réseau, onglet fermé, client qui plante à la lecture — ne vous coûte donc rien : vous redemandez |
| 30 minutes, puis c'est fini | au-delà, le résultat est effacé et le GET rend 410. Nous ne gardons pas un résultat que personne ne vient chercher |
DELETE est facultatif mais recommandé : il dit « j'ai bien reçu », le résultat part tout de suite,
et un GET ultérieur rend 410 — sans ambiguïté avec un identifiant erroné, qui rend 404.
Trois refus possibles, et ce qu'ils veulent dire¶
| Code | Sens |
|---|---|
402 |
votre quota de mots est épuisé pour la période. Rien ne sert de commencer un calcul qu'on ne pourrait pas facturer |
429 |
vous avez déjà huit réécritures en cours sur cette clé. Récupérez leurs résultats avant d'en soumettre d'autres |
404 sur le sondage |
cette tâche n'est pas la vôtre, ou n'existe pas. Nous ne distinguons pas les deux : répondre autre chose confirmerait l'existence d'une tâche à quelqu'un qui n'y a pas droit |
Le 404 vaut aussi entre deux clés du même compte : une tâche appartient à la clé qui l'a
soumise. Une clé qui fuite n'ouvre donc pas les tâches des autres.
Ce qui remonte¶
Des fragments chiffrés, un nombre de mots, une durée, et un manifeste signé. Jamais de texte.
La clé qui déchiffre ces fragments ne transite pas : vous la dérivez de votre propre contexte HPKE (secret exporté, RFC 9180 §5.3), le worker dérive la même du sien. Notre interface de programmation, qui n'a ni l'un ni l'autre, relaie sans pouvoir lire.
Chaque fragment porte son numéro de séquence et le dernier est marqué : retirer, réordonner ou tronquer le flux est détecté. Le SDK recompose et vérifie pour vous, et ne rend rien si quoi que ce soit cloche.
Le comptage de mots a lieu dans le worker parce que nos serveurs, eux, ne voient qu'un blob chiffré : c'est ainsi que nous facturons sans lire.
{
"tache": "…", "etat": "pret",
"mots_entree": 316, "mots_sortie": 312, "duree_ms": 84,
"fidelite": { "nombres_preserves": true, "glossaire_preserve": true },
"manifeste": { "empreinte": "…", "modele": "…", "signature": "…" }
}
Et si le travail a échoué, le sondage réussit quand même — c'est la tâche qui a échoué, pas votre requête :
{ "tache": "…", "etat": "echec", "cause": "cle_expiree" }
cle_expiree signifie que votre clé de traitement de dix minutes a tourné entre-temps :
redemandez-en une et rejouez. Un échec n'est jamais facturé — il n'y a pas de mots en sortie, et
vous faire payer une panne qui n'est pas la vôtre n'aurait aucun sens.
fidelite est vérifié, pas promis : un modèle de langue invente des chiffres, et c'est le
genre d'erreur qu'un lecteur ne repère pas. Nous comparons les nombres avant et après.
La sortie est inscrite à notre registre de provenance¶
Chaque réécriture entre au registre (son empreinte seule). Quiconque pourra donc établir qu'un texte est sorti de Veritext — voir plus haut.
C'est délibéré, et c'est l'inverse d'une promesse d'indétectabilité : nous n'en faisons aucune, et l'AI Act (art. 50 §2) impose depuis le 2 août 2026 de marquer les réécritures substantielles.
Ce qui tourne aujourd'hui
Le modèle de langue n'est pas encore branché : la réécriture applique pour l'instant des
corrections typographiques réelles mais minimales, et le champ modele le dit franchement
(doublure-typographique-1). Toute la chaîne chiffrée, elle, fonctionne.
Le worker, lui, a son conteneur isolé depuis le 12 septembre 2026 : lecture seule, mémoire
verrouillée, sans accès à Internet ni à notre base, vidages interdits. GET /v1/sante publie
worker.isole: true et l'empreinte de sa clé de signature — la même que sur
veritext.fr/securite, où vous devez la vérifier.
Vérification précision¶
POST /v1/detect répond 503 : elle exige le classifieur, dont l'entraînement attend une
fenêtre GPU.
503 et non 404 ni 501 : la fonction est prévue, pas absente ni abandonnée.
La vérification locale, elle, fonctionne dès aujourd'hui — dans votre navigateur, sans API et sans que le texte nous parvienne.
Le SDK Python¶
pip install veritext
from veritext import Client, empreinte
c = Client(cle="vtx_live_…")
# Le texte ne quitte pas votre machine : il est normalisé et haché ici.
print(c.verifier_provenance("Le texte à vérifier, collé tel quel."))
# Ou si vous avez déjà l'empreinte :
print(c.verifier_empreinte(empreinte("Le texte à vérifier, collé tel quel.")))
Le SDK n'a aucune dépendance : urllib suffit, et un SDK qui impose sa pile HTTP se fait
détester.
Il lève des erreurs typées — NonAutorise, LimiteAtteinte (avec reessayer_dans),
Indisponible — pour que vous distinguiez un quota d'une panne sans lire un code numérique.
Le SDK JavaScript¶
import { Client, empreinte } from "@veritext/sdk";
const c = new Client();
// Le texte ne quitte pas votre machine : il est normalisé et haché ici.
console.log(await c.verifierProvenance("Le texte à vérifier, collé tel quel."));
Même surface, mêmes erreurs, aucune dépendance non plus : fetch et WebCrypto suffisent, dans
Node 18+ comme dans le navigateur. Une seule différence d'ergonomie, et elle vient de la
plateforme : empreinte() y est asynchrone, parce que WebCrypto l'est.
Les deux SDK calculent la même empreinte — et c'est vérifié¶
C'est le point qui compte, parce qu'un écart ne se voit pas : votre texte serait le bon, votre empreinte non, et la vérification répondrait « inconnu » sans rien expliquer.
Le piège est réel. L'écriture naturelle en JavaScript, texte.split(/\s+/), est fausse dans les
deux sens — \s et la notion d'espace de Python ne coïncident pas :
| Caractère | Python | JavaScript \s |
|---|---|---|
U+001C–U+001F (séparateurs de fichier, groupe, article, unité) |
espace | non |
U+0085 (NEL) |
espace | non |
U+FEFF (marque d'ordre des octets) |
non | espace |
Ce ne sont pas des cas d'école : un texte collé depuis un traitement de texte porte couramment un
U+FEFF en tête, et un export de base de données contient des U+001F.
Nous figeons donc 74 vecteurs — un par point de code que l'un ou l'autre langage pourrait tenir pour un espace, plus les cas NFC, les emoji et du français ordinaire — produits par le SDK Python et rejoués par le SDK JavaScript à chaque construction. Si l'un des deux dérive, la construction s'arrête.
Le contrat OpenAPI¶
https://api.veritext.fr/v1/openapi.json
Il est vérifié à chaque construction : les routes existent, elles portent une description, et aucun schéma n'expose de champ de texte en clair. Ce dernier point est un test, pas une intention.