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, et le résultat de la session ne vous rend que ces attributs, même si le portefeuille en divulgue davantage. 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. Avec country_code, l'émetteur se résout par la liste de confiance de ce pays, via le registre officiel de l'UE, parmi ses fournisseurs qualifiés d'attestations, et pour les seuls vct que le registre SD-JWT VC classe qeaa, jamais un PID ; sans lui, par le registre SD-JWT VC de ce déploiement (GET /trust/registries), dont l'ancre doit couvrir le vct du credential. La réponse dit d'où vient l'ancre (trust). Les ancres sont portées par Todis : aucune clé ne se fournit dans la requête, et une requête qui porte encore issuer_public_key_pem est refusée.

Un PID ne se vérifie plus en donnant un pays. Jusqu'à la version 4.5.0, un PID SD-JWT VC pouvait se vérifier avec country_code. Depuis la version 5.0.0, un PID envoyé avec country_code échoue toujours, quel que soit le pays : la liste de confiance d'un pays ne vérifie que des attestations qualifiées (vct classé qeaa). Vérifiez un PID sans country_code, contre les registres de ce déploiement (GET /trust/registries).

Sur verify.todis.eu, aucun type n'est classé qeaa aujourd'hui : la voie country_code n'y vérifie donc aucun credential. Le classement d'un type qualifié est une donnée du registre, pas une version du service.

  • 200 : présentation vérifiée, claims et trust renseignés.
  • 400 : la requête porte issuer_public_key_pem, quelle qu'en soit la valeur ; l'erreur nomme le champ.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 422 : présentation malformée, signature non vérifiée, ou émetteur qu'aucune ancre n'accepte (verified: false, détail dans error). Un émetteur refusé est nommé : le détail porte le sujet, l'empreinte SHA-256, l'autorité émettrice et l'identifiant de clé de cette autorité du certificat présenté, de quoi le faire déclarer sans un aller-retour. Et si ce certificat chaîne vers une ancre que le registre porte déjà, mais qui n'est pas déclarée pour le type présenté, le détail le dit et nomme cette ancre et ses types : le remède est alors de déclarer un type, pas d'obtenir un certificat. Ces informations sont celles d'un certificat, publiques ; aucune revendication du porteur n'y figure. Aussi, avec country_code, un credential dont le vct n'est pas classé qeaa, que l'erreur nomme avec sa catégorie, ou toute présentation quand ce déploiement ne classe aucun type qeaa, que l'erreur dit : dans les deux cas, sans qu'aucune liste soit lue. Aussi un credential dont le typ n'est ni dc+sd-jwt ni vc+sd-jwt, et un credential qui porte aud, quelle qu'en soit la valeur : sans session, Todis n'est le destinataire d'aucun credential. Dans une session, aud est accepté quand il désigne la même audience que le KB-JWT de la requête (le client_id en redirection).
  • 429 : quota de requêtes du plan dépassé.
  • 502 : résolution du registre de confiance de l'émetteur impossible (mode country_code uniquement, pour un type classé qeaa, ex. registre européen temporairement indisponible, ou liste nationale close ou passée sa prochaine mise à jour, ou liste qui n'a pas pu être relue avec succès depuis 24 h, que l'erreur nomme).
  • 503 : le registre SD-JWT VC n'a aucune base chargée, avec ou sans country_code, qui ne juge que les types que ce registre classe.

POST /verify/mdoc

Vérifie une présentation mdoc (ISO/IEC 18013-5, notamment permis de conduire mobile), contre le registre mdoc de ce déploiement (GET /trust/registries), qui porte les IACA des émetteurs acceptés : la chaîne du signataire, embarquée dans la présentation, est validée jusqu'à l'une d'elles, signature et fenêtre de validité comprises, sur un seul niveau comme le prévoit ISO/IEC 18013-5, et l'ancre retenue doit couvrir le doctype signé. La réponse dit quelle ancre a validé l'émetteur (trust). Les ancres sont portées par Todis : aucun certificat ne se fournit dans la requête, et une requête qui porte encore issuer_trust_anchor_pem est refusée. Un émetteur mdoc qui manque au registre se signale à integration@todis.eu.

  • 200 : présentation vérifiée, doc_type, namespaces et trust renseignés.
  • 400 : la requête porte issuer_trust_anchor_pem, quelle qu'en soit la valeur ; l'erreur nomme le champ.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 422 : présentation malformée, signature non vérifiée, ou émetteur qu'aucune ancre du registre n'accepte, nommé comme sur POST /verify/sd-jwt-vc.
  • 429 : quota de requêtes du plan dépassé.
  • 503 : le registre mdoc n'a aucune base chargée.

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é.

Deux requêtes sur la page. Le bouton « Ouvrir mon portefeuille » porte la requête du même appareil : après sa réponse, le portefeuille renvoie le navigateur à la page avec un code frais (le Response Code, OpenID4VP 1.0 §14.2), et la page libère alors le résultat. Elle ne le libère que dans le navigateur qui l'a ouverte le premier, qu'elle reconnaît par un cookie todis_parcours ; un retour arrivé dans un autre navigateur fait échouer la session. Le QR code porte une requête cross-device à part, pour un portefeuille sur un autre appareil, sans retour, et servie trois fois au plus. Dans une iframe, le bouton ouvre la page dans un nouvel onglet. Deux conséquences pour vous : n'ouvrez pas hosted_url dans un navigateur avant d'y rediriger l'utilisateur, sinon le parcours serait lié à votre navigateur et non au sien ; et le webhook session.verified d'un retour sur le même appareil part quand la page libère le résultat, et non quand le portefeuille transmet sa réponse.

Champs purpose et cross_device. purpose (optionnel, texte non vide de 300 caractères au plus) est la finalité de la vérification, dans vos mots et dans la langue de la page : la page l'affiche avant que le visiteur ouvre son portefeuille, précédée de « Finalité indiquée par le site : » (OpenID4VP 1.0 §6.2). Sans lui, la page affiche la finalité du raccourci check (âge ou identité), sinon la liste des informations demandées. cross_device (booléen, true par défaut) à false sert une page du seul même appareil, sans QR code : HAIP 1.0 §5.1 le recommande quand vous comptez sur la liaison au navigateur de l'utilisateur contre l'hameçonnage.

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é. La page passe au navigateur, en un seul appel, une requête par protocole que la vérification permet, et le portefeuille répond par celle qu'il sait servir. La première est OpenID4VP 1.0 (openid4vp-v1-signed), signée par le même certificat et porteuse de la même requête que le QR code : le PID au format SD-JWT VC comme les attestations mdoc (ISO/IEC 18013-5) passent par elle. Avec check, le PID SD-JWT VC n'est demandé que si le registre SD-JWT VC de ce déploiement le couvre ; celui de verify.todis.eu ne le couvre pas, et le parcours demande donc les seules routes mdoc. La seconde, pour les attestations mdoc seulement, est l'Annexe C d'ISO/IEC 18013-7 (org-iso-mdoc). Avec check: "age_over_18", la requête OpenID4VP offre les mêmes sources d'âge que le QR code, l'attestation de preuve d'âge (eu.europa.ec.av.1) en premier ; la requête de l'Annexe C demande un seul document, sans repli : l'attribut age_over_18 de cette attestation, que le registre mdoc de verify.todis.eu couvre, comme le montre GET /trust/registries. Un portefeuille qui ne répond que par l'Annexe C et ne détient pas cette attestation ne peut pas répondre par le bouton ; le QR code reste sur la page. 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 de GET /verify/sessions/{id} et le webhook gagnent dc_api_protocol, le protocole par lequel le portefeuille a répondu. Hors navigateur, par exemple dans une application native, utilisez le QR code ou le lien profond. Un déploiement en mode HAIP verrouillé (option HAIP_VERROUILLE, fermée par défaut) verrouille ce que Todis émet : requête signée sous x509_hash et réponse chiffrée. Il ne restreint pas le protocole que le portefeuille retient : la page offre toujours l'Annexe C à côté d'OpenID4VP, pour qu'un portefeuille qui ne sait répondre que par elle puisse répondre. Pour n'offrir qu'OpenID4VP, sur la plateforme d'intégration seulement, créez la session avec l'en-tête todis-essai-openid4vp: seul.

Option revocation. Comme sur POST /verify/sessions, report par défaut ou require : la conduite du parcours face à un statut qui n'a pas pu être établi. Le résultat de la session et le webhook session.verified portent revocation, la conduite appliquée et ce que le contrôle a établi.

Option profil_av. Avec "profil_av": true, la page hébergée propose aussi, dans un onglet à part, l'application de vérification d'âge du profil européen : un QR code et un lien av:// qui lui passent la requête par valeur, non signée, à l'attestation de preuve d'âge (eu.europa.ec.av.1) seule, sans repli sur le PID. C'est la forme que ces applications exigent, et la seule qu'elles acceptent : elles refusent toute requête signée. Le premier onglet reste la page d'avant. Ce que vous perdez avec cette option : l'application ne peut pas authentifier Todis, faute de signature ; la réponse reste liée à cette requête et se vérifie comme les autres. Le résultat, GET /verify/sessions/{id} et le webhook sont inchangés. L'option exige qu'une route du parcours offre cette attestation : avec check: "age_over_18", c'est le cas quand le registre mdoc du déploiement la couvre, ce que fait celui de verify.todis.eu (voir GET /trust/registries). Sur un déploiement dont le registre ne la couvre pas, la création répond 400.

  • 201 : parcours créé, avec hosted_url, expires_at et le secret de webhook.
  • 400 : URL de retour invalide, check inconnu, champs de vérification incohérents, purpose vide ou de plus de 300 caractères, ou ancre de confiance, transaction_data ou requête DCQL mal formée fournis (mêmes règles que POST /verify/sessions).
  • 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 dont le vct est classé qeaa, liste officielle de l'UE ; un autre type échoue à la vérification, par un message qui le nomme) 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.

Un PID ne se vérifie plus en donnant un pays. Jusqu'à la version 4.5.0, un PID SD-JWT VC pouvait se vérifier avec country_code. Depuis la version 5.0.0, un PID envoyé avec country_code échoue toujours, quel que soit le pays : la liste de confiance d'un pays ne vérifie que des attestations qualifiées (vct classé qeaa). Vérifiez un PID sans country_code, contre les registres de ce déploiement (GET /trust/registries).

Sur verify.todis.eu, aucun type n'est classé qeaa aujourd'hui : la voie country_code n'y vérifie donc aucun credential. Le classement d'un type qualifié est une donnée du registre, pas une version du service.

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.

revocation (optionnel, report par défaut) fixe la conduite de la session face à une révocation qui n'a pas pu être contrôlée : avec report, la session est vérifiée et son résultat dit que le statut n'a pas été établi, et pourquoi ; avec require, elle échoue. Todis contrôle le statut de chaque credential qui en déclare un (liste de statut d'un SD-JWT VC, liste de statut ou d'identifiants d'un mdoc), avant de conclure la session et sur tous les transports. Un credential révoqué, suspendu ou dont l'index sort de sa liste fait échouer la session quelle que soit la conduite ; un credential qui ne prévoit aucun statut passe sous les deux. Le claim status d'un SD-JWT VC ne vous est jamais rendu. Ouvert dans tous les plans.

profil_av (optionnel, false par défaut) demande en plus le transport OpenID4VP du profil européen de vérification d'âge : la réponse porte alors profil_av, avec wallet_uri, un lien av:// qui ouvre l'application de vérification d'âge en lui passant la requête par valeur, et authorization_request, cette requête, non signée, sous redirect_uri:, en direct_post en clair, à l'attestation de preuve d'âge (eu.europa.ec.av.1) seule. C'est la seule forme que ces applications acceptent ; en échange, l'application ne peut pas authentifier Todis. La requête par redirection ne change pas. La session doit demander cette attestation en mdoc (400 sinon), et n'en demander que des éléments age_over_NN de son espace de noms : tout autre chemin est refusé à la création, par un 400 qui le nomme. Ce transport ne porte que cette attestation : une réponse postée sur son response_uri qui porte un autre credential est rejetée, alors que la requête par redirection et la DC API continuent d'accepter les autres sources d'âge de la session.

redirect_uri : même appareil ou QR code. Avec redirect_uri (optionnel, un URI absolu, 400 sinon), la session est un flux du même appareil : après sa réponse, le portefeuille renvoie le navigateur à redirect_uri, augmenté du paramètre response_code, un secret frais. Le résultat vérifié reste alors retenu : GET /verify/sessions/{id} rend {"status": "pending", "response_code_required": true} tant que vous n'avez pas présenté ce code à POST /verify/sessions/{id}/response-code (voir ci-dessous). Sans redirect_uri, la session est cross-device (QR code) : pas de redirection, et le résultat se lit directement. Ne passez donc pas redirect_uri pour un QR code, le résultat resterait retenu. encrypted_response: true n'exige pas redirect_uri. Cette adresse doit être joignable depuis l'appareil qui exécute le portefeuille, et non depuis votre serveur : une adresse de bouclage (http://localhost:...) y désigne le téléphone lui-même.

Requête DCQL. dcql_query est contrôlé à la création (400) : tableaux présents non vides (credentials, credential_sets, claims, claim_sets, options, values), identifiants de credential et de revendication faits de lettres, chiffres, _ et -, aucune revendication demandée deux fois (règle qui vaut aussi pour claims et mdoc_claims), values limité aux chaînes, entiers et booléens, chemin mdoc fait exactement de l'espace de noms puis de l'élément, claim_sets seulement avec claims. Un chemin de revendication suit le Claims Path Pointer d'OpenID4VP 1.0 §7 : un nom désigne une clé, null tous les éléments d'un tableau, un entier l'élément de ce rang ; une contrainte values est satisfaite dès qu'une valeur sélectionnée y figure. Avec "multiple": true sur un credential, chaque présentation reçue sous son identifiant est vérifiée, et credentials.<id> comme trust.<id> deviennent des tableaux, une entrée par présentation retenue ; sans lui, une seconde présentation sous l'identifiant, ou un tableau vide, fait échouer la session.

Autorités nommées au portefeuille. Pour chaque credential qu'un registre de ce déploiement jugera, Todis ajoute lui-même à la requête un trusted_authorities de type aki : l'identifiant de clé de chaque autorité (IACA) que le registre accepte pour son type. Un portefeuille qui applique ce filtre ne propose alors que les credentials qui en relèvent. Rien n'est ajouté pour un credential jugé par la liste européenne (country_code), ni quand une ancre candidate ne peut se désigner ainsi (ancre de rôle signataire). Un trusted_authorities que vous fournissez dans dcql_query est transmis tel quel ; une valeur aki qui n'est pas du base64url est refusée (400). C'est une aide au portefeuille : la vérification juge la confiance comme avant. Enfin, transaction_data est refusé (400) : Todis n'envoie pas de données de transaction au portefeuille et n'en vérifie pas les empreintes.

Intention de conservation. Sur une revendication d'un credential mso_mdoc de dcql_query, "intent_to_retain": true déclare que vous entendez conserver cet élément au-delà de la transaction (OpenID4VP 1.0 §B.2.4). Todis le transmet au portefeuille sur chaque transport : tel quel dans la requête OpenID4VP, et en IntentToRetain dans la requête de l'Annexe C d'ISO/IEC 18013-7 ; le portefeuille peut l'afficher à l'utilisateur. Absent, il vaut faux : Todis annonce alors au portefeuille que l'élément ne sera pas conservé, ce qui vous engage à ne pas le stocker plus longtemps que nécessaire pour mener la transaction (ISO/IEC 18013-5 §8.3.2.1.2.1). C'est ce qu'annoncent toujours les champs simplifiés et les raccourcis check. Le champ n'est jamais ignoré : dans mdoc_claims, sur une revendication SD-JWT VC ou avec une valeur qui n'est pas un booléen, il est refusé par une erreur qui le nomme.

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"]
  }'
  • 201 : session créée, avec session_id et authorization_request.
  • 400 : claims vide, aucune ancre valide ne couvre le type demandé, redirect_uri qui n'est pas un URI absolu, requête DCQL mal formée, transaction_data fourni, intent_to_retain hors d'une revendication mdoc de dcql_query, ou la requête porte issuer_public_key_pem, ou une entrée de credential_trust qui en porte un ; l'erreur nomme le champ, quel que soit le transport 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. Une session conclue par la Digital Credentials API porte dc_api_protocol, openid4vp-v1-signed ou org-iso-mdoc, le protocole par lequel le portefeuille a répondu. Une session du même appareil (créée avec redirect_uri) dont le résultat attend son code rend {"status": "pending", "response_code_required": true}. Une présentation liée à une autre requête (KB-JWT d'un autre nonce ou d'une autre audience, mdoc dont la signature de l'appareil ne vérifie pas sur la requête) rejette la réponse entière, même à côté d'un credential valide : l'erreur commence alors par the response is rejected (OpenID4VP 1.0 §14.1.2). Une session vérifiée porte aussi revocation : la conduite appliquée (policy) et ce que le contrôle a établi, status parmi not_provided (aucun statut prévu), valid (avec mechanism, list_issued_at et, quand la liste a été admise sous une déclaration de transition du registre, signer_exception) et not_checked (avec reason) ; valid et not_provided portent aussi certificates, la révocation des certificats de la chaîne de l'émetteur par la CRL de leur émetteur, not_provided ou checked ; Une session conclue porte aussi checks, la liste des contrôles effectués sur le credential présenté, dans l'ordre où ils ont abouti, chacun avec son issue (passed, not_checked ou failed) : issuer_signature, issuer_trust_anchor, credential_integrity, credential_validity, holder_binding, credential_type, requested_claims, credential_status, certificate_revocation. Un contrôle qui n'a pas eu lieu n'y figure pas, et celui dont le détail est ailleurs nomme le chemin où le lire sous reported_in : trust, revocation ou revocation.certificates. Une session échouée le porte aussi : les contrôles qui ont abouti avant celui qui a échoué, puis ce dernier avec failed ; un échec qui n'a fait juger aucune présentation, un refus du porteur par exemple, ne porte pas checks. Une session échouée porte aussi code, la cause dans un vocabulaire fermé à côté de error : wallet_declined, wallet_error, credential_missing, presentation_invalid, requirement_not_met, trust_resolution_unavailable, credential_revoked, revocation_not_checked ou unspecified.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 404 : session inconnue (identifiant invalide, session expirée après 15 minutes, ou session créée sous la licence d'un autre client).
  • 429 : quota de requêtes du plan dépassé.

POST /verify/sessions/{id}/response-code

Libère le résultat d'une session du même appareil, créée avec redirect_uri. Votre page de retour reçoit response_code en paramètre : présentez-le depuis votre serveur, pour la session que votre utilisateur a lancée, dont vous avez gardé l'identifiant de votre côté (dans sa session serveur, par exemple), jamais pour une session déduite de l'adresse de retour. C'est ce qui empêche qu'une requête relayée à une autre personne vous livre son identité (OpenID4VP 1.0 §14.2, HAIP 1.0 §5.1). Un portefeuille qui ne suit pas la redirection ne libère jamais le résultat : il reste retenu jusqu'à l'expiration de la session.

curl -X POST https://verify.todis.eu/verify/sessions/<session_id>/response-code \
  -H "Authorization: Bearer VOTRE_JETON" \
  -H "Content-Type: application/json" \
  -d '{"response_code": "<valeur reçue sur redirect_uri>"}'
  • 200 : même corps que GET /verify/sessions/{id}, avec le résultat libéré, que GET /verify/sessions/{id} rend ensuite sans code.
  • 400 : session sans retour du même appareil (créée sans redirect_uri), ou session d'une page hébergée, où c'est la page qui présente le code.
  • 401 : jeton de licence manquant, invalide, révoqué ou expiré.
  • 403 : ce code n'est pas celui de la session ; rien n'est libéré.
  • 404 : session inconnue, expirée, ou créée sous la licence d'un autre client.
  • 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, les sources officielles que ce déploiement doit interroger, avec la date et le résultat de leur dernière lecture (official_sources), et les pays qu'un country_code peut atteindre. trust_list_cache montre les listes de confiance que ce déploiement garde, vérifiées, pour country_code : la LOTL et chaque pays déjà résolu, avec leur numéro de séquence, l'empreinte du fichier lu, leur dernière lecture réussie, la date jusqu'à laquelle la copie est servie, et si elle l'est maintenant, sinon pourquoi. Une liste qui n'a pas pu être relue avec succès depuis 24 h n'est plus servie, jusqu'à la relecture suivante qui réussit. Une source que la Commission n'a pas encore publiée y dit ce qui manque pour la lire, et un compte nul y est une lecture, datée, pas une absence de lecture. Chaque ancre est un rôle d'un certificat, tel qu'une source le déclare : un même certificat figure une fois par source qui lui donne un rôle. Son usage vaut issuance quand elle valide des credentials, status quand elle signe leurs listes de statut, ce qui ne valide jamais un credential. type_categories donne la catégorie de chaque type que classe ce déploiement, couvert par une ancre ou non (pid, qeaa, pub_eaa, eaa), que la réponse d'une vérification reprend dans trust.category ; seuls les types qeaa du registre SD-JWT VC se vérifient par country_code. Une source lue d'un VICAL, la liste signée des autorités d'émetteurs mdoc, dit de quel VICAL elle vient. Une source lue d'une liste d'entités de confiance (LoTE, ETSI TS 119 602) porte list, avec la prochaine mise à jour de la liste : passée cette date, ses ancres ne sont plus acceptées. discarded liste les entrées de ces listes 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. La page Ancres de confiance présente ces ancres par pays, lues en direct sur la même route.

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.