La poussée: le contrat de dépôt, version 1

Page technique, pour un éditeur qui écrit son propre plugin ou son propre code de poussée.

Cette page décrit ce qu'un émetteur envoie à Eiger Vault, ce qu'il reçoit en retour, et comment il se comporte en cas d'erreur ou de coupure. Elle suit les contrats publiés tels qu'ils sont aujourd'hui sur la branche principale de nos dépôts: le document natif ev-native/1 pour la requête, le contrat de déposer v1 pour ce que répond le vault, et le format des droits ev-rights/1. Ces contrats sont versionnés, et une version publiée n'est jamais changée: un ajout arrive par une version nouvelle, qui n'enlève rien à la précédente. Quelques points seront précisés plus tard: voir la section 11.

Statut d'ouverture. L'interface qui reçoit vos requêtes n'est pas encore ouverte sur Internet; elle ne l'est qu'à l'arrivée du premier éditeur. Son adresse sera communiquée à ce moment. Les exemples ci-dessous utilisent https://<adresse de l'interface native>.

1. Le principe

Vous n'appelez pas le vault directement: vous appelez l'interface native, qui traduit votre document natif en requête de dépôt, la transmet au vault avec votre clé, et vous rend sa réponse telle quelle. Elle ne garde rien de ce qui passe par elle et n'a pas de file d'attente: elle ne répond qu'après la réponse du vault. Si le vault est injoignable, elle répond 503.

Poussée: le CMS de l'éditeur envoie à Eiger Vault. Relève: Eiger Vault lit à la source de l'éditeur.

Une requête de dépôt porte un document: les blocs de texte de l'article, et, facultativement, ses fichiers (images, PDF de la page, vidéo).

2. L'authentification

La clé éditeur que nous vous avons remise a la forme evk_ suivi de seize chiffres hexadécimaux, de _, puis de soixante-quatre autres chiffres hexadécimaux. Elle se présente dans l'en-tête Authorization, sous le schéma Bearer, et nulle part ailleurs:

Authorization: Bearer evk_0123456789abcdef_<64 chiffres hexadécimaux>

3. La requête

POST /deposits
Authorization: Bearer evk_0123456789abcdef_<64 chiffres hexadécimaux>
Content-Type: multipart/form-data; boundary=…
X-Correlation-ID: <facultatif>

POST /deposits pour déposer, et POST /presence-checks pour la vérification groupée (section 9): ce sont les deux seules routes. Tout autre chemin, ou toute autre méthode, reçoit 404 (not-found).

Le corps est en plusieurs parties (multipart/form-data):

Partie Contenu
native_document le document natif, en JSON obligatoire
supplementary_metadata le descriptif complémentaire, objet JSON libre (mots-clés du CMS, titres alternatifs) facultatif
une partie par fichier les octets du fichier, tels quels; son nom est celui que le document natif lui donne autant que de fichiers

Les noms native_document et supplementary_metadata sont réservés: aucun fichier ne les prend. Chaque partie que la requête porte doit être désignée par le document natif, et chaque partie qu'il désigne doit être présente. Une partie qui déclare un Content-Transfer-Encoding autre que 7bit, 8bit ou binary est refusée.

L'identifiant de corrélation. Vous pouvez envoyer X-Correlation-ID; sinon l'interface en tire un. Il accompagne la requête jusqu'au vault, revient dans l'en-tête de la réponse et dans le corps de tout refus: gardez-le pour toute demande d'assistance.

Les bornes (valeurs de départ, ajustées par Eiger Vault avant la mise en service):

Ce qui est borné Borne
la requête entière 16 Mio
la partie native_document 1 Mio
chacune des autres parties 16 Mio
la partie supplementary_metadata 64 Kio

Au-delà d'une borne, l'interface cesse de lire et refuse native.request-too-large (413). Avec ces valeurs, le dépôt le plus lourd qui puisse en résulter, base64 compris, tient sous les 24 Mio que le vault accepte pour le corps d'un dépôt.

4. Le document natif

C'est un texte JSON que l'interface ne réécrit jamais. S'il n'y a pas d'enveloppe d'origine explicite, ces octets sont eux-mêmes scellés comme enveloppe d'origine du dépôt: ils sont conservés tels quels.

{
  "format": "ev-native/1",
  "source": "ch-src-0192f3a4-5b6c-7d8e-9fa0-b1c2d3e4f5a6",
  "metadata": {
    "document_type": "article",
    "source_reference": "12345",
    "published_at": "2026-10-05T08:30:00+02:00",
    "title": "Un titre",
    "authors": ["Anne Exemple"]
  },
  "body_form": "blocks",
  "text_blocks": [
    {"role": "paragraph", "text": "Un paragraphe."},
    {"role": "caption", "text": "La photo.", "file": "photo-1"}
  ],
  "files": [
    {"name": "photo-1", "role": "image", "media_type": "image/jpeg"}
  ],
  "cms_revision": "48213"
}

Le contrat est fermé à tous les niveaux. Un membre que le contrat ne nomme pas, à quelque profondeur que ce soit, fait refuser le document (native.unknown-member): un membre mal orthographié qui passerait en silence serait scellé pour toujours sans avoir rien dit à personne.

Les membres du premier niveau.

Membre Valeur
format ev-native/1 obligatoire
source identifiant de votre source, <région>-src-<UUID version 7>, que nous vous communiquons obligatoire
collection identifiant de collection, <source>:c<numéro>; sans lui, la collection par défaut de la source (ou celle de la dernière version, pour un document connu) facultatif
metadata le descriptif, voir plus bas obligatoire
body_form blocks, seule forme servie en v1 obligatoire
text_blocks liste d'au moins un bloc obligatoire
files liste de fichiers; absente ou vide pour un article sans fichier facultatif
rights les droits au format ev-rights/1 (voir section 11) facultatif
cms_revision l'identifiant de la révision de l'article dans votre CMS; chaîne non blanche obligatoire

Le descriptif metadata.

Membre Valeur
document_type article (seul type en v1) obligatoire
source_reference l'identifiant que votre CMS donne à l'article, tel quel, sous la règle de référence convenue pour votre source (par exemple un préfixe si votre source réunit plusieurs éditions) obligatoire
version_label étiquette de version choisie par vous; jamais la révision du CMS facultatif
published_at, updated_at date de publication et date de mise à jour affichée au lecteur, voir plus bas facultatif
canonical_url adresse canonique facultatif
language étiquette BCP 47 facultatif
title, lede titre, chapeau facultatif
genres codes de genre IPTC (Analysis, Interview…); une valeur inconnue passe et est comptée facultatif
category chemin de catégorie, liste ordonnée de libellés facultatif
authors, places listes de chaînes facultatif
relations liens vers d'autres documents: types derived_from, corrects, translates, cites; cible: un document Eiger Vault, un couple (source, référence), une adresse ou une empreinte facultatif

Les champs que le vault écrit lui-même – le type de schéma, sa version, la collection résolue – ne se déclarent pas: les déclarer fait refuser le document.

Les blocs. Chaque bloc a un role – paragraph, subheading (intertitre), sidebar (encadré) ou caption (légende) – et un text non blanc. Un encadré porte une liste text_blocks d'au moins un bloc, qui ne contient pas d'encadré. Une légende peut désigner un fichier par son name dans file. Le texte est recopié sans aucune modification, balises comprises: le vault n'y lit aucun balisage, et <b> y compte pour quatre caractères.

Les fichiers. Chaque entrée de files a un name (unique, non réservé: c'est le nom de la partie qui porte les octets), un role (image, video ou page_pdf; jamais text) et un media_type (de la forme type/sous-type, en minuscules, sans paramètre). Les fichiers sont conservés dans l'ordre de la liste, octet pour octet. Le nom d'un fichier ne passe jamais au vault et ne figure dans aucune trace.

Les dates. published_at et updated_at s'écrivent soit comme un instant RFC 3339 avec son décalage, recopié tel que le CMS le tient (2026-10-05T08:30:00+02:00), soit comme un jour AAAA-MM-JJ, seulement quand rien d'autre n'est connu. Une date sans décalage n'est pas un instant. Ne convertissez pas le décalage: le vault le convertit en UTC. updated_at est la date de mise à jour affichée au lecteur: si votre site n'en affiche pas, omettez le membre, et n'y mettez jamais une date technique de modification. Une date absente reste absente; nous ne la complétons jamais.

La lecture du JSON. La partie JSON est de l'I-JSON strict (RFC 7493): UTF-8 valide, sans marque d'ordre des octets, sans membre répété, sans caractère de substitution isolé ni non-caractère, avec des nombres entiers seulement. Une chaîne blanche, null, une liste vide et un objet vide valent «absent»: le membre est omis, et un membre obligatoire blanc vaut absent. Aucun bloc de text_blocks et aucun fichier de files n'est retiré par cette règle.

La référence à la source. C'est l'identifiant que votre CMS donne à l'article, sans aucune normalisation, et elle ne change jamais pour un même article. Elle ne se tire pas d'une adresse. Elle suit la forme attendue que nous avons convenue pour votre source: si elle ne la suit pas, le document est refusé (native.reference-rule-mismatch) sans que le refus cite la référence.

La révision du CMS. Elle identifie, pour votre CMS, un état de l'article. Elle ne devient jamais l'étiquette de version. Elle sert à calculer la clé d'idempotence (section 6): il faut donc qu'une même révision produise toujours le même contenu, et qu'une modification visible produise une révision nouvelle.

5. La réponse

Un dépôt qui crée un document ou une version répond 201. Un dépôt qui ne crée rien – réessai reconnu, contenu inchangé – répond 200, avec le sceau de la version reconnue. Les deux réponses ont la même forme:

{
  "document": "ch-0192f3a4-5b6c-7d8e-9fa0-b1c2d3e4f5a6",
  "notices": [],
  "seal": {
    "depositor": "ch-dep-0192f3a4-5b6c-7d8e-9fa0-b1c2d3e4f5a7",
    "format": "ev-seal/1",
    "identity_level": "authenticated",
    "manifest_digest": {"sha-256": "…", "sha3-512": "…"},
    "origin_proofs_digest": {"sha-256": "…", "sha3-512": "…"},
    "received_at": "2026-10-05T06:31:12.123456Z"
  },
  "version_number": 1
}
Membre Valeur
document l'identifiant du document, <région>-<UUID version 7>: il ne dérive ni du contenu ni de votre référence, et n'est jamais réutilisé
version_number le numéro de version, à partir de 1
seal le sceau de version complet; ses octets sont les octets scellés. Il ne porte ni signature ni adresse
notices les indications, liste vide quand il n'y en a aucune

Le sceau rendu est recopié octet pour octet de ce que le vault a écrit; vous pouvez le garder comme reçu.

Les indications (au plus une fois chacune, sans jamais citer ce qui les a causées):

Valeur Sens
retry_recognized le dépôt est la reprise d'un dépôt déjà reçu: rien n'a été créé
content_unchanged le contenu est celui de la dernière version: rien n'a été créé
version_label_already_used une version a été créée sous une étiquette déjà employée
out_of_schema_field_ignored un champ du descriptif n'appartient pas au schéma et a été ignoré
supplementary_metadata_not_kept le descriptif complémentaire a été accepté, mais n'a été transmis à personne pour l'instant

6. L'idempotence

Vous n'envoyez pas de clé d'idempotence: l'interface la calcule, par une empreinte à clé (HMAC-SHA-256, sous votre clé) du triplet (source, référence à la source, révision du CMS). La clé n'est donc jamais lisible, et un réessai qui traverse un déploiement de notre côté présente la même clé. Le vault la garde 30 jours, pour votre déposant seulement.

Votre envoi Résultat
même document, même révision, même contenu: une reprise 200, le sceau du premier dépôt, retry_recognized
même document, même révision, contenu différent refus deposit.idempotency-key-conflict (409): une même révision ne peut pas porter deux contenus
même document, révision nouvelle, contenu identique à la dernière version 200, content_unchanged; pas de nouvelle version
même document, contenu changé 201, version suivante
même document, étiquette déjà employée, contenu changé 201, version suivante, avec version_label_already_used

Premier envoi, reprise, contenu inchangé, contenu modifié.

Passés les 30 jours, une clé est tenue pour neuve. Un contenu identique à celui de la dernière version rend alors content_unchanged plutôt que retry_recognized: il n'y a de toute façon pas de version en plus.

7. Les versions

Un document est identifié par le couple (source, référence à la source). Un dépôt dont le couple est connu vise le document existant. Les versions sont numérotées 1, 2, 3, sans trou, par document.

Ce qui fait une version. On compare le contenu que vous déclarez – le descriptif et les pièces, texte structuré compris – à celui de la dernière version. Les octets de l'enveloppe d'origine n'entrent pas dans la comparaison. S'ils diffèrent, cela seul ne crée pas de version: une page régénérée, ou un document natif dont un membre non traduit change, n'en crée pas. L'étiquette de version et la collection entrent au descriptif: les changer crée une version.

Ce qui ne change jamais. Le type d'un document: un dépôt qui le changerait est refusé (deposit.document-type-change, 409). Le déposant qui peut déposer une nouvelle version est tout déposant autorisé pour la source du document.

Le reclassement. Une nouvelle version peut changer de collection au sein de la source du document, en désignant la nouvelle collection.

La date de publication du document est celle que déclare sa première version.

8. Les retraits

Il n'existe pas d'opération de retrait dans le contrat de dépôt: ni une suppression, ni un statut «dépublié». Un article supprimé ou dépublié dans votre CMS reste dans notre registre, avec toutes ses versions. Pour retirer un contenu, l'éditeur en fait la demande à Eiger Vault; un opérateur prend alors une décision, qui est inscrite au registre pour toujours avec ses auteurs, son instant et son motif. Deux degrés existent: l'interdiction (le contenu n'est plus montré à personne, le sceau est conservé; la décision est réversible) et l'effacement (les clés de chiffrement des pièces et du descriptif sont détruites; il ne se défait pas). Cette procédure sera détaillée avant la mise en service.

Ce qui n'est jamais retiré, même après un effacement: l'identifiant, les empreintes, le sceau, et une pierre tombale qui dit qu'il y eut un document, quand, de la part de qui, et pourquoi il n'est plus là.

9. La reprise après une coupure

Si vous n'avez pas reçu de réponse – délai dépassé, connexion coupée, 503 –, l'issue du dépôt est inconnue. Renvoyez la même requête, avec la même révision du CMS: vous recevrez le sceau du premier dépôt (200, retry_recognized) s'il a eu lieu, ou le dépôt aura lieu (201). Un réessai ne crée jamais de version en double.

Reprise après une coupure: renvoyer la même requête, ou vérification groupée.

Si une série de dépôts a pu être perdue, par exemple après une panne chez vous ou chez nous, la vérification groupée vous dit ce que nous connaissons, sans rien nous envoyer:

POST /presence-checks
Authorization: Bearer evk_0123456789abcdef_<64 chiffres hexadécimaux>
Content-Type: application/json

{"references": [{"source": "ch-src-0192f3a4-5b6c-7d8e-9fa0-b1c2d3e4f5a6", "source_reference": "12345"},
                {"source": "ch-src-0192f3a4-5b6c-7d8e-9fa0-b1c2d3e4f5a6", "source_reference": "12346"}]}

Le corps est limité à 1000 références et à 4 Mio. La réponse 200 donne un résultat par couple soumis, dans l'ordre soumis:

{"results": [
  {"source": "ch-src-…", "source_reference": "12345", "state": "served",
   "collection": "ch-src-…:c1", "version_label": "édition du soir",
   "comparison_digest": {"sha-256": "…", "sha3-512": "…"}},
  {"source": "ch-src-…", "source_reference": "12346", "state": "absent"}
]}
État Sens
absent le couple est inconnu: envoyez le document
served le document a une version: il est déjà chez nous
out_of_scope votre clé n'est pas autorisée pour cette source, ou la source est inconnue; les autres références ne sont pas touchées

La réponse ne porte jamais de contenu, ni identifiant de document, ni numéro de version. Un redépôt périodique consiste à lister vos contenus récents (au départ les 48 dernières heures, toutes les heures), à les soumettre à la vérification groupée, puis à ne déposer que les absents.

Le mode redépôt existe dans le contrat de dépôt: un dépôt marqué ainsi ne crée jamais de version d'un document existant, et rend le sceau de sa dernière version. Son usage par l'interface native sera précisé: voir la section 11.

10. Les refus

Un refus a toujours la même forme, avec son statut HTTP:

{"code": "native.missing-required-field", "message": "<phrase fixe>", "correlation_id": "…"}

Le message est une phrase fixe qui dit où (le nom d'un membre, le rang d'un bloc ou d'un fichier) et quoi; il ne cite jamais l'entrée: ni valeur, ni nom de membre inconnu, ni référence, ni nom de fichier. Le code est la seule chose sur laquelle votre programme doit s'appuyer. Un refus n'écrit rien chez nous.

10.1 L'ordre

Un même envoi donne toujours le même refus, parce que les contrôles se font dans un ordre fixe, et le premier écart donne le refus. Il y a deux étages: l'interface native, qui contrôle avant tout appel au vault, puis le vault.

Les contrôles de l'interface native, puis ceux du vault.

Étage 1, l'interface native.

  1. avant de lire le document: une chaîne en forme de clé dans l'adresse, puis la taille, puis le codage de transfert des parties;
  2. la lecture I-JSON, puis la forme d'objet du document;
  3. format: absent ou différent de ev-native/1;
  4. les noms de membres, à tous les niveaux;
  5. les membres, dans l'ordre source, collection, metadata, body_form, text_blocks, files, rights, chacun à fond;
  6. l'enveloppe d'origine explicite et ses preuves, si elles sont là;
  7. les désignations: chaque nom de fichier, puis chaque légende;
  8. cms_revision;
  9. le rapprochement des parties de la requête avec ce que le document désigne; puis la source dans la configuration de l'interface, et la référence face à la règle de sa source.

L'emplacement exact du rapprochement des parties de la requête par rapport aux étapes 3 à 8 sera précisé.

Étage 2, le vault, selon le contrat de déposer v1:

  1. la clé;
  2. l'attribution à une source ou à une collection, et le périmètre: source ou collection non autorisée, désactivée, inconnue, non désignée, et, dans notre environnement d'essai seulement, source non admise;
  3. la taille du corps et de chaque pièce;
  4. le format de la requête, du descriptif, des pièces et du texte structuré;
  5. les preuves d'origine, s'il y en a;
  6. une enveloppe d'origine déjà liée à un autre document;
  7. sous le verrou du document, dans l'ordre: le périmètre est relu; puis la décision de la section 7: une clé d'idempotence déjà vue, un changement de type, la reconnaissance d'un réessai, d'un contenu inchangé, ou la création d'une version.

Un membre de premier niveau que le contrat de dépôt ne connaît pas, ou un corps qui n'est pas un objet, est refusé deposit.malformed-request.

10.2 Les codes de l'interface native

Cause Code Statut
la partie JSON n'est pas de l'I-JSON strict native.invalid-json 400
version de format absente ou non servie native.unsupported-format 400
membre que le contrat ne nomme pas native.unknown-member 400
membre obligatoire absent native.missing-required-field 400
forme du corps non servie native.unsupported-body-form 400
autre entorse: forme d'une valeur, rôle hors vocabulaire, fichier de rôle text ou original, date ou type de média mal écrits, nom de fichier en double ou réservé, légende vers un fichier absent de la liste native.invalid-native-document 400
enveloppe d'origine explicite sans preuve, ou preuve sans enveloppe native.unpaired-original-envelope 400
le document désigne une partie absente de la requête native.missing-form-part 400
la requête porte une partie que le document ne désigne pas, ou deux parties de même nom native.undesignated-form-part 400
codage de transfert d'une partie autre que 7bit, 8bit, binary native.unsupported-transfer-encoding 400
une chaîne en forme de clé dans l'adresse, la partie JSON ou le corps native.key-outside-header 400
une partie, la requête ou le corps d'une vérification groupée dépasse sa borne native.request-too-large 413
descriptif complémentaire qui n'est pas un objet I-JSON, ou trop grand native.invalid-supplementary-metadata 400
la référence ne suit pas la forme attendue de sa source native.reference-rule-mismatch 400
la source désignée n'est pas configurée dans l'interface native.unconfigured-source 403
autre chemin ou autre méthode not-found 404
vault injoignable, ou sa réponse illisible unavailable 503

Une clé écrite avec des échappements \u dans la partie JSON ne se découvre qu'à la lecture de celle-ci: native.invalid-json passe alors devant native.key-outside-header.

10.3 Les codes du vault, rendus tels quels

Cause Code Statut
clé inconnue, révoquée, absente, mal formée, ou d'un déposant désactivé deposit.unknown-or-revoked-key 401
requête mal formée: membre inconnu, corps qui n'est pas un objet… deposit.malformed-request 400
source désignée inconnue ou non autorisée, autorisation retirée, ou déposant autorisé pour aucune source deposit.source-not-authorized 403
la source est désactivée deposit.source-deactivated 403
la collection est désactivée deposit.collection-deactivated 403
(environnement d'essai) source non admise deposit.source-not-staging-admitted 403
collection inconnue, ou qui n'est pas celle de la source désignée deposit.unknown-collection 404
plusieurs sources autorisées, et aucune désignée deposit.source-not-designated 400
pièce ou corps trop volumineux (pièce > 16 Mio, corps > 24 Mio) deposit.part-or-body-too-large 413
enveloppe d'origine déjà liée à un autre document deposit.original-envelope-of-another-document 409
clé d'idempotence vue, avec un autre contenu ou pour un autre document deposit.idempotency-key-conflict 409
changement de type d'un document deposit.document-type-change 409
refus du format: JSON non conforme, nombre, date, empreinte, pièce ou référence manquante, type de média, texte structuré, descriptif, pièce, texte, preuve d'origine format.invalid-json, format.invalid-number, format.malformed-date, format.malformed-digest, format.missing-required-field, format.malformed-media-type, format.invalid-structured-text, format.invalid-text-part, format.invalid-metadata, format.invalid-part, format.incomplete-digest-only, format.unsupported-origin-proof-type, format.invalid-origin-proof 400
clés du vault pas encore déverrouillées, ou compartiment des pièces qui ne répond pas unavailable 503

Les codes propres à une relève (deposit.attested-fetch-missing, deposit.invalid-attested-fetch, deposit.domain-outside-source-domains, deposit.original-envelope-not-attested) ne concernent pas une poussée avec clé éditeur. Qu'un code soit refusé ou transitoire se lit à son statut: tout 4xx est définitif tant que la requête ne change pas; 503 se réessaie, avec la même requête.

11. Ce qui sera clarifié

Certains points du contrat seront précisés ou complétés. Nous vous les annoncerons, et un ajout ne changera pas ce que cette page décrit déjà.

12. Les vecteurs

Le contrat du document natif est livré avec des vecteurs de référence: des documents natifs admis, avec la requête de dépôt qu'ils produisent, et des documents refusés, avec leur code. Ils ne changent jamais et peuvent être rejoués contre votre propre code. Nous vous les remettons avec le contrat.