Référence de l’API
API v2 de Sellat
Tout ce que fait l’application web, depuis votre propre système : enregistrer l’empreinte SHA-256 d’un fichier, l’ancrer sur Polygon et Bitcoin, demander l’horodatage qualifié (eIDAS, RFC 3161), télécharger le certificat et le dossier de preuves, et conserver l’original. REST et JSON, avec une clé d’API.
https://sellat.app/api/v2 Introduction
L’API travaille avec l’empreinte du fichier, pas avec le fichier : vous calculez le SHA-256 chez vous et n’envoyez que ces 64 caractères. L’original ne quitte pas votre machine, sauf si vous choisissez de le confier en conservation.
Chaque preuve entre dans un lot Merkle ancré sur Polygon, et le même lot est attesté sur Bitcoin. Avec le compte gratuit, les lots se clôturent toutes les 4 heures, à heures fixes (00:00, 04:00, 08:00, 12:00, 16:00 et 20:00 UTC) : chaque lot réunit les preuves de ces heures et il est ancré en une seule transaction. Si vous le demandez, l’empreinte reçoit aussi l’horodatage qualifié d’un prestataire qualifié de la liste de confiance de l’UE, au moment de la demande.
Toutes les routes partent de l’URL de base et parlent JSON. Les preuves créées par l’API et sur le web appartiennent au même compte et apparaissent dans le même tableau de bord.
Elle est faite pour être appelée depuis un serveur : elle n’envoie pas d’en-têtes CORS, donc un navigateur ne peut pas l’appeler depuis un autre site, et votre clé ne doit jamais se trouver dans du code qui arrive au navigateur.
Authentification
Chaque requête porte votre clé dans l’en-tête Authorization: Bearer sellat_.... Les clés se créent et se révoquent depuis votre tableau de bord ; un compte peut avoir jusqu’à 5 clés actives, une par intégration.
Sans clé, ou avec une clé révoquée, l’API répond 401 unauthorized avec l’en-tête WWW-Authenticate: Bearer. La seule route publique est la preuve portable (/proof/{id}.json).
curl https://sellat.app/api/v2/account \
-H "Authorization: Bearer sellat_3f9c..." Démarrage rapide
Calculez l’empreinte, créez la preuve avec l’horodatage qualifié et gardez l’id renvoyé. Quand son lot est clos et ancré, elle passe à anchored et toutes ses URL fonctionnent ; le champ anchoring de la réponse indique quand le lot se clôt (avec le compte gratuit, à la prochaine heure fixe UTC, au plus 4 heures après). L’horodatage qualifié n’attend pas le lot : il porte l’heure de son émission.
Sans "qualified": true, la preuve est tout aussi vérifiable (Polygon et Bitcoin) et ne consomme pas d’horodatage. Vous pouvez le demander plus tard avec POST /proofs/{id}/qualified.
# Your key, from the dashboard
export SELLAT_API_TOKEN=sellat_...
# The fingerprint: the file stays on your machine
HASH=$(sha256sum contract.pdf | cut -d' ' -f1)
# The proof, with the qualified seal
curl https://sellat.app/api/v2/proofs \
-H "Authorization: Bearer $SELLAT_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"hash\":\"$HASH\",\"name\":\"contract.pdf\",\"qualified\":true}" États d’une preuve
Le champ state dit ce que Sellat sait à chaque instant : il ne présente jamais comme confirmée une transaction qu’il n’a pas encore vérifiée.
L’attestation Bitcoin (bitcoin) suit son propre chemin : pending jusqu’à ce qu’OpenTimestamps l’inclue dans un bloc, puis confirmed avec sa hauteur. L’horodatage qualifié (qualified) ne dépend pas de l’état : il est émis tout de suite.
| État | Signification |
|---|---|
queued | Reçue. Attend la clôture de son lot : avec le compte gratuit, à la prochaine heure fixe UTC, toutes les 4 heures (anchoring.batch_closes_after). |
batched | Dans un lot Merkle, chemin fixé. Attend l’ancrage sur Polygon. |
anchored | Ancrée : la transaction est dans un bloc Polygon. proof.json est disponible. |
confirmed | Le bloc a 12 confirmations. |
Erreurs
Une erreur est toujours un JSON avec error.code (stable, pour votre code) et error.message (pour les personnes). Certaines ajoutent des champs, comme seals_remaining et buy_url sur un 402. La preuve portable, publique, répond {"error": "..."}.
Si la passerelle coupe une requête avant l’API (trop de requêtes depuis une IP, ou un corps de plus de 25 Mo), ce 429 ou ce 413 n’a pas ce format.
| Code | HTTP | Ce qui s’est passé |
|---|---|---|
invalid_request | 400 | Un champ manque ou n’a pas le format attendu. Le message dit lequel. |
unauthorized | 401 | Pas de clé, clé mal formée ou révoquée. |
payment_required | 402 | Plus d’horodatages qualifiés. Contient seals_remaining et buy_url. |
key_without_account | 403 | La clé n’est rattachée à aucun compte (anciennes clés). |
not_found | 404 | La preuve n’existe pas ou appartient à un autre compte. |
conflict | 409 | Un autre compte a enregistré ces octets en premier, ou l’original n’est pas encore stocké. |
hash_mismatch | 409 | Le fichier envoyé n’a pas l’empreinte de la preuve. |
file_too_large | 413 | Le fichier dépasse la limite par fichier de votre offre (max_file_bytes). |
rate_limited | 429 | Trop de requêtes par minute. Respectez Retry-After. |
quota_exceeded | 429 | Limite quotidienne de preuves atteinte. Retry-After jusqu’à 00:00 UTC. |
download_quota_exceeded | 429 | Limite quotidienne de téléchargements de l’original atteinte. |
integrity_failed | 500 | L’original stocké ne correspond plus à son empreinte ; il n’est pas servi. |
unavailable | 502 · 503 | Un service interne ne répond pas. Réessayez plus tard. |
seal_unavailable | 503 | L’autorité d’horodatage n’est pas disponible pour le moment. |
custody_unavailable | 503 | La conservation n’est pas disponible pour le moment. |
custody_quota_exceeded | 507 | Plus d’espace de conservation sur votre compte. |
{
"error": {
"code": "payment_required",
"message": "No qualified seals left on this account.",
"seals_remaining": 0,
"buy_url": "https://sellat.app/precios"
}
} Limites
Chaque compte peut créer un nombre de preuves par API et par jour, toutes clés confondues. Les preuves faites sur le web ne comptent pas, ni le renvoi d’octets qui vous appartiennent déjà. La journée repart à 00:00 UTC.
À la limite, POST /proofs répond 429 quota_exceeded avec un objet quota et Retry-After. Chaque route a aussi un frein par minute (par exemple 60 créations et 10 horodatages par minute) qui répond 429 rate_limited, et la passerelle accepte jusqu’à 120 requêtes par minute et par IP. Un corps de requête fait 25 Mo au plus.
Les horodatages qualifiés n’ont pas de limite quotidienne : chacun consomme un horodatage de votre solde. Consultez ce qu’il vous reste à tout moment avec GET /account, et pour plus de volume, écrivez à hola@sellat.app.
| Compte gratuit | Avec la Conservation | |
|---|---|---|
| Preuves par API et par jour | 1 000 | 1 000 |
| Ancrage sur Polygon et Bitcoin | Toutes les 4 heures, à heures fixes UTC | Toutes les 4 heures, à heures fixes UTC |
| Clés actives | 5 | 5 |
| Original en conservation, par fichier | 10 MB | 50 MB |
| Conservation au total | 1 GB | 20 GB |
| Téléchargements de l’original par jour | 10 | 50 |
HTTP/1.1 429 Too Many Requests
Retry-After: 41231
{
"error": {
"code": "quota_exceeded",
"message": "Daily limit reached: 1000 proofs per account per UTC day across all its keys. Write to hola@sellat.app for more.",
"quota": {
"limit": 1000,
"used": 1000,
"resets_in_seconds": 41231
}
}
} Payer à l’appel (x402, agents d’IA)
Un agent d’IA doté d’un portefeuille peut utiliser l’API sans compte ni clé : il paie chaque appel en USDC sur le réseau Base avec le protocole x402 (v2, schéma exact). Payer vaut inscription : le portefeuille qui paie est le compte.
Appelez sans clé et sans paiement : la réponse est 402 avec l’en-tête PAYMENT-REQUIRED (JSON en base64) qui indique le montant exact, le jeton, le réseau et l’adresse d’encaissement. Signez une autorisation EIP-3009 transferWithAuthorization pour ce montant et répétez la requête avec l’en-tête PAYMENT-SIGNATURE. Le paiement est vérifié avant le travail et réglé après : si l’appel échoue, rien n’est facturé. La réponse porte PAYMENT-RESPONSE avec la transaction, et un objet payment dans le corps.
La première preuve renvoie une seule fois api_key, une clé en lecture seule : elle consulte les preuves du portefeuille et télécharge le certificat et le paquet de preuves. Elle ne crée pas de preuves, car rien n’est gratuit sur ce canal. Les mêmes octets envoyés à nouveau par le même portefeuille ne sont pas facturés. La liste des prix est sur GET /x402.
Payer vaut acceptation des conditions du service : chaque offre 402 les lie et indique leur version (dans la phrase error de PAYMENT-REQUIRED et dans l’objet terms du corps), et Sellat conserve cette version avec le paiement.
| Appel | Prix | Ce que vous obtenez |
|---|---|---|
POST /proofs | 0,01 USDC | Une preuve dans le lot rapide (minutes), avec certificat, proof.json et ZIP de preuves |
POST /proofs/{id}/qualified | 0,90 USDC | L’horodatage qualifié eIDAS sur une preuve de votre portefeuille, en quelques secondes |
POST /wallet/key | 0,01 USDC | Une nouvelle clé en lecture seule pour le compte du portefeuille |
Fixez le maximum par paiement à 1 USDC. Beaucoup de clients x402 plafonnent le montant de chaque paiement : @x402/fetch et @x402/axios (v2) à 1 USDC par défaut, d’autres à la valeur que vous leur donnez. L’horodatage qualifié coûte 0,90 USDC : avec un maximum plus bas, votre client refuse le paiement avant de le signer. Les paquets de la version 1 (x402-fetch, x402-axios, 0,10 USDC par défaut) ne conviennent pas : Sellat parle x402 v2.
Distinguer les échecs. Un 402 avec payment_required est envoyé par Sellat quand la requête arrive sans PAYMENT-SIGNATURE : c’est l’offre, pas une erreur. payment_invalid signifie qu’une signature est arrivée mais n’est pas valable (montant, réseau, solde ou autorisation déjà utilisée ; le message précise laquelle), et payment_failed que le travail a été fait mais que le paiement n’a pas été réglé. Si votre client refuse le montant à cause de son plafond, la requête payée n’atteint jamais Sellat : aucune réponse de notre part, seulement une exception dans votre client (« rejected by spendControls.maxAmountPerPayment » dans @x402/fetch v2, « Payment amount exceeds maximum allowed » dans thirdweb).
// npm install @x402/fetch @x402/evm viem
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const client = new x402Client();
// Per-payment cap. The qualified timestamp costs 0.90 USD: a cap below
// that makes the client refuse to sign, and Sellat never sees the request.
// "$1" is the v2 default; set it explicitly so nobody lowers it by accident.
client.setSpendControls({ maxAmountPerPayment: "$1" });
client.register("eip155:8453", new ExactEvmScheme(privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`)));
const pay = wrapFetchWithPayment(fetch, client);
// 1. Hash locally: only the SHA-256 leaves your machine.
const hash = createHash("sha256").update(readFileSync("contract.pdf")).digest("hex");
// 2. The proof (0.01 USD).
const proofRes = await pay("https://sellat.app/api/v2/proofs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ hash, name: "contract.pdf" }),
});
if (!proofRes.ok) throw new Error(await proofRes.text());
const proof = await proofRes.json();
// proof.id, proof.state ("queued", then "anchored"), proof.urls.proof_json,
// proof.payment.transaction; proof.api_key comes once: keep it (read-only).
// 3. The qualified eIDAS timestamp on that proof (0.90 USD).
const sealRes = await pay(`https://sellat.app/api/v2/proofs/${proof.id}/qualified`, { method: "POST" });
if (!sealRes.ok) throw new Error(await sealRes.text());
const seal = await sealRes.json();
// seal.qualified.state === "issued", seal.qualified.time, seal.qualified.tsr_url
// thirdweb sets no cap unless asked: wrapFetchWithPayment(fetch, client, wallet, { maxValue: 1_000_000n }) from "thirdweb/x402" (1 USDC in base units). Idempotence
Envoyez un en-tête Idempotency-Key (jusqu’à 200 caractères) avec POST /proofs et vous pourrez réessayer sans risque : si vous l’avez déjà utilisé avec la même clé d’API, vous recevez la preuve d’origine avec 200 et "created": false, quel que soit le nouveau corps, et aucun horodatage n’est émis.
Sans l’en-tête, renvoyer des octets qui appartiennent déjà à votre compte renvoie aussi la preuve existante (200), sans changer son nom ni ses métadonnées. Mais si un autre compte a enregistré ces octets en premier, chaque requête crée une nouvelle preuve avec limitations : si vous pouvez réessayer, utilisez toujours Idempotency-Key.
L’horodatage n’est jamais dupliqué non plus : une preuve en a un seul, et le redemander renvoie le même sans en consommer un autre.
Vérifier sans Sellat
Une preuve Sellat n’a pas besoin de Sellat pour être vérifiée. Avec le fichier et son proof.json, le vérificateur open source recalcule l’empreinte et le chemin Merkle, et lit l’ancrage sur la blockchain publique.
L’horodatage qualifié se vérifie avec OpenSSL, avec le fichier, le .tsr et le certificat de l’autorité, fourni dans le dossier de preuves. -partial_chain est nécessaire parce que la liste de confiance publie le certificat de l’autorité elle-même, pas celui d’une CA.
# 1. The proof: file + proof.json + the public blockchain
curl https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json -o proof.json
npx sellat-verify contract.pdf proof.json
# 2. The qualified seal: file + .tsr + the authority's certificate
# (autoridad-sellado.pem comes inside evidence.zip)
openssl ts -verify -in seal.tsr -data contract.pdf \
-CAfile autoridad-sellado.pem -partial_chain Preuves
Créer une preuve
POST /proofs
Enregistre l’empreinte SHA-256 d’un fichier et renvoie la preuve. Avec "qualified": true, elle émet aussi l’horodatage qualifié (eIDAS) dans le même appel, en consommant un horodatage du compte.
Paramètres
-
hashobligatoire - L’empreinte SHA-256 du fichier : 64 caractères hexadécimaux.
-
namefacultatif - Le nom affiché dans le tableau de bord et sur le certificat. Seul le nom du fichier est gardé, sans le chemin. Par défaut, les 12 premiers caractères du hash.
-
metadatafacultatif - Un objet JSON à vous, jusqu’à 4 Ko. Stocké avec la preuve, jamais renvoyé ni publié.
-
sizefacultatif - Taille du fichier en octets. Informatif.
-
mime_typefacultatif - Type du fichier, utilisé pour servir l’original si vous le conservez. Par défaut,
application/octet-stream. -
qualifiedfacultatif -
trueémet aussi l’horodatage qualifié (eIDAS). -
Idempotency-Keyfacultatif - Réessayer sans dupliquer (voir Idempotence).
Réponses
- 201
- Preuve créée.
- 200
- Elle existait déjà : ces octets appartiennent déjà à votre compte, ou vous avez répété une
Idempotency-Key. Contient"created": false. - 400
invalid_request: un champ manque ou n’est pas valide.- 401
unauthorized.- 402
payment_required: horodatage demandé, mais il ne vous en reste plus. Rien n’est écrit.- 409
conflict: horodatage demandé sur des octets qu’un autre compte a enregistrés en premier.- 429
quota_exceeded(limite quotidienne) ourate_limited(par minute).- 503
seal_unavailableouunavailable. Si la preuve a été créée, l’erreur contient sonproof_id.
Si l’autorité tarde à répondre, la preuve est quand même créée et qualified arrive comme {"state": "pending"} : redemandez-le avec POST /proofs/{id}/qualified, sans frais.
Si un autre compte a enregistré ces octets en premier, la preuve est créée et ancrée, mais sans certificat propre : les URL du certificat sont null et la réponse contient "limitations": ["hash_registered_by_another_account"].
curl https://sellat.app/api/v2/proofs \
-H "Authorization: Bearer $SELLAT_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: contract-2026-0142" \
-d '{
"hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
"name": "contract.pdf",
"qualified": true,
"metadata": { "ref": "case-2026-0142" }
}' {
"id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"created": true,
"hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
"algorithm": "SHA-256",
"name": "contract.pdf",
"state": "queued",
"received_at": "2026-09-30T09:12:03.418Z",
"anchoring": {
"policy": "scheduled",
"batch_closes_after": "2026-09-30T12:00:00.000Z"
},
"anchors": [],
"bitcoin": {
"state": "pending",
"block_height": null
},
"qualified": {
"state": "issued",
"authority": "FNMT-RCM",
"time": "2026-09-30T09:12:05.000Z",
"serial_number": "175755C77B4239226AA0428FE1C107C1",
"policy_oid": "0.4.0.2023.1.1",
"tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
},
"original": null,
"urls": {
"self": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"proof_json": "https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json",
"certificate": "https://sellat.app/certificate/3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b",
"certificate_pdf": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf",
"evidence_zip": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip"
}
} Consulter une preuve
GET /proofs/{id}
Renvoie la preuve avec son état, ses ancrages, l’attestation Bitcoin, l’horodatage s’il existe, l’original s’il est conservé, et toutes ses URL.
Paramètres
-
idobligatoire - L’
idde la preuve.
Réponses
- 200
- La preuve.
- 401
unauthorized.- 404
not_found: elle n’existe pas ou appartient à un autre compte.
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e \
-H "Authorization: Bearer $SELLAT_API_TOKEN" {
"id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
"algorithm": "SHA-256",
"name": "contract.pdf",
"state": "anchored",
"received_at": "2026-09-30T09:12:03.418Z",
"anchoring": {
"policy": "scheduled",
"batch_closes_after": "2026-09-30T12:00:00.000Z"
},
"anchors": [
{
"network": "Polygon Mainnet",
"chain_id": 137,
"state": "anchored",
"tx_hash": "0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a",
"block_number": 94653624,
"block_timestamp": "2026-09-30T12:01:44.000Z",
"explorer_url": "https://polygonscan.com/tx/0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a"
}
],
"bitcoin": {
"state": "pending",
"block_height": null
},
"qualified": {
"state": "issued",
"authority": "FNMT-RCM",
"time": "2026-09-30T09:12:05.000Z",
"serial_number": "175755C77B4239226AA0428FE1C107C1",
"policy_oid": "0.4.0.2023.1.1",
"tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
},
"original": null,
"urls": {
"self": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"proof_json": "https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json",
"certificate": "https://sellat.app/certificate/3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b",
"certificate_pdf": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf",
"evidence_zip": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip"
}
} Lister les preuves
GET /proofs
Les preuves de votre compte, les plus récentes d’abord : celles créées par l’API et celles créées sur le web. Chaque élément de data est une preuve complète, comme GET /proofs/{id}.
Pour la page suivante, passez le next_cursor renvoyé par la précédente. Quand il vaut null, il n’y en a plus.
Paramètres
-
limitfacultatif - Preuves par page, de 1 à 100. Par défaut, 25.
-
cursorfacultatif - Le
next_cursorde la page précédente.
Réponses
- 200
dataetnext_cursor.- 400
invalid_request:limitoucursorinvalide.- 401
unauthorized.
curl "https://sellat.app/api/v2/proofs?limit=25" \
-H "Authorization: Bearer $SELLAT_API_TOKEN" {
"data": [
{
"id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
"name": "contract.pdf",
"state": "confirmed",
"received_at": "2026-09-30T09:12:03.418Z"
}
],
"next_cursor": "1843"
} Horodatage qualifié (eIDAS)
Demander l’horodatage qualifié
POST /proofs/{id}/qualified
Émet un horodatage qualifié RFC 3161 sur l’empreinte du fichier, signé par un prestataire de services de confiance qualifié de la liste de confiance de l’UE. Selon l’article 41, paragraphe 2, du règlement eIDAS, un horodatage électronique qualifié bénéficie d’une présomption d’exactitude de la date et de l’heure qu’il indique et d’intégrité des données.
Il consomme un horodatage du compte : celui de bienvenue ou l’un de vos packs. L’API ne débite jamais de carte et n’ouvre aucun paiement ; les packs s’achètent sur la page des tarifs.
Paramètres
-
idobligatoire - L’
idde la preuve.
Réponses
- 200
- Horodatage émis, ou il existait déjà (
"created": false, rien consommé). Contientseals_remaining. - 202
- L’horodatage est payé, mais l’autorité n’a pas répondu. Rappelez (
Retry-After: 30) : il se termine sans frais. - 401
unauthorized.- 402
payment_required: plus d’horodatages. Contientseals_remainingetbuy_url.- 404
not_found.- 409
conflict: un autre compte a enregistré ces octets en premier.- 503
seal_unavailable.
Une preuve a un seul horodatage, pour toujours. Si l’émission échoue définitivement, l’horodatage revient à votre solde.
curl -X POST https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified \
-H "Authorization: Bearer $SELLAT_API_TOKEN" {
"proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"created": true,
"qualified": {
"state": "issued",
"authority": "FNMT-RCM",
"time": "2026-09-30T09:12:05.000Z",
"serial_number": "175755C77B4239226AA0428FE1C107C1",
"policy_oid": "0.4.0.2023.1.1",
"tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
},
"seals_remaining": 4
} Consulter l’horodatage
GET /proofs/{id}/qualified
Renvoie seulement l’horodatage de la preuve, ou null s’il n’y en a pas encore. N’émet rien et ne consomme rien.
Paramètres
-
idobligatoire - L’
idde la preuve.
Réponses
- 200
proof_idetqualified.- 401
unauthorized.- 404
not_found.
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified \
-H "Authorization: Bearer $SELLAT_API_TOKEN" {
"proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"qualified": {
"state": "issued",
"authority": "FNMT-RCM",
"time": "2026-09-30T09:12:05.000Z",
"serial_number": "175755C77B4239226AA0428FE1C107C1",
"policy_oid": "0.4.0.2023.1.1",
"tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
}
} Télécharger le jeton (.tsr)
GET /proofs/{id}/qualified.tsr
L’horodatage tel que l’autorité l’a renvoyé : une TimeStampResp RFC 3161 en DER. C’est le fichier qu’un expert ou un tribunal peut valider sans Sellat.
Paramètres
-
idobligatoire - L’
idde la preuve.
Réponses
- 200
- Le
.tsr(application/timestamp-reply). - 401
unauthorized.- 404
not_found: la preuve n’existe pas ou n’a pas encore d’horodatage.
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr \
-H "Authorization: Bearer $SELLAT_API_TOKEN" \
-o seal.tsr HTTP/1.1 200 OK
Content-Type: application/timestamp-reply
Content-Disposition: attachment; filename="sellat-6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e-qualified-timestamp.tsr"
<RFC 3161 TimeStampResp, DER> Certificat et preuves
Certificat PDF
GET /proofs/{id}/certificate.pdf
Le certificat de la preuve, pour les personnes : l’empreinte, la date, les ancrages et l’horodatage qualifié s’il existe, avec les instructions pour le vérifier.
Paramètres
-
idobligatoire - L’
idde la preuve. -
langfacultatif - Langue du certificat :
es,en,deoufr. Toute autre valeur donneen.
Réponses
- 200
- Le PDF.
- 401
unauthorized.- 404
not_found.- 409
conflict: un autre compte a enregistré ces octets en premier et la preuve n’a pas de certificat propre.
curl "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf?lang=es" \
-H "Authorization: Bearer $SELLAT_API_TOKEN" \
-o certificate.pdf HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="certificado-3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b.pdf"
<PDF> Dossier de preuves
GET /proofs/{id}/evidence.zip
Un ZIP avec ce qu’il faut pour défendre la preuve sans Sellat : le certificat, le proof.json, l’horodatage et le certificat de l’autorité s’il existe, les instructions et, s’il est conservé, l’original.
Paramètres
-
idobligatoire - L’
idde la preuve. -
langfacultatif - Langue des textes du dossier :
es,en,deoufr.
Réponses
- 200
- Le ZIP.
- 401
unauthorized.- 404
not_found.- 409
conflict: un autre compte a enregistré ces octets en premier.
Joindre l’original consomme l’un des téléchargements du jour. S’il n’en reste plus, le dossier sort sans lui et le README le dit.
curl "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip?lang=es" \
-H "Authorization: Bearer $SELLAT_API_TOKEN" \
-o evidence.zip HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="sellat-evidencia-3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b.zip"
<ZIP> Conservation de l’original
Déposer l’original
PUT /proofs/{id}/original
Stocke le fichier à côté de sa preuve, sur des serveurs de l’Union européenne. Sellat recalcule l’empreinte de ce qu’il reçoit et le refuse s’il ne correspond pas à la preuve.
Envoyez les octets bruts avec le Content-Type du fichier, ou en multipart/form-data dans le champ file. POST fonctionne comme PUT.
Paramètres
-
idobligatoire - L’
idde la preuve. -
fileobligatoire - Le fichier. Jusqu’à 10 MB avec le compte gratuit et 50 MB avec la Conservation, et 25 Mo par requête au plus.
Réponses
- 201
- Stocké. Renvoie la preuve avec
original. - 200
- Il était déjà stocké.
- 400
invalid_request: corps vide ou illisible.- 401
unauthorized.- 404
not_found.- 409
hash_mismatch(ce n’est pas le fichier de la preuve) ouconflict(octets d’un autre compte).- 413
file_too_large: au-delà de la limite par fichier de votre offre. Contientmax_file_bytes.- 502
unavailable: le stockage n’a pas répondu.- 503
custody_unavailable.- 507
custody_quota_exceeded: plus d’espace sur votre compte. Contientused_bytesetmax_account_bytes.
La conservation est facultative : la preuve vaut autant sans l’original. Conserver ajoute la préservation ; cela ne conditionne pas la preuve.
curl -X PUT https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
-H "Authorization: Bearer $SELLAT_API_TOKEN" \
-H "Content-Type: application/pdf" \
--data-binary @contract.pdf {
"id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"state": "anchored",
"original": {
"stored": true,
"file_name": "contract.pdf",
"size_bytes": 184320,
"mime_type": "application/pdf",
"stored_at": "2026-09-30T09:15:10.000Z",
"url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original"
}
} Télécharger l’original
GET /proofs/{id}/original
Renvoie le fichier conservé. Sellat recalcule son empreinte à la sortie et refuse de le servir s’il ne correspond plus. Chaque téléchargement compte dans la limite quotidienne de votre offre ; l’en-tête X-Sellat-Custody-Downloads-Remaining indique combien il en reste.
Paramètres
-
idobligatoire - L’
idde la preuve.
Réponses
- 200
- Le fichier, avec son type et son nom.
- 401
unauthorized.- 404
- Aucun original en conservation.
- 409
conflict: l’original n’est pas encore stocké.- 429
download_quota_exceeded: limite quotidienne de téléchargements. Repart à 00:00 UTC.- 500
integrity_failed: ce qui est stocké ne correspond plus à l’empreinte.- 502
unavailable.
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
-H "Authorization: Bearer $SELLAT_API_TOKEN" \
-o contract.pdf HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="contract.pdf"
X-Sellat-Custody-Downloads-Remaining: 9
<the original bytes> Mettre fin à la conservation
DELETE /proofs/{id}/original
Supprime l’original conservé. La preuve ne change pas : elle reste ancrée, horodatée et vérifiable.
Paramètres
-
idobligatoire - L’
idde la preuve.
Réponses
- 200
"original": nullet"proof_unaffected": true."purged": falsesignifie que la suppression physique est en file d’attente.- 401
unauthorized.- 404
- Aucun original en conservation.
curl -X DELETE https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
-H "Authorization: Bearer $SELLAT_API_TOKEN" {
"proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"original": null,
"purged": true,
"proof_unaffected": true
} Compte
Votre compte
GET /account
Ce que peut faire la clé qui appelle : l’offre, les horodatages qu’il vous reste (bienvenue et packs), l’espace et les téléchargements de conservation, les preuves du jour et leur limite, et les clés actives. Consultez-le avant un gros lot.
Le bloc connector contient les limites des boutiques connectées (WooCommerce).
Réponses
- 200
- Le compte.
- 401
unauthorized.
curl https://sellat.app/api/v2/account \
-H "Authorization: Bearer $SELLAT_API_TOKEN" {
"plan": "free",
"plan_name": "Free",
"seals": {
"available": true,
"remaining": 1,
"welcome_remaining": 1,
"pack_remaining": 0,
"price_eur": 6,
"buy_url": "https://sellat.app/precios"
},
"custody": {
"available": true,
"used_bytes": 0,
"max_account_bytes": 52428800,
"max_file_bytes": 10485760,
"downloads_used_today": 0,
"max_downloads_per_day": 10
},
"proofs": {
"daily_limit": 1000,
"used_today": 3,
"resets_in_seconds": 41231
},
"connector": {
"mode_available": "daily",
"eidas_available": false,
"custody_available": false,
"operations": {
"daily_limit": 1000,
"resets_in_seconds": 41231
}
},
"keys": {
"active": 1
},
"urls": {
"dashboard": "https://sellat.app/dashboard",
"plans": "https://sellat.app/precios#pricing-custody",
"seals": "https://sellat.app/precios#pricing-seal-title",
"docs": "https://sellat.app/developers/docs",
"connect": "https://sellat.app/connect/woocommerce"
}
} Preuve portable
Preuve portable (proof.json)
GET /proof/{id}.json Sans clé
Le document qui permet à chacun de vérifier la preuve sans Sellat, au format public sellat-proof/2 : l’empreinte, la feuille, le chemin Merkle, la racine, l’ancrage Polygon et les attestations (Bitcoin et l’horodatage qualifié). Il est public exprès : qui le détient peut vérifier la preuve sans compte.
Paramètres
-
idobligatoire - L’
idde la preuve.
Réponses
- 200
- Le
proof.json. - 202
- Pas encore ancrée :
{"ready": false, "state": "..."}avecRetry-After: 60. - 400
- L’
idn’est pas un UUID. - 404
- Elle n’existe pas.
curl https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json -o proof.json {
"schema": "sellat-proof/2",
"proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
"content": {
"algorithm": "SHA-256",
"hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f"
},
"leaf": {
"formula": "SHA-256('sellat-leaf:v2:' + proof_id + ':' + content.hash)",
"value": "31668b4d846680cd920a1cb5be200ab0562909e8778223087c1738746a1cbb26"
},
"merkle": {
"index": 1,
"path": [
{
"position": "left",
"hash": "9e246a6d6dda91ee3916e149d834cd9a140e3470fbf5b6ec67824705dbbc82d4"
}
],
"root": "88fa1c4c3587d86d6f713e438c7ae745a91a6e03a5dd1b8282f17f35f30bfa14"
},
"anchors": [
{
"chain_id": 137,
"network": "Polygon Mainnet",
"tx_hash": "0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a",
"block_number": 94653624,
"payload": "sellat:v2:88fa1c4c3587d86d6f713e438c7ae745a91a6e03a5dd1b8282f17f35f30bfa14"
}
],
"attestations": [
{
"type": "opentimestamps",
"state": "submitted"
},
{
"type": "rfc3161-qualified-timestamp",
"state": "issued"
}
]
} Il vous manque quelque chose, ou vous avez besoin de plus de volume ? Écrivez à hola@sellat.app : vous parlerez avec ceux qui ont écrit le code.