Documentation
Démarrez avec l'API Anchorify
REST API
L'API Anchorify est une API REST JSON simple. Authentifiez-vous avec
une clé API via le header X-API-Key.
URL de base
https://api.anchorify.cloud Spécification OpenAPI
openapi.jsonDémarrage rapide
L'API Anchorify vous permet de créer des preuves infalsifiables en ancrant des hashes sur des blockchains publiques. Voici comment commencer :
1. Obtenez votre clé API
Créez un compte et générez une clé API depuis la console.
2. Hashez vos données localement
Générez un hash de votre fichier ou donnée. Les données originales n'ont jamais besoin de quitter votre système.
# Générer un hash SHA-256 (sortie hexadécimale)
openssl dgst -sha256 document.pdf
# SHA256(document.pdf)= e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
# Utilisez la chaîne hexadécimale directement dans l'API 3. Soumettez le hash
Envoyez le hash encodé en hexadécimal à notre API REST pour créer une notarisation. Vous recevrez un ID de notarisation pour le suivi.
curl -X POST https://api.anchorify.cloud/v1/notarizations \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"algorithm": "SHA256",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"name": "document.pdf"
}' import hashlib
import requests
with open("document.pdf", "rb") as f:
hash_hex = hashlib.sha256(f.read()).hexdigest()
resp = requests.post(
"https://api.anchorify.cloud/v1/notarizations",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"algorithm": "SHA256",
"hash": hash_hex,
"name": "document.pdf",
},
)
print(resp.json()["id"]) package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
f, _ := os.Open("document.pdf")
defer f.Close()
h := sha256.New()
io.Copy(h, f)
hashHex := hex.EncodeToString(h.Sum(nil))
body, _ := json.Marshal(map[string]string{
"algorithm": "SHA256",
"hash": hashHex,
"name": "document.pdf",
})
req, _ := http.NewRequest("POST",
"https://api.anchorify.cloud/v1/notarizations",
bytes.NewReader(body))
req.Header.Set("X-API-Key", "YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result["id"])
} {
"id": "019c0e1a-d891-7f1f-ab6e-5588c46c2a4b",
"algorithm": "SHA256",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"status": "pending",
"blockchain_proofs": [
{ "provider_name": "Algorand", "status": "pending", "created_at": "2026-01-30T08:52:46Z" },
{ "provider_name": "Base", "status": "pending", "created_at": "2026-01-30T08:52:46Z" },
{ "provider_name": "Polygon", "status": "pending", "created_at": "2026-01-30T08:52:46Z" }
],
"created_at": "2026-01-30T08:52:46Z",
"name": "document.pdf"
} 4. Interrogez le statut
Vérifiez le statut de la preuve. Une fois ancrée sur la blockchain, vous pouvez exporter votre document de preuve signé.
curl https://api.anchorify.cloud/v1/notarizations/019c0e1a-d891-7f1f-ab6e-5588c46c2a4b \
-H "X-API-Key: YOUR_API_KEY" resp = requests.get(
"https://api.anchorify.cloud/v1/notarizations/019c0e1a-d891-7f1f-ab6e-5588c46c2a4b",
headers={"X-API-Key": "YOUR_API_KEY"},
)
notarization = resp.json()
print(f"Statut : {notarization['status']}") req, _ := http.NewRequest("GET",
"https://api.anchorify.cloud/v1/notarizations/019c0e1a-d891-7f1f-ab6e-5588c46c2a4b",
nil)
req.Header.Set("X-API-Key", "YOUR_API_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var notarization map[string]any
json.NewDecoder(resp.Body).Decode(¬arization)
fmt.Printf("Status: %s\n", notarization["status"]) {
"id": "019c0e1a-d891-7f1f-ab6e-5588c46c2a4b",
"algorithm": "SHA256",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"status": "completed",
"timestamp_proof": {
"status": "timestamped",
"tsa_provider": { "name": "FreeTSA" },
"data": "MIIB...",
"validation": {
"chain": "MIIDXj...",
"ocsp": "MIIBpg...",
"this_update": "2026-01-30T08:52:49Z",
"captured_at": "2026-01-30T08:52:51Z"
},
"timestamped_at": "2026-01-30T08:52:48Z",
"created_at": "2026-01-30T08:52:47Z"
},
"blockchain_proofs": [
{
"provider_name": "Polygon",
"transaction_id": "0xabc123...",
"explorer_url": "https://polygonscan.com/tx/0xabc123...",
"status": "anchored",
"anchored_at": "2026-01-30T08:53:32Z",
"created_at": "2026-01-30T08:52:47Z"
}
],
"merkle_proof": {
"hash_algorithm": "SHA256",
"leaf_index": 2,
"siblings": [{ "position": "left", "hash": "9f8e7d6c..." }]
},
"merkle_root": "a1b2c3d4...",
"accumulator": {
"merkle_root": "a1b2c3d4...",
"sealed_at": "2026-01-30T08:52:47Z"
},
"created_at": "2026-01-30T08:52:46Z",
"updated_at": "2026-01-30T08:53:32Z",
"notarized_at": "2026-01-30T08:53:32Z",
"name": "document.pdf"
} Référence API
POST /v1/notarizations Créer une notarisation
En-têtes : X-API-Key (requis),
Idempotency-Key (optionnel, voir les
relances idempotentes)
Body : algorithm (requis, SHA256 | SHA384 | SHA512),
hash (requis, chaîne hexadécimale),
name (optionnel),
qualified_timestamp (optionnel)
GET /v1/notarizations/{id} Récupérer une notarisation par ID
Chemin : id (requis, UUID)
Quotas et limite de dépense
Chaque notarisation compte dans la période de facturation de votre organisation, qui suit la date anniversaire de votre abonnement. Chaque plan inclut un quota de notarisations standard ; Pro et Enterprise incluent aussi des horodatages qualifiés (eIDAS). Une notarisation qualifiée ne compte que dans le quota qualifié.
Au-delà du quota inclus, chaque notarisation est facturée au prix de dépassement de votre plan, et chaque notarisation qualifiée à l'unité (PAYG), dans la limite de dépense que les owners et admins de votre organisation fixent sur la page Facturation de la console : un montant HT par période de facturation, de 0 à 1 000 €, commun aux deux. La limite vaut 0 € par défaut : les notarisations s'arrêtent au quota inclus tant que vous n'en fixez pas.
Une notarisation que la limite de dépense ne couvre pas est refusée en 429 quota_exceeded, sans Retry-After : le quota revient avec la période de facturation suivante, ou dès que la limite est relevée.
Les owners et admins reçoivent un e-mail à 80 % et 100 % de chaque quota inclus, puis à 80 % et 100 % de la limite de dépense. Une notarisation qui échoue définitivement n'est pas facturée.
Les horodatages qualifiés dépendent aussi du stock que nous détenons auprès de l'autorité d'horodatage qualifiée. S'il est épuisé, une requête qualifiée est refusée en 429 qualified_capacity_exhausted, alors que les notarisations standard continuent : réessayez la requête qualifiée plus tard.
Relances idempotentes
Un délai dépassé ou une erreur réseau vous laisse dans le doute : la notarisation a-t-elle été créée ? Envoyez un en-tête Idempotency-Key avec POST /v1/notarizations, puis relancez avec la même clé et le même corps : la notarisation n'est créée, et facturée, qu'une fois.
curl -X POST https://api.anchorify.cloud/v1/notarizations \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f0c2b8e-6d1a-4c3e-9b7a-2e4f8d1c6a90" \
-d '{
"algorithm": "SHA256",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"name": "document.pdf"
}' Une relance dans les 24 h qui suivent la première requête ne crée rien et n'est pas comptée de nouveau : elle répond 201 avec la notarisation dans son état actuel, et l'en-tête Idempotent-Replayed: true.
HTTP/1.1 201 Created
Content-Type: application/json
Idempotent-Replayed: true Vous choisissez la clé : 255 caractères ASCII visibles au plus, sans espace ; un UUID convient. Les clés sont propres à votre organisation et conservées 24 h ; au-delà, la même clé crée une nouvelle notarisation. Utilisez une nouvelle clé pour chaque notarisation voulue.
La même clé avec un autre corps répond 422 idempotency_key_reused, et ne crée rien.
Erreurs
Une erreur renvoie un corps JSON avec un code lisible par une machine et un message lisible par un humain. Traitez le code : le message peut changer.
{
"error": {
"code": "quota_exceeded",
"message": "quota exceeded: the spending limit does not cover this notarization"
}
} | Statut | Code | Signification |
|---|---|---|
| 400 | invalid_argument | La requête est invalide : son JSON, son algorithme, son hash ou son en-tête Idempotency-Key. Corrigez-la avant de relancer. |
| 401 | unauthenticated | La clé API est absente ou invalide. |
| 402 | subscription_inactive | Votre organisation n'a pas d'abonnement qui accorde la notarisation : aucun, en attente, résilié, impayé, ou en retard de paiement depuis plus de 7 jours. Vos données restent consultables. |
| 403 | permission_denied | Votre plan n'inclut pas ce que la requête utilise : SHA-384, SHA-512 ou les horodatages qualifiés. |
| 404 | not_found | GET /v1/notarizations/{id} uniquement : la notarisation n'existe pas, ou appartient à une autre organisation. |
| 422 | idempotency_key_reused | L'Idempotency-Key a déjà servi dans les 24 h pour une autre requête. Rien n'a été créé : envoyez une nouvelle clé. |
| 429 | resource_exhausted | La limite de débit de votre plan est dépassée. Relancez après les secondes indiquées par Retry-After. |
| 429 | quota_exceeded | Votre quota inclus est consommé, et votre limite de dépense ne couvre pas la notarisation. Pas de Retry-After : relevez la limite, ou attendez la période de facturation suivante. |
| 429 | qualified_capacity_exhausted | Les horodatages qualifiés sont indisponibles pour l'instant. Les notarisations standard passent toujours ; réessayez la requête qualifiée plus tard. |
| 500 | internal | Erreur inattendue. Relancez avec la même Idempotency-Key : la notarisation n'est pas créée deux fois. |
Webhooks
ProLes webhooks permettent à votre application de recevoir des notifications HTTP POST en temps réel lorsque des événements de notarisation se produisent. Configurez les URL d'endpoint et abonnez-vous aux types d'événements depuis la console. Une URL d'endpoint doit être en HTTPS et désigner une adresse publique : le loopback, les réseaux privés et les services de métadonnées cloud sont refusés.
Types d'événements
Abonnez-vous aux événements pertinents pour votre workflow. Chaque livraison webhook inclut un snapshot complet de la notarisation avec l'état actuel des preuves.
| Type d'événement | Description |
|---|---|
notarization.created | Notarisation soumise et acceptée. |
notarization.sealed | Accumulateur scellé, preuve Merkle assignée. L'ancrage blockchain commence. |
notarization.timestamped | Horodatage RFC 3161 obtenu. Le champ event_context inclut les détails de timestamp_proof. |
notarization.anchor_submitted | Transaction blockchain soumise pour un fournisseur. Déclenché par fournisseur. |
notarization.anchored | Transaction blockchain confirmée on-chain pour un fournisseur. |
notarization.completed | Succès terminal : l'horodatage RFC 3161 et l'ancrage blockchain sont tous deux terminés. Le champ event_context inclut completed_at. |
notarization.failed | Échec terminal : l'horodatage ou l'ancrage blockchain n'a pas pu aboutir. Le champ event_context inclut failure_reason. |
Garanties des événements terminaux
notarization.completed et notarization.failed sont terminaux, mutuellement exclusifs et livrés exactement une fois par notarisation. Vous ne recevrez jamais les deux pour une même notarisation, dans un sens comme dans l'autre.
- completed n'est envoyé qu'une fois l'horodatage et l'ancrage blockchain terminés — jamais après le premier des deux seulement.
- Une notarisation ancrée par au moins un fournisseur blockchain aboutit à completed, même si d'autres fournisseurs du même profil d'ancrage ont échoué. Le tableau anchors[] détaille le résultat par fournisseur.
- Si tous les fournisseurs échouent définitivement, la notarisation est signalée failed : elle n'a aucune preuve blockchain.
- L'ordre de livraison n'est pas garanti. Les événements sont émis dans l'ordre du cycle de vie, mais les livraisons sont des requêtes HTTP indépendantes : ordonnez vos traitements sur le created_at de l'enveloppe, pas sur l'ordre d'arrivée.
Structure du payload
Chaque livraison webhook envoie une enveloppe JSON avec un snapshot complet de la notarisation. L'objet data.notarization est autonome — vous n'avez pas besoin d'appeler GET /v1/notarizations/{id}.
{
"id": "019c0e1a-e7a2-7f3b-8c1d-4a9b2e6f8d3c",
"type": "notarization.completed",
"api_version": "2026-09-01",
"created_at": "2026-01-30T08:53:32Z",
"organization_id": "019b7a3c-5d2e-7f1a-9b4c-3e8f1a2d5c7b",
"data": {
"notarization": {
"id": "019c0e1a-d891-7f1f-ab6e-5588c46c2a4b",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"algorithm": "SHA256",
"status": "completed",
"name": "document.pdf",
"created_at": "2026-01-30T08:52:46Z",
"updated_at": "2026-01-30T08:53:32Z",
"notarized_at": "2026-01-30T08:53:32Z",
"merkle_root": "a1b2c3d4e5f6...",
"timestamp_proof": {
"status": "timestamped",
"serial_number": "1A2B3C",
"tsa_provider": {
"name": "FreeTSA",
"cert_subject": "CN=FreeTSA,O=Free TSA,C=DE",
"cert_serial": "04B9A5",
"cert_ski": "A1B2C3D4E5F6...",
"policy_oid": "1.2.3.4.1"
},
"timestamped_at": "2026-01-30T08:52:48.123456Z",
"created_at": "2026-01-30T08:52:47.987654Z"
},
"blockchain_proofs": [
{
"provider_name": "Polygon",
"status": "anchored",
"transaction_id": "0xabc123...",
"explorer_url": "https://polygonscan.com/tx/0xabc123...",
"submitted_at": "2026-01-30T08:52:50Z",
"anchored_at": "2026-01-30T08:53:32Z",
"created_at": "2026-01-30T08:52:47.987654Z"
}
],
"accumulator": {
"merkle_root": "a1b2c3d4e5f6..."
}
},
"event_context": {
"completed_at": "2026-01-30T08:53:32Z"
}
}
} Note : Les champs timestamp_proof.data (le jeton ASN.1 RFC 3161, encodé en base64) et timestamp_proof.validation (le matériel permettant de vérifier ce jeton dans dix ans) ne sont inclus que si l'endpoint a activé l'option include_timestamp_token — ils partagent un seul drapeau car le matériel de validation décrit le jeton et n'a aucun sens sans lui. Le merkle_proof est toujours délivré.
En-têtes HTTP
Chaque requête webhook inclut les en-têtes suivants :
| En-tête | Valeur |
|---|---|
Content-Type | application/json |
User-Agent | Anchorify-Webhooks/1.0 |
X-Anchorify-Signature | t=<unix>,v1=<hex> (HMAC-SHA256) |
X-Anchorify-Event-Type | Type d'événement |
X-Anchorify-Event-ID | UUID de l'événement |
X-Anchorify-Delivery-ID | UUID de la tentative de livraison |
Vérification de la signature
Chaque webhook est signé avec HMAC-SHA256 en utilisant le secret de votre endpoint. Le format de l'en-tête de signature est t=<timestamp>,v1=<hex_signature>, calculé sur <timestamp>.<raw_body>. Nous recommandons de vérifier la signature et la fraîcheur du timestamp (tolérance de 5 minutes).
import hmac
import hashlib
import time
def verify_webhook(payload: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
timestamp = parts["t"]
signature = parts["v1"]
# Check timestamp freshness (5 min tolerance)
if abs(time.time() - int(timestamp)) > 300:
raise ValueError("Timestamp expired")
expected = hmac.new(
secret.encode(),
f"{timestamp}.".encode() + payload,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature) const crypto = require("crypto");
function verifyWebhook(payload, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=", 2))
);
// Check timestamp freshness (5 min tolerance)
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) {
throw new Error("Timestamp expired");
}
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${payload}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1)
);
} package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"math"
"strings"
"time"
)
func verifyWebhook(payload []byte, header, secret string) error {
parts := make(map[string]string)
for _, p := range strings.Split(header, ",") {
kv := strings.SplitN(p, "=", 2)
parts[kv[0]] = kv[1]
}
// Check timestamp freshness (5 min tolerance)
ts, _ := time.Parse("", parts["t"])
if math.Abs(float64(time.Now().Unix()-ts.Unix())) > 300 {
return fmt.Errorf("timestamp expired")
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(parts["t"] + "."))
mac.Write(payload)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(parts["v1"])) {
return fmt.Errorf("invalid signature")
}
return nil
} Politique de retry
Les livraisons échouées sont relancées avec un backoff exponentiel. Votre endpoint doit répondre avec un statut 2xx dans les 30 secondes.
- Jusqu'à 5 tentatives par livraison.
- Backoff exponentiel : min(30s × 2^tentative, 24h) + 0–5s de jitter.
-
2xx— 2xx — livraison réussie. -
429 / 5xx— 429 ou 5xx — relancé avec backoff. -
410— 410 Gone — endpoint immédiatement désactivé. -
4xx— Autres 4xx — échec définitif, pas de relance. -
3xx— Les redirections ne sont pas suivies — échec définitif, pas de relance. Enregistrez l'URL finale. - Les endpoints sont automatiquement désactivés après 10 échecs consécutifs.
Algorithmes de hachage supportés
Algorithmes supportés : SHA-256 (tous les plans), SHA-384 et SHA-512 (Pro et Enterprise). Le hash doit être fourni sous forme de chaîne encodée en hexadécimal.
API asynchrone
L'API est asynchrone. Soumettez votre hash et interrogez le statut, ou utilisez les webhooks (Pro) pour les notifications.
Documents de preuve signés
Les plans Pro et Enterprise peuvent exporter des documents de preuve signés contenant toutes les preuves cryptographiques. Pro
Horodatages qualifiés
Les plans Pro et Enterprise peuvent ajouter des horodatages qualifiés eIDAS pour une valeur juridique renforcée. Enterprise