Aller au contenu

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 :

  1. 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.
  2. 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.