Horodater un fichier
Obtenir le jeton d'horodatage d'un fichier ou de son empreinte, télécharger le jeton et son attestation, puis vérifier ce qu'il prouve.
Un horodatage est un jeton RFC 3161, émis par une autorité d'horodatage sur l'empreinte SHA-256 d'un fichier. Il prouve que ces octets existaient à cet instant, et que votre compte les détenait — pas qui en est l'auteur. La moindre modification du fichier change son empreinte : le fichier horodaté se garde au bit près.
Chaque jeton obtenu est facturé à l'organisation. Une organisation qui n'est pas encore en production ne peut pas horodater (400) :
voir Environnement d'essai.
1Horodater une empreinte, en un appel
Calculez l'empreinte SHA-256 du fichier chez vous (sha256sum contrat.pdf) : le fichier ne quitte pas vos machines.
POST https://staging-api.docage.com/Timestamps/StampNow
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…
Content-Type: application/json
{
"Name": "Contrat Atelier Lumière — version 3",
"Digest": "4C3F2A9B8E7D6C5B4A39281706F5E4D3C2B1A09F8E7D6C5B4A3928170F6E5D4C",
"FileName": "contrat-atelier-lumiere-v3.pdf",
"TemplateId": "77777777-0000-4000-8000-000000000007"
}curl -X POST "https://staging-api.docage.com/Timestamps/StampNow" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…" \
-H "Content-Type: application/json" \
-d '{
"Name": "Contrat Atelier Lumière — version 3",
"Digest": "4C3F2A9B8E7D6C5B4A39281706F5E4D3C2B1A09F8E7D6C5B4A3928170F6E5D4C",
"FileName": "contrat-atelier-lumiere-v3.pdf",
"TemplateId": "77777777-0000-4000-8000-000000000007"
}'using HttpClient client = new();
using HttpRequestMessage request = new(HttpMethod.Post, "https://staging-api.docage.com/Timestamps/StampNow");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent("""
{
"Name": "Contrat Atelier Lumière — version 3",
"Digest": "4C3F2A9B8E7D6C5B4A39281706F5E4D3C2B1A09F8E7D6C5B4A3928170F6E5D4C",
"FileName": "contrat-atelier-lumiere-v3.pdf",
"TemplateId": "77777777-0000-4000-8000-000000000007"
}
""", Encoding.UTF8, "application/json");
using HttpResponseMessage response = await client.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(result);const response = await fetch("https://staging-api.docage.com/Timestamps/StampNow", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"Name": "Contrat Atelier Lumière — version 3",
"Digest": "4C3F2A9B8E7D6C5B4A39281706F5E4D3C2B1A09F8E7D6C5B4A3928170F6E5D4C",
"FileName": "contrat-atelier-lumiere-v3.pdf",
"TemplateId": "77777777-0000-4000-8000-000000000007"
})
});
const text = await response.text();
if (!response.ok) throw new Error(text);
const result = text ? JSON.parse(text) : null;La réponse est l'identifiant de l'horodatage, déjà obtenu.
Digest: l'empreinte, en hexadécimal — 64 caractères, en majuscules ou en minuscules.TemplateId(facultatif) : un modèle d'horodatage (GET https://staging-api.docage.com/Timestamps/TemplatesIdAndName), dont la nature, le renouvellement et la langue de l'attestation sont repris. Un modèle « PDF horodaté » n'y sert pas : il attend le fichier (étape 2).TransactionId(facultatif) : le parcours auquel rattacher l'horodatage — un parcours de votre organisation, que vous voyez.- Pour un ensemble de fichiers — les fichiers d'une version d'un logiciel —, donnez
Manifestà la place deDigest: la liste des fichiers,[{ "Path": "app/main.js", "Sha256": "…" }, …]. Un seul jeton couvre l'empreinte du manifeste, queGET https://staging-api.docage.com/Timestamps/Manifest/{id}rend ensuite, une ligne par fichier. Prouver qu'un fichier fait partie de l'ensemble, c'est produire ce manifeste : il montre le chemin et l'empreinte des autres fichiers, jamais leur contenu.
400 : l'empreinte est invalide, l'organisation n'est pas en production, ou l'autorité est momentanément injoignable — dans ce
dernier cas, rien n'est créé : rejouez l'appel tel quel. 404 : le modèle ou le parcours est introuvable, ou vous ne le voyez pas ;
un parcours d'une autre organisation aussi. Rien n'est alors horodaté.
La création d'un brouillon (étape 2) ignore TransactionId, comme Origin et Manifest, que le serveur pose seul : un horodatage
se rattache à un parcours ici, ou par le document de ce parcours (étape 3).
2Horodater un fichier déposé
Pour que Docage garde le fichier, ou incorpore le jeton dans un PDF, créez un brouillon, déposez-y le fichier, puis horodatez.
POST https://staging-api.docage.com/Timestamps
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…
Content-Type: application/json
{ "Name": "Plans de la vitrine", "Kind": 3 }curl -X POST "https://staging-api.docage.com/Timestamps" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…" \
-H "Content-Type: application/json" \
-d '{"Name": "Plans de la vitrine", "Kind": 3}'using HttpClient client = new();
using HttpRequestMessage request = new(HttpMethod.Post, "https://staging-api.docage.com/Timestamps");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent("""
{
"Name": "Plans de la vitrine",
"Kind": 3
}
""", Encoding.UTF8, "application/json");
using HttpResponseMessage response = await client.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(result);const response = await fetch("https://staging-api.docage.com/Timestamps", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({"Name": "Plans de la vitrine", "Kind": 3})
});
const text = await response.text();
if (!response.ok) throw new Error(text);
const result = text ? JSON.parse(text) : null;Kind |
Nature |
|---|---|
1 |
Empreinte seule (d'office) : le fichier n'est pas gardé. |
2 |
Fichier conservé : Docage garde le fichier horodaté. |
3 |
PDF horodaté : le jeton est incorporé au PDF. |
Un brouillon créé ainsi ne reprend pas les réglages d'un modèle : donnez vous-même Kind, et au besoin AutoRenew (ci-dessous).
curl -X POST "https://staging-api.docage.com/Timestamps/Upload/88888888-0000-4000-8000-000000000008" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…" \
-F "file=@plans-vitrine.pdf;type=application/pdf"Le serveur calcule l'empreinte et la rend. Un PDF horodaté n'accepte qu'un PDF ; un fichier ne dépasse pas 200 Mo.
POST https://staging-api.docage.com/Timestamps/Stamp/88888888-0000-4000-8000-000000000008
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…curl -X POST "https://staging-api.docage.com/Timestamps/Stamp/88888888-0000-4000-8000-000000000008" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…"using HttpClient client = new();
using HttpRequestMessage request = new(HttpMethod.Post, "https://staging-api.docage.com/Timestamps/Stamp/88888888-0000-4000-8000-000000000008");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
using HttpResponseMessage response = await client.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(result);const response = await fetch("https://staging-api.docage.com/Timestamps/Stamp/88888888-0000-4000-8000-000000000008", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`
}
});
const text = await response.text();
if (!response.ok) throw new Error(text);
const result = text ? JSON.parse(text) : null;200, sans corps : le jeton est obtenu. 400 : l'autorité n'a pas répondu — l'horodatage reste un brouillon, le motif dans
StatusDetails, rien n'est facturé — ou l'organisation n'est pas en production. 409 : l'horodatage est déjà obtenu.
GET https://staging-api.docage.com/Timestamps/Document/{id} rend le fichier gardé, ou le PDF qui porte le jeton.
3Horodater un document de parcours
POST https://staging-api.docage.com/Timestamps/FromTransactionFile/eeeeeeee-0000-4000-8000-000000000025?templateId=77777777-0000-4000-8000-000000000007
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…curl -X POST "https://staging-api.docage.com/Timestamps/FromTransactionFile/eeeeeeee-0000-4000-8000-000000000025?templateId=77777777-0000-4000-8000-000000000007" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…"using HttpClient client = new();
using HttpRequestMessage request = new(HttpMethod.Post, "https://staging-api.docage.com/Timestamps/FromTransactionFile/eeeeeeee-0000-4000-8000-000000000025?templateId=77777777-0000-4000-8000-000000000007");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
using HttpResponseMessage response = await client.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(result);const response = await fetch("https://staging-api.docage.com/Timestamps/FromTransactionFile/eeeeeeee-0000-4000-8000-000000000025?templateId=77777777-0000-4000-8000-000000000007", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`
}
});
const text = await response.text();
if (!response.ok) throw new Error(text);
const result = text ? JSON.parse(text) : null;L'identifiant est celui du document dans le parcours (TransactionFiles[].Id) ; le modèle est facultatif. La réponse est
l'identifiant de l'horodatage, rattaché au parcours. Quand l'horodatage échoue, rien n'est créé.
404 : le document ou le modèle est introuvable, ou vous ne le voyez pas. 400 : le contenu du document est introuvable,
l'organisation n'est pas en production, ou l'autorité est momentanément injoignable.
4Ce que dit le jeton
GET https://staging-api.docage.com/Timestamps/{id} :
| Propriété | Ce qu'elle dit |
|---|---|
Status |
0 brouillon, 2 horodaté, -100 erreur. |
GenTimeUtc |
L'instant attesté, en UTC. |
AuthorityName, IsQualified |
L'autorité d'horodatage, et si elle est qualifiée au sens du règlement eIDAS. |
SerialNumber, PolicyOid |
Le numéro de série du jeton, la politique de l'autorité. |
TsuExpirationDate |
La fin de validité du certificat qui a signé le jeton : un renouvellement, avant cette date, garde la preuve vérifiable. |
GET https://staging-api.docage.com/Timestamps/Token/{id}: le jeton, en DER (fichier.tst). Il se vérifie hors de Docage, avecopenssl ts -verify.GET https://staging-api.docage.com/Timestamps/Receipt/{id}: l'attestation, en PDF — la lecture du jeton, le jeton joint, scellée. L'en-têteDocage-Sealeddit si le cachet a pu être posé.
Un horodatage sans jeton répond 409 à ces deux routes.
5Vérifier un jeton
GET https://staging-api.docage.com/Timestamps/Verify/{id}vérifie un horodatage de Docage avec son jeton, son empreinte et les données de validation gardées à son émission, qui la rendent possible même après un changement de certificat de l'autorité.POST https://staging-api.docage.com/Timestamps/Verifyvérifie un jeton reçu de n'importe où, sans compte : un formulairemultipart/form-dataavectoken(le fichier.tst) etfile(le fichier horodaté, qui n'est pas gardé) oudigest(son empreinte).
curl -X POST "https://staging-api.docage.com/Timestamps/Verify" \
-F "token=@contrat-atelier-lumiere-v3.tst" \
-F "file=@contrat-atelier-lumiere-v3.pdf"La réponse répond à quatre questions, pour savoir laquelle échoue : le jeton se lit-il (IsWellFormed), porte-t-il cette
empreinte (DigestMatches), sa signature tient-elle (SignatureIsValid), et de qui vient-il (AuthorityName, ChainIsTrusted).
IsValid est vrai quand les trois premières le sont : le jeton atteste bien cette empreinte à l'instant GenTimeUtc.
6Retrouver si un fichier a été horodaté
QUERY https://staging-api.docage.com/Timestamps
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…
Content-Type: application/json
{
"Filter": {
"OperatorType": "And",
"Operands": [
{ "PropertyName": "Digest", "Comparison": "=", "Value": "4C3F2A9B8E7D6C5B4A39281706F5E4D3C2B1A09F8E7D6C5B4A3928170F6E5D4C" },
{ "PropertyName": "Status", "Comparison": "=", "Value": 2 }
]
},
"Take": 10
}curl -X QUERY "https://staging-api.docage.com/Timestamps" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…" \
-H "Content-Type: application/json" \
-d '{
"Filter": {"OperatorType": "And", "Operands": [{"PropertyName": "Digest", "Comparison": "=", "Value": "4C3F2A9B8E7D6C5B4A39281706F5E4D3C2B1A09F8E7D6C5B4A3928170F6E5D4C"}, {"PropertyName": "Status", "Comparison": "=", "Value": 2}]},
"Take": 10
}'using HttpClient client = new();
using HttpRequestMessage request = new(new HttpMethod("QUERY"), "https://staging-api.docage.com/Timestamps");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent("""
{
"Filter": {
"OperatorType": "And",
"Operands": [
{
"PropertyName": "Digest",
"Comparison": "=",
"Value": "4C3F2A9B8E7D6C5B4A39281706F5E4D3C2B1A09F8E7D6C5B4A3928170F6E5D4C"
},
{
"PropertyName": "Status",
"Comparison": "=",
"Value": 2
}
]
},
"Take": 10
}
""", Encoding.UTF8, "application/json");
using HttpResponseMessage response = await client.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(result);const response = await fetch("https://staging-api.docage.com/Timestamps", {
method: "QUERY",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"Filter": {"OperatorType": "And", "Operands": [{"PropertyName": "Digest", "Comparison": "=", "Value": "4C3F2A9B8E7D6C5B4A39281706F5E4D3C2B1A09F8E7D6C5B4A3928170F6E5D4C"}, {"PropertyName": "Status", "Comparison": "=", "Value": 2}]},
"Take": 10
})
});
const text = await response.text();
if (!response.ok) throw new Error(text);
const result = text ? JSON.parse(text) : null;L'empreinte est rangée en majuscules. Une fiche trouvée : un jeton de cette empreinte existe parmi les horodatages que vous voyez. Aucune : ce n'est pas la preuve qu'aucun horodatage n'existe ailleurs.
Les pièges
- Gardez le fichier tel quel. En empreinte seule, Docage ne garde pas le fichier (sauf
KeepFileàtrue) : sans lui, le jeton ne prouve plus rien. AutoRenewàtrue— posé sur le brouillon, ou repris du modèle parStampNowetFromTransactionFile— fait obtenir un nouveau jeton avant la fin de validité du certificat qui a signé le précédent ; chaque renouvellement est un horodatage de plus (RenewsTimestampId), facturé.- Un modèle ne s'horodate pas : seul un horodatage tiré de lui reçoit un jeton.
- Le jeton que pose la signature d'un parcours n'est pas facturé, et ne paraît dans aucune liste.