Authentification et limites de débit

Toutes les routes de vérification exigent l'en-tête Authorization: Bearer <jeton>. Un jeton manquant, invalide, révoqué ou expiré renvoie 401. Au-delà de la limite de votre plan (30 requêtes/seconde en Starter, 60 en Standard), une requête renvoie 429.

Quand votre jeton arrive à moins de 7 jours de son expiration, chaque réponse authentifiée porte en plus l'en-tête X-License-Warning (ex. license expires in 3 day(s), renew soon) : un simple signal, la requête en cours aboutit normalement.

Qui décide quelles informations sont divulguées

Chaque attribut d'une présentation SD-JWT VC (l'âge, un diplôme, un statut) est protégé par son propre digest salé dans le JWT signé. Le portefeuille ne joint à sa présentation que la valeur et le sel des attributs que l'utilisateur a accepté de révéler : les autres restent des empreintes non réversibles. Todis n'a techniquement accès qu'à ce qui est effectivement divulgué dans la présentation qu'il reçoit, jamais plus.

Avec POST /verify/sessions, le champ claims de votre requête devient une requête DCQL intégrée à la demande d'autorisation envoyée au portefeuille : l'utilisateur voit et approuve exactement cette liste avant que son portefeuille ne construise la présentation. Avec POST /verify/sd-jwt-vc, cette étape a déjà eu lieu avant que vous n'obteniez la présentation : Todis la vérifie et vous renvoie son contenu tel quel.

POST /verify/sd-jwt-vc

Vérifie une présentation SD-JWT VC. Deux modes au choix dans le corps de la requête : issuer_public_key_pem (clé de l'émetteur fournie directement) ou country_code (résolution automatique via le registre officiel de l'UE) ; issuer_public_key_pem est prioritaire si les deux sont présents.

  • 200 : présentation vérifiée, claims renseigné.
  • 400 : ni issuer_public_key_pem ni country_code fourni.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 422 : présentation malformée ou signature non vérifiée (verified: false, détail dans error).
  • 429 : quota de requêtes du plan dépassé.
  • 502 : résolution du registre de confiance de l'émetteur impossible (mode country_code uniquement, ex. registre européen temporairement indisponible).

POST /verify/mdoc

Vérifie une présentation mdoc (ISO/IEC 18013-5, notamment permis de conduire mobile). L'ancre de confiance (issuer_trust_anchor_pem) doit être fournie explicitement dans le corps de la requête. Ce peut être l'IACA qui a émis le signataire du document, c'est le choix recommandé : la chaîne du signataire, embarquée dans la présentation, est validée jusqu'à elle, signature et fenêtre de validité comprises, ce qui vous dispense de suivre le renouvellement des signataires. Ce peut aussi être le certificat du signataire lui-même, si vous préférez l'épingler. Un seul niveau est suivi, comme le prévoit ISO/IEC 18013-5. Il n'y a pas de résolution automatique par pays pour ce format (contactez-nous si un émetteur mdoc particulier vous concerne).

  • 200 : présentation vérifiée, doc_type et namespaces renseignés.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 422 : présentation malformée, certificat de confiance incorrect, ou signature non vérifiée.
  • 429 : quota de requêtes du plan dépassé.

POST /hosted/sessions

Le parcours de vérification hébergé : ce service sert lui-même la page présentée à l'utilisateur (QR code, lien vers le portefeuille, textes en français, anglais ou espagnol). C'est l'intégration recommandée quand vous ne voulez pas construire d'écran de vérification, et elle est détaillée sur la page Intégrations. Vous redirigez l'utilisateur vers l'URL renvoyée, il revient sur votre success_url, et vous confirmez toujours le résultat côté serveur avec GET /verify/sessions/{id}. success_url signifie vérification réussie, jamais que l'utilisateur est majeur : une preuve d'âge qui vaut false y renvoie aussi, comme tout résultat vérifié. Lisez le résultat côté serveur (claims.age_over_18 avec le raccourci age_over_18) et appliquez-y votre propre règle.

C'est le seul endpoint qui accepte un webhook_url (POST JSON signé en HMAC-SHA256 à l'issue du parcours, même si l'utilisateur ferme la page) et les raccourcis check ("age_over_18", "identity"), disponibles sur tous les plans. Il exige un déploiement avec identité de signature des requêtes, ce qui est le cas du service hébergé.

Option dc_api. Avec "dc_api": true, la page hébergée propose au visiteur d'ouvrir son portefeuille par la Digital Credentials API du W3C, quand son navigateur la prend en charge ; le QR code reste affiché. Le protocole est l'Annexe C d'ISO/IEC 18013-7 (org-iso-mdoc) : seules les attestations mdoc (ISO/IEC 18013-5) passent par ce chemin, et le PID au format SD-JWT VC passe par le QR code. Avec check: "age_over_18", ce chemin demande l'attestation de preuve d'âge (eu.europa.ec.av.1), sans repli sur le PID. La réponse du portefeuille est chiffrée pour Todis et liée à l'origine https://verify.todis.eu : vous n'avez aucune origine à déclarer. Le résultat, GET /verify/sessions/{id} et le webhook sont inchangés. Hors navigateur, par exemple dans une application native, utilisez le QR code ou le lien profond.

  • 201 : parcours créé, avec hosted_url, expires_at et le secret de webhook.
  • 400 : URL de retour invalide, check inconnu, ou champs de vérification incohérents.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 403 : le plan du jeton n'inclut pas la grammaire DCQL complète (dcql_query fourni par vous).
  • 429 : quota de requêtes du plan dépassé.
  • 501 : déploiement sans identité de signature, flux hébergé indisponible.

POST /verify/sessions

Crée une session de vérification OpenID4VP. Le corps de la requête liste le type de credential demandé (vct) et les attributs demandés (claims, chemins simples, ex. "age_over_18"). La confiance dans l'émetteur vient de country_code (SD-JWT VC, liste officielle de l'UE) ou des registres d'ancres de ce déploiement, listés par GET /trust/registries. La réponse contient une requête d'autorisation OpenID4VP à présenter au portefeuille de l'utilisateur (QR code ou lien profond) ; la construction exacte de la chaîne du code QR ou du lien est détaillée dans notre fichier d'intégration. La session expire 15 minutes après sa création, qu'elle ait reçu une réponse ou non. Pour les besoins avancés (plusieurs credentials par session, alternatives entre credentials ou entre attributs, valeurs contraintes), le champ dcql_query accepte une requête DCQL complète à la place de vct/claims, et peut mélanger SD-JWT VC et mdoc. Le champ credential_trust donne alors à un credential SD-JWT VC son propre country_code, pour vérifier dans une même session des credentials d'émetteurs de pays différents (chaque entrée ne vaut que pour son credential, jamais pour un autre). Voir le détail dans la spécification OpenAPI.

encrypted_response vaut false par défaut sur cet endpoint, contrairement au flux hébergé : c'est donc ici que vous devez agir. Certains portefeuilles nationaux n'acceptent rien d'autre qu'une réponse chiffrée (response_mode=direct_post.jwt) et, sans elle, ne renvoient aucune erreur : ils lisent la requête puis abandonnent, et la session reste indéfiniment pending. Si vous visez un portefeuille de production, activez-le.

Sans chiffrement, redirect_uri est optionnel (il sert au flux same-device) ; le chiffrement le rend obligatoire (HAIP 1.0 §5.1). Cette adresse doit être joignable depuis l'appareil qui exécute le portefeuille, et non depuis votre serveur : en cross-device, le portefeuille la reçoit après avoir transmis sa réponse et tente de l'ouvrir sur le téléphone. Une adresse de bouclage (http://localhost:...) y désigne le téléphone lui-même. Le symptôme est déroutant : la vérification a réussi, le résultat vous parvient, et c'est le téléphone qui affiche une erreur.

curl -X POST https://verify.todis.eu/verify/sessions \
  -H "Authorization: Bearer VOTRE_JETON" \
  -H "Content-Type: application/json" \
  -d '{
    "vct": "https://example.eu/credentials/pid",
    "claims": ["age_over_18"],
    "country_code": "FR"
  }'
  • 201 : session créée, avec session_id et authorization_request.
  • 400 : claims vide, ou aucune ancre valide ne couvre le type demandé.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 403 : le plan du jeton n'inclut pas la grammaire DCQL complète (dcql_query, plan Premium).
  • 429 : quota de requêtes du plan dépassé.
  • 502 : résolution du registre de confiance impossible (mode country_code).
  • 503 : le registre d'ancres dont dépend la session n'a aucune base chargée.

GET /verify/sessions/{id}

Consulte le résultat d'une session créée par POST /verify/sessions, à interroger après que l'utilisateur a répondu avec son portefeuille (ou périodiquement en attendant).

curl https://verify.todis.eu/verify/sessions/<session_id> \
  -H "Authorization: Bearer VOTRE_JETON"
  • 200 : trois statuts possibles, {"status": "pending"}, {"status": "verified", "claims": {...}} ou {"status": "failed", "error": "..."}. Pour une session mdoc (doctype/mdoc_claims), claims imbrique les éléments sous leur espace de noms. Une session vérifiée porte aussi trust, d'où vient l'ancre qui l'a validée.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 404 : session inconnue (identifiant invalide, ou session expirée après 15 minutes).
  • 429 : quota de requêtes du plan dépassé.

GET /trust/registries

Liste, sans jeton, les ancres de confiance que ce déploiement accepte : pour chaque registre (mdoc, sd_jwt_vc), ses sources et ses ancres, avec les types de credential que chacune couvre et les dates de validité de son certificat, et les pays qu'un country_code peut atteindre. Une source lue d'un VICAL, la liste signée des autorités d'émetteurs mdoc, dit de quel VICAL elle vient, et discarded liste les entrées de ce VICAL qui ne sont pas devenues des ancres, avec leur raison : c'est là que se comprend l'absence d'une ancre attendue. Pour signaler une ancre manquante : report_missing_anchor.

curl https://verify.todis.eu/trust/registries
  • 200 : ancres des registres et pays de la liste officielle de l'UE, réponse cachable (Cache-Control, ETag).
  • 304 : inchangé depuis l'ETag présenté dans If-None-Match.

GET /usage

Consultez votre volume de requêtes directement avec votre jeton de licence, sans attendre un tableau de bord ni solliciter le support : utile pour vérifier votre consommation avant qu'un bloc Starter ne s'épuise, ou simplement suivre votre activité au fil du temps. Si vous avez racheté plusieurs blocs Starter, le total additionne automatiquement tous les jetons associés à votre adresse email, pas besoin de les cumuler vous-même.

curl https://verify.todis.eu/usage \
  -H "Authorization: Bearer VOTRE_JETON"
  • 200 : {"client_ids": [...], "total_authenticated_requests": 42}, ou total_authenticated_requests: null avec un champ error si les métriques sont temporairement indisponibles (le reste de la réponse reste fiable).
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 429 : quota de requêtes du plan dépassé.

GET /usage/history

L'historique quotidien de votre consommation, à partir du plan Standard : totaux par jour sur 30 jours. Le plan Premium y ajoute la ventilation par endpoint, les latences, une profondeur de 400 jours et l'export CSV (?days=30&format=csv). Aucune donnée brute de vérification n'est conservée, seulement des agrégats.

  • 200 : historique JSON, ou CSV si format=csv.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 403 : le plan du jeton n'inclut pas l'historique (Standard et au-delà).
  • 429 : quota de requêtes du plan dépassé.
  • 501 : déploiement sans base de données (auto-hébergement sans DATABASE_URL).

Format de réponse (vérification directe)

Pour /verify/sd-jwt-vc et /verify/mdoc : un champ verified (booléen) et, selon le résultat, soit les informations divulguées (claims pour SD-JWT VC, doc_type/namespaces pour mdoc), soit un champ error texte. Jamais l'intégralité du document d'identité, seulement les attributs effectivement divulgués (voir ci-dessus).

Aller plus loin

Pour la vue d'ensemble (authentification, deux façons d'intégrer, exemple curl), voir Comment fonctionne l'API. Pour le schéma exact des requêtes/réponses, todis.eu/docs (Swagger). Une question qui n'est pas couverte ici ? contact@todis.eu.