API REST (Tool API et Server-to-Server)
Le SDK navigateur communique avec la Tool API. Vous pouvez appeler la même API depuis votre propre backend — pour enregistrer des consentements recueillis dans votre propre interface, pour émettre des jetons d’approbation à usage unique, ou pour rechercher et vérifier des enregistrements.
URL de base : https://system.hashentry.com
| Méthode | Chemin | Objet | Authentification |
|---|---|---|---|
GET | /tool/v1/config | Configuration du bandeau, documents rattachés, région détectée | Clé API (read) |
GET | /tool/v1/documents/{slug}/active | Version active d’un document, contenu compris | Clé API (read) |
GET | /tool/v1/documents/active | Documents actifs rattachés au site | Clé API (read) |
POST | /tool/v1/consents | Enregistrer un consentement (route du SDK navigateur / mobile) | Clé API (write) + signature |
POST | /tool/v1/consents/batch | Enregistrer jusqu’à 100 consentements | Clé API (write) + signature |
POST | /tool/v1/s2s/consents | Enregistrer un consentement depuis votre backend | Clé API (write) + JWT + signature |
POST | /tool/v1/s2s/consents/batch | Enregistrer jusqu’à 100 consentements depuis votre backend | Clé API (write) + JWT + signature |
POST | /tool/v1/s2s/approval-tokens | Émettre un jeton d’approbation de document à usage unique | Clé API (write) + JWT + signature |
GET | /tool/v1/consents/status | Consentements actuels d’un visiteur | Clé API (read) |
GET | /tool/v1/consents/verify/{id} | Contrôle d’intégrité d’un enregistrement | Clé API (read) |
GET POST | /api/v1/public/consents/verify | Contrôle d’intégrité public (sans clé) | aucune |
Authentification
Section intitulée « Authentification »Chaque requête à la Tool API porte la clé du site :
X-API-Key: he_live_…Une clé possède une portée : read autorise les points de terminaison GET, write autorise en plus POST. Le site doit être vérifié dans le tableau de bord. Si le site possède une liste d’origines autorisées, les requêtes de navigateur provenant d’autres origines sont rejetées ; s’il possède une liste d’adresses IP autorisées, les requêtes provenant d’autres adresses sont rejetées.
Signature des requêtes (toutes les requêtes POST)
Section intitulée « Signature des requêtes (toutes les requêtes POST) »X-Signature: <HMAC-SHA256 en hexadécimal>X-Timestamp: <temps Unix en secondes>X-Nonce: <valeur unique par requête>body_hash = SHA-256(corps brut de la requête) (hex en minuscules)canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + body_hashsignature = HMAC-SHA256(key = clé API, message = canonical) (hex en minuscules)METHODest en majuscules.PATHest le chemin de la requête,/tool/v1compris, sans la chaîne de requête, par exemple/tool/v1/s2s/consents.- Le hash du corps est calculé sur les octets exacts que vous envoyez. Sérialisez le JSON une seule fois et envoyez cette même chaîne.
X-Timestampdoit se situer à ±300 secondes de l’heure du serveur.X-Noncecomporte 8 à 128 caractères parmiA–Z a–z 0–9 . _ : -(un UUID convient) et ne peut être utilisé qu’une seule fois par site ; une répétition est rejetée avecNONCE_REPLAYED.
Exemple détaillé (le vecteur de test commun au SDK et à l’API) :
API key : he_live_0123456789abcdef0123456789abcdefMethod : POSTPath : /tool/v1/consentsTimestamp : 1760000000Nonce : 3f2b8c1e-5a47-4d0e-9b6a-7c1d2e3f4a5bBody : {"consent_type":"cookie","decision":"accepted","language":"tr","idempotency_key":"9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d","metadata":{"note":"çerez ✓"}}
body_hash : 93316d8d6adb505a19668f4022e4e33b12aed9c0c4bb97d205b5064257090d34canonical : POST\n/tool/v1/consents\n1760000000\n3f2b8c1e-5a47-4d0e-9b6a-7c1d2e3f4a5b\n93316d8d6adb505a19668f4022e4e33b12aed9c0c4bb97d205b5064257090d34signature : e108683927badf35c36d5f467f63a9704c131d0102f14b06af7ed3046d44a8be(\n représente un unique caractère de saut de ligne ; le corps est en UTF-8.) Si votre implémentation produit cette signature pour ces entrées, elle est correcte.
const crypto = require('crypto');
function sign(apiKey, method, path, body) { const timestamp = Math.floor(Date.now() / 1000).toString(); const nonce = crypto.randomUUID(); const bodyHash = crypto.createHash('sha256').update(body, 'utf8').digest('hex'); const canonical = [method.toUpperCase(), path, timestamp, nonce, bodyHash].join('\n'); const signature = crypto.createHmac('sha256', apiKey).update(canonical, 'utf8').digest('hex'); return { 'X-Signature': signature, 'X-Timestamp': timestamp, 'X-Nonce': nonce };}
const body = JSON.stringify({ consent_type: 'document_approval', decision: 'accepted', /* … */ });const headers = { 'Content-Type': 'application/json', 'X-API-Key': apiKey, Authorization: `Bearer ${jwt}`, // routes S2S uniquement ...sign(apiKey, 'POST', '/tool/v1/s2s/consents', body),};La signature prouve que la requête a été effectuée avec la clé API et qu’elle n’a pas été rejouée. Comme le SDK navigateur utilise la même clé, qui est visible dans le code source de la page, la signature seule ne prouve pas qu’une requête provient de votre backend — c’est à cela que servent les routes S2S.
Routes serveur à serveur (/tool/v1/s2s/…)
Section intitulée « Routes serveur à serveur (/tool/v1/s2s/…) »Les requêtes S2S nécessitent trois éléments : une clé API write, la signature de la requête et un JWT d’un utilisateur ayant le rôle api_user :
- Invitez un membre de l’équipe avec le rôle
api_userdans le tableau de bord. Ce rôle ne peut utiliser que l’API S2S ; il ne peut pas se connecter au panneau. - Connectez-vous avec
POST /api/v1/auth/loginpour obtenir un JWT ; renouvelez-le avecPOST /api/v1/auth/refresh. - Envoyez
Authorization: Bearer <jwt>avecX-API-Keyet les en-têtes de signature. L’utilisateur doit appartenir au tenant propriétaire du site.
Seules les requêtes S2S peuvent définir ip_address, user_agent, otp_verified et otp_reference. Sur la route navigateur, l’adresse IP et le user agent sont toujours tirés de la connexion, et les champs OTP sont ignorés.
Enregistrer un consentement
Section intitulée « Enregistrer un consentement »POST /tool/v1/consents (ou /tool/v1/s2s/consents)
{ "consent_type": "cookie", "decision": "partial", "document_version_id": "0190a1b2-…", "language": "en", "categories": { "necessary": true, "analytics": true, "marketing": false }, "idempotency_key": "9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d", "visitor_id": "user-4711", "visitor_region": "DE", "compliance_framework": "gdpr", "metadata": { "order_id": "ORD-54321" }}| Champ | Règles |
|---|---|
consent_type | Requis. Jusqu’à 50 caractères (A–Z a–z 0–9 _ - .). Utilisés actuellement : cookie, document_approval, do_not_sell_opt_out. |
decision | Requis. accepted, rejected, partial ou withdrawn. |
document_version_id | UUID d’une version active d’un document rattaché au site. Sans lui — ou avec une version qui n’est pas active — la requête est rejetée avec 409 CONFIG_OUTDATED. Un consentement aux cookies référence le document cookie_policy du site. |
language | S’il est fourni, il doit correspondre à la langue de cette version. |
categories | Objet clé de catégorie → booléen. Seules les catégories définies dans la configuration du bandeau sont stockées. |
idempotency_key | 8 à 64 caractères (A–Z a–z 0–9 . _ : -). Voir ci-dessous. |
visitor_id | Votre référence pour la personne, jusqu’à 100 caractères. |
visitor_region, visitor_country | Jusqu’à 10 / 2 caractères. |
compliance_framework | kvkk, gdpr, uk_gdpr, ccpa, lgpd, popia, pipeda, pipl, pipa ou none. |
source | Jusqu’à 20 caractères, par exemple client, server. |
metadata, extra_fields | Objets comportant au plus 30 clés et environ 16 Ko chacun. |
approval_token | Jeton à usage unique pour un document_approval (voir ci-dessous). |
| Champs de signature / de document personnalisé | signature_image, signature_mime_type, signer_name, signer_email, signer_phone, signer_id_number, declaration_text, drawing_duration_ms, stroke_count, strokes_data, input_type, screen_resolution, custom_document_content, custom_document_mime_type — tous de premier niveau. Voir Approbation de documents et signature. |
ip_address, user_agent, otp_verified, otp_reference | S2S uniquement. |
Obtenez le document_version_id à partir de GET /tool/v1/config?language=en (data.documents[].version.id) ou de GET /tool/v1/documents/{slug}/active?language=en.
Réponse : 202 Accepted
Section intitulée « Réponse : 202 Accepted »{ "message": "…", "data": { "consent_token": "ct_…", "proof_hash": "…64 hex characters…", "created_at": "2026-10-10T10:00:00+00:00", "duplicate": false }}202 signifie que la requête a passé tous les contrôles et que l’enregistrement a été accepté pour écriture. Le jeton, le hash de preuve et l’horodatage sont définitifs à ce stade, mais la ligne est écrite par un worker en arrière-plan ; status et verify peuvent donc ne pas la trouver pendant un court instant.
idempotency_key
Section intitulée « idempotency_key »Envoyez une clé par action utilisateur et réutilisez-la à chaque nouvelle tentative de cette action. Une requête répétée avec la même clé ne crée pas de second enregistrement et ne consomme pas de quota ; elle renvoie la réponse d’origine avec "duplicate": true. Les clés sont mémorisées pendant 24 heures. Le SDK navigateur génère un UUID par décision et l’utilise pour la première tentative, pour les nouvelles tentatives côté client et pour les renvois depuis sa file d’attente hors ligne.
POST /tool/v1/consents/batch avec { "consents": [ { … }, … ] } (1 à 100 éléments, mêmes champs que ci-dessus).
{ "data": { "accepted": 2, "rejected": 1, "results": [ { "index": 0, "status": "accepted", "consent_token": "ct_…", "proof_hash": "…", "created_at": "…", "duplicate": false }, { "index": 1, "status": "accepted", "consent_token": "ct_…", "proof_hash": "…", "created_at": "…", "duplicate": true }, { "index": 2, "status": "rejected", "error": "…", "error_code": "CONSENT_LIMIT_EXCEEDED", "code": 429 } ] }}Le statut est 202 lorsque le lot a été traité ; vérifiez chaque entrée de results. Les erreurs de validation et un élément dont la version de document est inconnue ou inactive entraînent le rejet de tout le lot (422 / 409).
Jetons d’approbation à usage unique
Section intitulée « Jetons d’approbation à usage unique »POST /tool/v1/s2s/approval-tokens — S2S uniquement.
{ "document_slug": "service-contract", "subject": "user-4711", "signer_name": "Jane Doe", "expires_in": 600}| Champ | Règles |
|---|---|
document_slug / document_version_id | Au moins l’un des deux. Avec un identifiant de version, le jeton n’est valable que pour cette version. |
subject | Requis. Votre référence pour la personne (100 caractères max.) ; stockée comme visitor_id de l’enregistrement. |
signer_name, signer_email, signer_phone, signer_id_number | Facultatifs. Informations d’identité dont vous vous portez garant. |
otp_verified, otp_reference | Facultatifs. Déclarent une vérification OTP que vous avez effectuée. |
custom_document_hash | Facultatif. SHA-256 (hex) d’un document généré par votre système ; l’approbation doit alors porter exactement ce contenu. |
expires_in | Secondes, 30 à 3600. 600 par défaut. |
Réponse 201 :
{ "data": { "approval_token": "hat_…", "approval_token_id": "atk_…", "expires_in": 600, "expires_at": "2026-10-10T10:10:00+00:00", "document_id": "…", "document_slug": "service-contract", "document_version_id": null }}Remettez approval_token à la page et transmettez-le au SDK (approvalToken), ou envoyez-le en tant que champ de premier niveau approval_token d’un consentement document_approval. Le serveur le consomme une seule fois, tire l’identité du jeton et enregistre identity_assurance: tenant_attested avec l’identifiant non secret approval_token_id. Une requête rejetée pour une autre raison (limite de débit, quota) ne consomme pas le jeton. HashEntry ne stocke qu’un hash du jeton.
Lecture et vérification
Section intitulée « Lecture et vérification »GET /tool/v1/consents/status?visitor_id=…
Section intitulée « GET /tool/v1/consents/status?visitor_id=… »Renvoie jusqu’à 20 consentements non expirés d’un visitor_id, du plus récent au plus ancien. Filtres facultatifs : consent_type, document_slug, document_version_id.
GET /tool/v1/consents/verify/{id}
Section intitulée « GET /tool/v1/consents/verify/{id} »{id} est un UUID de consentement, un consent_token (ct_…) ou un proof_hash.
{ "valid": true, "data": { "consent_type": "cookie", "decision": "accepted", "proof_hash": "…", "hash_version": 2, "hash_valid": true, "merkle_valid": null, "is_anonymized": false, "is_tsa_stamped": false, "created_at": "2026-10-10T10:00:00+00:00" }}Ce que cette réponse signifie — et ce qu’elle ne signifie pas :
valid: truesignifie : l’enregistrement appartient à votre tenant, HashEntry a recalculé le hash de preuve à partir de l’enregistrement stocké avec la clé secrète de votre tenant et il correspond, et — si l’enregistrement fait partie d’un lot Merkle — sa preuve d’inclusion est correcte. L’existence d’une ligne ne suffit pas.merkle_validvautnulllorsque l’enregistrement n’a pas de preuve Merkle.is_tsa_stampedindique qu’un jeton d’horodatage existe pour l’enregistrement (directement ou par l’intermédiaire de son lot Merkle). Il n’indique pas que la signature du jeton a été vérifiée lors de cet appel.- Il s’agit d’un contrôle effectué par HashEntry sur les données de HashEntry. Pour vérifier sans vous fier à la réponse du serveur, exportez un paquet de preuves et vérifiez-le vous-même : Vérifier une preuve hors ligne.
- Un identifiant inconnu renvoie
404avec"valid": falseetcode: "CONSENT_NOT_FOUND".
GET|POST /api/v1/public/consents/verify
Section intitulée « GET|POST /api/v1/public/consents/verify »Pas de clé API ; débit limité. Paramètres : token (un jeton ct_… ou la valeur du cookie _he_consent du SDK), proof_hash ou id. Il renvoie la décision, les catégories, le domaine du site, les horodatages, l’état du hash et les informations d’horodatage, et jamais l’adresse IP, le user agent ou l’identifiant du visiteur.
- Un enregistrement qui existe :
200avecvalidtel que décrit ci-dessus. - Une valeur de cookie de navigateur qui ne peut être rapprochée d’aucun enregistrement serveur :
200avec"valid": false,"source": "client_token","server_record_found": falseet les champs décodés du cookie. Le cookie n’est pas signé ; il n’est donc jamais signalé comme valide à lui seul. - Rien de trouvé :
404avec"valid": false. Aucun identifiant :422.
Codes d’erreur
Section intitulée « Codes d’erreur »Les réponses d’erreur sont en JSON et portent un code machine stable dans code (également error_code et error.code ; certaines erreurs d’authentification n’utilisent que error.code).
| HTTP | Code | Signification |
|---|---|---|
| 401 | MISSING_API_KEY, INVALID_API_KEY | Clé API absente ou inconnue. |
| 401 | MISSING_SIGNATURE_HEADERS, TIMESTAMP_EXPIRED, INVALID_SIGNATURE, INVALID_NONCE, NONCE_REPLAYED | Problèmes de signature de la requête. |
| 401 | MISSING_BEARER_TOKEN, INVALID_BEARER_TOKEN | S2S : JWT manquant ou invalide. |
| 403 | SITE_NOT_VERIFIED | Vérifiez le site dans le tableau de bord. |
| 403 | ORIGIN_NOT_ALLOWED, IP_NOT_ALLOWED | Absent de la liste d’autorisation du site. |
| 403 | INSUFFICIENT_ROLE, TENANT_MISMATCH | S2S : l’utilisateur n’est pas un api_user de ce tenant. |
| 403 | — | La portée de la clé API n’autorise pas la méthode. |
| 404 | DOCUMENT_NOT_FOUND, VERSION_NOT_FOUND, CONSENT_NOT_FOUND | |
| 409 | CONFIG_OUTDATED | document_version_id manquant, non actif, de la mauvaise langue, ou le cache de configuration du serveur est à froid. Relisez /tool/v1/config ; ne renvoyez la requête que si la version est toujours la version active. |
| 422 | VALIDATION_ERROR | Les erreurs de champ se trouvent dans errors. |
| 422 | APPROVAL_TOKEN_REQUIRED, APPROVAL_TOKEN_INVALID, APPROVAL_TOKEN_CONFLICT | Voir jetons d’approbation à usage unique. |
| 422 | INVALID_CATEGORIES, METADATA_TOO_MANY_KEYS, METADATA_TOO_LARGE, EXTRA_FIELDS_TOO_MANY_KEYS, EXTRA_FIELD_NAME_TOO_LONG, EXTRA_FIELDS_TOO_LARGE, STROKES_DATA_TOO_LARGE, INVALID_SIGNATURE_IMAGE, SIGNATURE_IMAGE_TOO_LARGE, INVALID_CUSTOM_DOCUMENT, UNSUPPORTED_CUSTOM_DOCUMENT_TYPE, CUSTOM_DOCUMENT_TOO_LARGE | Limites de la charge utile. |
| 429 | RATE_LIMIT_EXCEEDED | Limite de débit propre à l’offre. Voir Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining. |
| 429 | CONSENT_LIMIT_EXCEEDED, NO_CONSENT_SUBSCRIPTION | Le quota de consentements de l’offre est épuisé / il n’y a pas d’offre active. |
| 429 | TOO_MANY_INVALID_API_KEYS | Trop de requêtes avec des clés invalides depuis une même adresse IP. |
| 503 | QUEUE_OVERLOADED, ADMISSION_UNAVAILABLE, API_KEY_LOOKUP_UNAVAILABLE | Temporaire. Réessayez après Retry-After avec le même idempotency_key. |
Réessayez les réponses 408, 425, 429 et 5xx avec le même corps et le même idempotency_key (et un nouvel horodatage, un nouveau nonce et une nouvelle signature). Ne réessayez pas les autres réponses 4xx sans modification.