Guide de l’éditeur
Publier vos produits
Publier sur prodid, c’est mettre deux fichiers sur le web et un enregistrement dans le DNS. Sur votre propre domaine, il n’y a ni inscription ni clé à garder : le domaine est l’identifiant, et vous modifiez ou retirez ce que vous publiez en modifiant les fichiers.
Comment allez-vous publier ?
L’identité et l’hébergement sont séparés. Votre identité est un domaine que vous contrôlez ; l’endroit où les documents sont servis est un détail que vous pouvez changer plus tard sans changer votre identité ni aucun identifiant que prodid a attribué à vos produits. Trois voies, qui produisent exactement les mêmes enregistrements.
Voie 1
Vous pouvez servir des fichiers sur votre propre domaine.
Servez le document de découverte sur le domaine et publiez un enregistrement TXT qui nomme le domaine lui-même.
- Identité
- le domaine
- Confiance
- verified
Voie 2
Vous avez un domaine mais ne pouvez pas y servir de fichiers.
Publiez sur un slug hébergé et faites pointer un enregistrement TXT de votre domaine vers cet hôte.
- Identité
- le domaine, prouvé par le DNS
- Confiance
- verified
Voie 3
Vous n’avez pas de domaine du tout.
Publiez sur un slug hébergé. Ajoutez un domaine plus tard sans rien perdre.
- Identité
- l’hôte du slug
- Confiance
- unverified
Les voies 2 et 3 utilisent le service de publication hébergé sur <slug>.prodid.org. Connectez-vous sur publish.prodid.org avec un lien envoyé par e-mail, créez un site, téléversez votre fichier et connectez un domaine si vous en avez un. Vous pouvez tout exporter ou supprimer le site à tout moment.
Voie 1
Publier sur votre propre domaine
Deux fichiers et un enregistrement DNS. Une plateforme qui publie pour des marchands fait la même chose sur chaque domaine marchand, si bien que chacun garde sa propre identité et son propre niveau de confiance.
Servir le document de découverte
Un fichier, à un chemin fixe, qui indique où sont vos flux produit :
https://acme.example/.well-known/prodid.json.well-known/prodid.jsonapplication/json{ "$type": "org.prodid.feed", "version": 1, "publisher": { "name": "Acme Tekstil", "contact": "data@acme.example" }, "license": "CC0-1.0", "feeds": ["https://acme.example/prodid/items.ndjson"] }L’exemple de spec/fixtures/v1/examples/prodid.json, rendu à partir du fichier lui-même. $typeRequis: oui- Toujours org.prodid.feed.
versionRequis: oui- 1. Un document à 0 est accepté par les explorations qui commencent avant le 2027-01-26 00:00 UTC et refusé ensuite, comme un négatif définitif.
publisher.nameRequis: oui- Le nom qu’une personne doit voir à côté de vos assertions.
publisher.contactRequis: non- Une adresse e-mail que quelqu’un lit vraiment. C’est là que l’index écrit quand votre source passe en lapse, et c’est le seul avertissement que vous recevrez.
licenseRequis: oui- Identifiant SPDX couvrant les informations de vos flux, par exemple CC0-1.0.
feedsRequis: oui- Jusqu’à 100 URL https, toutes sur le même hôte que ce document.
domainRequis: en délégation- Le domaine pour lequel ce document parle. Requis dès que le document est atteint via un enregistrement TXT qui nomme un autre hôte, et il doit alors être égal à ce domaine. Facultatif quand le domaine sert lui-même le document, et s’il est présent il doit quand même correspondre.
Servez-le en HTTPS sur le port par défaut, avec un certificat valide, en
application/json, ou toutapplication/…+json. Ne le redirigez pas vers un autre hôte : la seule redirection que suit le robot va deacme.exampleàwww.acme.example.Servir le flux
Un flux est du NDJSON : un objet JSON complet par ligne, UTF-8, séparé par des retours à la ligne. Pas de tableau autour, pas de virgules entre les lignes. Ainsi un flux de deux millions de produits se lit ligne à ligne, et une ligne défectueuse ne vous coûte que cette ligne, pas le reste.
prodid/items.ndjsonapplication/x-ndjson{"$type":"org.prodid.item","key":"TJ-2041-NVY-M","brand":"Acme","name":"Trail Jacket","lang":"en","group":"TJ-2041","variant":{"color":"navy","size":"M"},"identifiers":[{"scheme":"gtin","value":"09506000134352"},{"scheme":"mpn","value":"TJ-2041-NVY-M"}],"markets":["TR","DE"],"properties":{"material":"100% recycled polyester","madeIn":"TR","weight":{"value":480,"unit":"g"}},"media":[{"url":"https://acme.example/img/tj2041-navy.webp","license":"CC-BY-4.0","kind":"image"}]} {"$type":"org.prodid.item","key":"TJ-2041-NVY-L","brand":"Acme","name":"Trail Jacket","lang":"en","group":"TJ-2041","variant":{"color":"navy","size":"L"},"identifiers":[{"scheme":"gtin","value":"09506000134406"},{"scheme":"mpn","value":"TJ-2041-NVY-L"}],"markets":["TR","DE"],"properties":{"material":"100% recycled polyester","madeIn":"TR","weight":{"value":505,"unit":"g"}},"media":[{"url":"https://acme.example/img/tj2041-navy.webp","license":"CC-BY-4.0","kind":"image"}]} {"$type":"org.prodid.item","key":"TJ-2041-OLV-M","brand":"Acme","name":"Trail Jacket","lang":"en","group":"TJ-2041","variant":{"color":"olive","size":"M"},"identifiers":[{"scheme":"gtin","value":"09506000134413"},{"scheme":"mpn","value":"TJ-2041-OLV-M"}],"markets":["TR"],"properties":{"material":"100% recycled polyester","madeIn":"TR","weight":{"value":480,"unit":"g"}},"media":[{"url":"https://acme.example/img/tj2041-olive.webp","license":"CC-BY-4.0","kind":"image"}]}Trois variantes d’un produit, tirées de spec/fixtures/v1/examples/items.ndjson. Elles partagent un group, donc l’index en construit une famille à trois variantes. Les flux vivent sur le même hôte que le document qui les liste. Un document de découverte qui nomme un flux sur un autre hôte est rejeté entièrement, pas en partie : vous perdez toute la source, pas un flux, et cela compte comme un négatif définitif, donc cela déclenche l’horloge de lapse décrite plus bas. Servir le JSON depuis votre site et le NDJSON depuis un nom d’hôte CDN séparé est un souhait légitime, mais non pris en charge en v1. Mettez les deux sur un seul hôte.
La première de ces lignes, mise en forme pour la lecture :
un enregistrement org.prodid.item{ "$type": "org.prodid.item", "key": "TJ-2041-NVY-M", "brand": "Acme", "name": "Trail Jacket", "lang": "en", "group": "TJ-2041", "variant": { "color": "navy", "size": "M" }, "identifiers": [ { "scheme": "gtin", "value": "09506000134352" }, { "scheme": "mpn", "value": "TJ-2041-NVY-M" } ], "markets": ["TR", "DE"], "properties": { "material": "100% recycled polyester", "madeIn": "TR", "weight": { "value": 480, "unit": "g" } }, "media": [{ "url": "https://acme.example/img/tj2041-navy.webp", "license": "CC-BY-4.0", "kind": "image" }] }$typeRequis: oui- Toujours org.prodid.item.
keyRequis: oui- Votre nom stable pour cet enregistrement dans le flux. Publier à nouveau la même key remplace l’ancien.
brandRequis: oui- La marque telle qu’elle figure sur le produit. Elle est normalisée avant de délimiter un mpn, si bien que ARÇELİK et Arçelik sont une seule marque, pas deux.
nameRequis: oui- Le nom du produit, sans les axes de variante.
identifiersRequis: oui- Au moins un { scheme, value }, et au moins un que l’index peut stocker. Les valeurs n’ont pas d’espace aux extrémités et ne sont pas vides. Un gtin doit avoir 8, 12, 13 ou 14 chiffres avec une clé de contrôle correcte et ne pas être que des zéros ; un mpn exige une brand qui se normalise en quelque chose, car c’est le périmètre dans lequel il est unique ; un gtin à diffusion restreinte est conservé, mais seulement comme votre propre rcn. Un enregistrement sans rien de stockable n’identifie rien et est ignoré.
langRequis: non- Étiquette BCP 47 pour le texte lisible de cet enregistrement.
groupRequis: non- Votre propre clé de regroupement. Les items qui la partagent sont les variantes d’une même famille.
variantRequis: non- Les axes qui distinguent cet item dans le groupe : couleur, taille, capacité.
categoryRequis: non- Code de brique GS1 GPC, écrit gpc: suivi de huit chiffres.
marketsRequis: non- Codes ISO 3166-1 alpha-2 des marchés où cet item est vendu.
propertiesRequis: non- Chaînes, nombres, booléens, listes, ou { value, unit } pour une mesure.
mediaRequis: non- Jusqu’à 20 URL https, chacune avec un kind et une licence SPDX de la liste acceptée. Contrairement aux flux, elles peuvent être sur n’importe quel hôte.
Envoyez des items à plat. Vous ne construisez jamais vous-même de familles ni de variantes et n’envoyez jamais d’
opid: l’index déduit les premières degroupet attribue les seconds. Si vous produisez déjà un flux Google Merchant,groupest votreitem_group_id.Deux choses à savoir sur les codes-barres avant d’exporter. Un
gtincomposé uniquement de zéros n’est pas un code-barres mais un emplacement réservé de catalogue, et un enregistrement qui en porte un est rejeté comme une mauvaise clé de contrôle. Un numéro à diffusion restreinte, c’est-à-dire les plages réservées au magasin, est stocké et vous est attribué, mais il n’identifie rien au niveau mondial : il ne fusionne jamais votre produit avec celui d’un autre, et/01/ne le résout pas.Revendiquer l’identité dans le DNS
Une fois les fichiers en ligne, publiez un enregistrement qui nomme votre propre domaine comme hôte.
_prodid.acme.example TXT_prodid.acme.example. IN TXT "v=prodid0; host=acme.example"Il pointe exactement vers ce que la découverte aurait trouvé de toute façon : rien ne change pour vos données. Ce qui change, c’est ce que peut faire quelqu’un qui a seulement le droit d’ajouter des entrées à votre DNS. Sans enregistrement, un enregistrement ajouté par lui s’empare silencieusement de votre identité, car le DNS l’emporte sur le fichier. Avec votre enregistrement déjà en place, un second fait échouer la découverte avec plus d’un enregistrement : un lapse, visible, notifié et réversible.
Ne déléguez jamais
_prodidà un prestataire. Le droit d’écriture sur_prodid.<domain>est le contrôle de votre identité, exactement comme pour DKIM ou un challenge ACME DNS-01. Un domaine dont le DNS est partagé est une identité partagée. Donnez le flux à une agence, pas l’enregistrement.Mettez les fichiers en ligne avant l’enregistrement, pas après : un enregistrement qui pointe vers un document absent est une découverte échouée. Les valeurs sont lues sans tenir compte de la casse et un point final est accepté, donc
ACME.Example.etacme.examplesont le même hôte.Signaler le changement à l’index
Facultatif. Chaque domaine connu est réexploré selon un calendrier ; un ping ne fait qu’avancer la prochaine exploration.
demander à l’index de lancer la découverte maintenantcurl -X POST https://prodid.org/ping \ -H 'content-type: application/json' \ -d '{"domain": "acme.example"}' HTTP/1.1 202 Accepted Content-Type: application/json {"status":"accepted"}{"domains": [...]}en accepte jusqu’à 1000 à la fois : c’est ainsi qu’une plateforme présente chaque marchand qu’elle héberge. Il n’y a pas de clé à envoyer, car le ping ne donne à l’appelant aucune prise sur le contenu, et202est tout ce qu’il répond. Il ne dit pas si le domaine était connu, si quelque chose a été trouvé ni si quelqu’un d’autre l’a pingé il y a une minute. Un429signifie seulement que vous dépassez votre propre budget de requêtes, et les erreurs arrivent enapplication/problem+json. Ce qui est arrivé à votre domaine apparaît dans votre état de source, pas dans le ping.
Voie 2
Déléguer l’hébergement, garder l’identité
Beaucoup de fabricants possèdent un domaine sans pouvoir y déposer de fichier. Le flux est hébergé ailleurs et le domaine prouvé par le DNS : l’identité de la source reste le domaine et le niveau de confiance reste verified. Créez un site sur publish.prodid.org, saisissez votre domaine ; il affiche l’enregistrement exact à ajouter et le vérifie pour vous.
À quoi cela ressemble
Un catalogue téléversé sur un slug hébergé
, par exemple
acme.prodid.org, qui sert à la fois le document de découverte et le flux.Un enregistrement TXT sur le domaine que vous possédez
, qui nomme cet hôte. Un hôte, jamais une URL : un enregistrement ne peut donc jamais diriger le robot vers une adresse arbitraire.
_prodid.acme.example TXT_prodid.acme.example. IN TXT "v=prodid0; host=acme.prodid.org"Un document qui nomme votre domaine en retour.
Le DNS dit mon document est là-bas ; le document dit je parle pour ce domaine. Un document délégué sans
domainest rejeté : l’absence ne vaut jamais consentement, et c’est ce qui empêche quiconque de pointer un enregistrement vers le flux de quelqu’un d’autre.acme.prodid.org/.well-known/prodid.jsonapplication/json{ "$type": "org.prodid.feed", "version": 1, "domain": "acme.example", "publisher": { "name": "Acme Tekstil", "contact": "data@acme.example" }, "license": "CC0-1.0", "feeds": ["https://acme.prodid.org/feeds/items.ndjson"] }Tiré de spec/fixtures/v1/examples/prodid.delegated.json. La seule différence avec la voie 1 est le champ domain, et ici il est obligatoire. Créez votre site
L’éditeur hébergé n’écrit
domaindans le document qu’après avoir vu lui-même votre enregistrement TXT : il n’y a donc aucun moment où un document revendique un domaine qui ne l’a pas demandé.
Voie 3
Publier sans domaine
Créez votre site
Un producteur sans aucun domaine publie sur un slug hébergé,
<slug>.prodid.org, sous son propre nom. Réservez-en un sur publish.prodid.org. L’identité de la source est l’hôte du slug et le niveau de confianceunverified, car rien n’a été prouvé sur un domaine. Les enregistrements sont de vrais enregistrements : identifiants, résolution, attribution.La plupart des éditeurs dans cette situation tiennent déjà un fichier produit Google Merchant ; la correspondance entre un tel fichier et des enregistrements
org.prodid.itemest donc écrite dans la spécification, colonne par colonne, avec ce qu’elle écarte délibérément : prix, disponibilité, livraison et état appartiennent à un vendeur un jour donné, pas au produit. Importer un flux Merchant vaut la lecture avant d’exporter : publish.prodid.org convertit le CSV ou le TSV que vous téléversez avec exactement cette correspondance. Quatre points y décident de la forme de votre tableur :- Quatre colonnes deviennent des axes de variante :
color,size,materialetpattern, celles entre lesquelles un acheteur choisit au sein d’un mêmeitem_group_id.gender,age_group,size_typeetsize_systemrestent des propriétés ordinaires : elles décrivent tout le groupe, et en axes elles scinderaient un produit en plusieurs dès que quelqu’un écrirait l’une d’elles de deux façons. - Un attribut répété est séparé par des virgules ou à en-tête répété, par fichier, pas par cellule. Une cellule n’est découpée sur les virgules que si son en-tête n’apparaît qu’une fois dans tout le fichier ; répétez l’en-tête et chaque cellule en dessous est prise entière. C’est la seule façon de transporter une valeur contenant une virgule, et c’est une décision prise une fois pour le fichier. Cela vaut pour
additional_image_link,product_type,product_highlight,product_detailetcertification; tout le reste, y compris unedescriptionpleine de virgules, est pris entier dans tous les cas. - Un
product_detailn’écrase jamais rien. Son sous-attribut du milieu devient la clé de propriété, le nom de section est abandonné, et la première valeur d’une clé l’emporte. Un detail ultérieur avec la même clé, ou qui tombe sur une clé déjà produite par la correspondance, est abandonné et signalé avec sa ligne : une colonne detail ne peut jamais redéfinir en douce ce que disait le reste de la ligne. - Une licence média doit être déclarée. Le format Merchant n’a pas de colonne pour cela et rien n’est ingéré sans licence ; un import déclare donc un seul identifiant SPDX acceptable couvrant chaque image et chaque document du fichier. Votre propre colonne
licensele remplace ligne par ligne, et un fichier dont la licence déclarée n’est pas acceptable produit des enregistrements sans média plutôt que des enregistrements en échec.
Une ligne qui ne peut pas devenir un enregistrement (aucun identifiant utilisable, un GTIN dont la clé de contrôle est fausse) est ignorée et signalée par son numéro de ligne dans le tableur, l’en-tête comptant comme ligne 1 : une mauvaise ligne coûte cette ligne et pas l’import. Un fichier d’exemple pour vous comparer se trouve dans /spec/fixtures/merchant : un CSV, les enregistrements qu’il doit produire et la ligne qui doit être ignorée.
Ce qui compte pour une décision prise aujourd’hui, c’est la suite. Ajouter un enregistrement TXT sur un domaine que vous acquérez, nommant ce même hôte, transforme le slug en domaine à la prochaine exploration : même source, mêmes enregistrements, chaque
opidconservé, et la confiance passe deunverifiedàverified. Vos identifiantsskusont rattachés à la source elle-même plutôt qu’à son nom, ils survivent donc au changement. Sur publish.prodid.org, c’est le même champ domain que celui de la voie 2.- Quatre colonnes deviennent des axes de variante :
Vérifiez votre domaine
Publication terminée ? Saisissez votre domaine. L’index est invité à l’examiner maintenant, et vous arrivez sur sa page d’état en direct.
Avant la mise en ligne
Vérifier vos fichiers avant le ping
prodid-batch check valide votre document de découverte et chaque flux exactement comme l’index le ferait, sans jeton et sans rien demander à l’index. Il s’exécute depuis un clone du dépôt avec Bun 1.4.4. Donnez-lui les fichiers eux-mêmes : un chemin finissant par .json est lu comme un document, tout autre chemin comme un flux NDJSON.
git clone https://github.com/productdevbook/prodid
cd prodid
bun install --frozen-lockfile
curl -fsSo prodid.json https://acme.example/.well-known/prodid.json
curl -fsSo items.ndjson https://acme.example/prodid/items.ndjson
bun apps/batch/src/cli.ts check prodid.json items.ndjson
prodid.json: ok, 1 record
items.ndjson: ok, 3 recordsUn fichier sans problème affiche ok et le nombre d’enregistrements que l’index en tirerait. Sinon, il affiche une ligne par problème, sous la forme items.ndjson:12: message avec la ligne du flux concernée, et se termine avec un code non nul. Il lit les fichiers, pas votre DNS ni votre serveur : l’enregistrement TXT, le type de contenu et les en-têtes de cache de la liste ci-dessus restent à vérifier par vous.
Les règles de validation d’un enregistrement sont dans la spécification, les schémas sont servis sur /spec/schemas/v1, et /spec/fixtures/v1 contient des vecteurs lisibles par machine sur lesquels tester votre export avant que quiconque l’explore.
La spécification est publiée en anglais uniquement, et c’est le texte anglais qui fait foi.
État : Spec v1. L’index et l’éditeur hébergé tournent sur prodid.org. Tout le monde peut publier, sur son propre domaine ou en hébergé. Les formats de fichier de cette page sont ceux que lit l’index quand vous le pingez.
Référence
À savoir avant tout le reste, car c’est ce qui décide laquelle des trois voies ci-dessous est la vôtre. La découverte repart de zéro à chaque exploration, DNS d’abord, et rien du chemin vers vos données n’est mémorisé d’une exploration à l’autre.
D’abord
Un enregistrement DNS TXT sur
_prodid.acme.exampleS’il y a un enregistrement
v=prodid0, il est suivi exclusivement, même s’il nomme votre propre domaine. L’enregistrement nomme un hôte ; le document est toujourshttps://<host>/.well-known/prodid.json.Seulement s’il n’y a pas d’enregistrement
https://acme.example/.well-known/prodid.jsonLe document sur le domaine lui-même. Celui qui sert le domaine en HTTPS parle pour lui.
Le DNS passe en premier parce que c’est la preuve la plus forte : qui peut déposer un fichier sur un hôte n’est pas toujours le propriétaire du domaine, et le propriétaire doit pouvoir passer outre. C’est aussi pourquoi vous devriez écrire l’enregistrement même si vous servez le fichier vous-même ; voie 1, étape 3 explique ce que cela vous apporte.
Un seul enregistrement, et rien derrière. Plus d’un enregistrement v=prodid0 à ce nom est une erreur, pas un choix entre eux, et un seul enregistrement mal formé ou trop long est aussi une erreur. Il n’y a pas de repli : dès qu’un enregistrement existe, un enregistrement qui ne fonctionne pas met fin à la découverte au lieu d’envoyer le robot voir votre domaine. Si vous changez d’hôte, remplacez l’enregistrement ; n’en ajoutez jamais un second. La grammaire, avec vecteurs de test, est dans la spécification.
Ce qui compte comme une réponse. HTTP 200 après au plus une redirection, un type de contenu JSON, un corps valide selon le schéma du flux, et un domain qui nomme le domaine découvert. Un site qui répond aux chemins inconnus par une page HTML et un 200 ne répond pas, pas plus qu’un document servi en text/html.
La confiance est une propriété de la source, pas d’une assertion. Elle dit ce qui a été prouvé sur qui parle, et rien sur la justesse d’une information donnée. C’est à l’utilisateur des données de décider quoi en faire.
| Niveau | Signification |
|---|---|
| authoritative | Contrôle du domaine prouvé et propriété de la marque confirmée. |
| verified | Contrôle du domaine prouvé, par l’enregistrement TXT ou par le domaine qui sert le document. |
| unverified | Un slug hébergé sans domaine, un import de jeu de données ou une contribution de la communauté. |
Seule une source dont l’identité est un domaine peut être plus que unverified. verified vient de l’exploration. authoritative, non, car aucune exploration ne peut prouver à qui appartient une marque : l’opérateur de l’index l’accorde, et seulement sur preuve.
La preuve, c’est votre préfixe entreprise GS1.
Le préfixe entreprise GS1 des GTIN que votre source publie sous la marque doit être concédé à l’organisation qui contrôle votre domaine, comme le montre la recherche publique « Verified by GS1 ». L’URL de cette recherche est conservée avec l’attribution comme preuve.
Une attribution nomme une marque, et chacune peut être révoquée.
La marque est enregistrée sous sa forme normalisée. Une source peut détenir des attributions pour plusieurs marques, et l’opérateur révoque chacune séparément.
Une attribution ne survit pas à la preuve qui la fonde.
Votre source est
authoritativeexactement tant que le contrôle de son domaine est prouvé, qu’elle n’est pas lapsed et qu’elle détient au moins une attribution non révoquée. Perdez la preuve de domaine ou passez en lapse, et elle redescend aussitôt à ce que la preuve restante permet (verifiedpendant le lapse, moins si la preuve de domaine a aussi disparu) sans que personne n’intervienne. Elle redevientauthoritativedès que les trois conditions sont à nouveau réunies.Elle couvre la source, pas la marque.
La confiance reste une propriété de la source : une attribution pour une marque rend
authoritativechaque assertion de votre source, y compris celles sur des marques pour lesquelles elle n’a pas d’attribution.
Pour la demander, publiez d’abord sur votre propre domaine, puis faites la demande avec le formulaire, en indiquant votre domaine, la marque et une recherche Verified by GS1 pour l’un de ses GTIN. L’opérateur vérifie dans Verified by GS1 les GTIN que votre source publie sous cette marque. La demande est publique.
Publier un enregistrement avec une key existante remplace le précédent. Il n’y a pas de point d’API de mise à jour, car le flux est l’état.
Une key qui disparaît d’un flux récupéré en entier marque vos assertions à son sujet comme retirées. L’entité et son identifiant restent : les liens des autres vers elle continuent de fonctionner, tout comme les assertions des autres sources.
Quand une exploration lit un document qui ne nomme plus l’une de vos URL de flux, chaque enregistrement actif de ce flux est retiré aussitôt, comme si toutes ses keys avaient disparu, et l’URL n’est plus récupérée. Le délai de grâce de sept jours décrit plus bas ne s’applique pas : retirer une URL, c’est dire que le fichier n’existe plus, et l’index vous prend au mot. La lister à nouveau plus tard la fait revenir, relue depuis zéro. Servez le document avec un ETag si possible : un 304 signifie que la liste n’a pas changé, donc rien n’est mis hors service.
Une ligne de plus de 64 KiB, illisible ou invalide est ignorée et comptée. Comme la key d’une ligne ignorée est inconnue, un flux avec des lignes ignorées ne retire rien pendant sept jours. Ensuite, les keys absentes sont retirées comme d’habitude. Le compteur est public : un flux qui perd des lignes en silence ne passe pas inaperçu.
Une exploration ne réussit que si tout ce que liste le document a été lu : un document acceptable, et chaque flux qu’il contient répond 200 ou 304. Un flux qui expire ne vous fait pas perdre les autres (les flux qui ont répondu sont ingérés, et rien n’est retiré lors d’une exploration échouée), mais l’exploration compte comme une erreur de transport, et feeds_failed l’indique dans votre état. Un document parfaitement sain devant une URL de flux morte pour de bon passe donc en lapse sur la même horloge de sept jours qu’un domaine qui ne répond plus du tout, et perd le catalogue trente jours plus tard. Une URL de flux que vous ne servez plus doit être retirée du document, pas laissée à l’abandon.
Un négatif définitif, c’est-à-dire une découverte qui se termine sans document acceptable (ni enregistrement ni document ; un enregistrement mal formé, trop long ou en double ; ou un document qui n’est pas en 200, pas en JSON, pas valide, qui ne s’accorde plus sur domain ou qui liste un flux hors de son hôte), fait passer la source en lapse immédiatement. Une erreur de transport (expiration DNS, 5xx, 429, limite atteinte, sur le document ou sur un flux) ne change rien en soi, mais sept jours consécutifs où chaque tentative se termine ainsi vous font aussi passer en lapse : renvoyer des erreurs indéfiniment n’est pas un moyen de figer vos assertions.
Ce bénéfice du doute appartient à un hôte, pas à vous. Il ne s’applique qu’à un hôte où votre source a déjà été vérifiée. Sur un hôte où elle ne l’a jamais été (un nouveau slug, un hôte vers lequel vous venez de pointer _prodid, tout ce qui relève des voies 2 et 3), toute erreur, expiration et 5xx compris, est un négatif définitif et vous fait passer en lapse immédiatement. C’est voulu : sinon, une source vérifiée pourrait pointer son enregistrement vers un hôte qu’elle ne contrôle pas et déduire, selon que son lapse commence aujourd’hui ou dans une semaine, si cet hôte est en ligne. Changer d’hôte ne vous coûte rien pour autant. La première exploration réussie sur le nouvel hôte efface tout, et un lapse reste réversible pendant les 30 jours qui précèdent tout retrait.
Une source en lapse n’accepte plus de nouvelles assertions, est affichée comme lapsed aux lecteurs, et son publisher.contact est prévenu. Une exploration d’une source en lapse n’écrit rien tant qu’elle n’a pas entièrement réussi ; ce n’est qu’alors que le lapse est levé. Après 30 jours de lapse continu, vos enregistrements et leurs assertions actives sont retirés ensemble et restent dans l’historique. Ces 30 jours sont votre fenêtre de récupération ; c’est pourquoi l’adresse de contact doit être lue par quelqu’un.
Revenir ne demande aucun changement. Servez le même document et le même flux que d’habitude, et tout ce que vous publiez est de nouveau affirmé dès que la découverte réussit, qu’un octet ait bougé ou non : tant qu’il ne détient rien d’actif de vous, l’index renonce aux requêtes conditionnelles, si bien qu’un fichier inchangé n’est jamais une raison de rester retiré.
Chaque assertion qu’un lecteur voit porte seen_at : non pas la date de première publication de l’information où que ce soit, mais depuis quand cette source affirme cette valeur sans interruption. Il survit à chaque nouvelle exploration qui trouve la même valeur, c’est tout son intérêt. Il ne survit pas à un retrait. Laissez votre source en lapse plus de 30 jours et la date de chaque information repart du jour de votre retour, car pendant l’interruption personne de vérifiable ne la soutenait. Les lignes retirées restent, donc ?history=1 montre l’interruption au lieu de la cacher ; ce que voit un lecteur est dans la spécification.
/source/acme.example vous montre que quelque chose ne va pas : le résultat de la dernière exploration, le chemin pris par la découverte, le nombre d’enregistrements modifiés, de lignes ignorées, de flux illisibles, et si vous êtes en lapse et depuis quand. Elle ne rapporte que ce que publie votre domaine et quand l’index a regardé, jamais qui a demandé une exploration.
{
"$type": "org.prodid.source",
"version": 1,
"name": "acme.example",
"kind": "domain",
"trust": "verified",
"verified_by": "dns-txt",
"verified_at": "2026-09-14T09:12:03Z",
"lapsed": false,
"license": "CC0-1.0",
"last_crawl": {
"at": "2026-09-19T06:00:11Z",
"via": "dns-txt",
"document_url": "https://acme.prodid.org/.well-known/prodid.json",
"outcome": "success",
"http_status": 200,
"records_seen": 3,
"records_changed": 1,
"lines_skipped": 0,
"feeds_failed": 0,
"media_failed": 0
}
}Le type est déterminé en lisant les octets ; votre Content-Type n’est qu’un indice. Six types sont stockés et rien d’autre : JPEG, PNG, GIF, WebP et AVIF en image, PDF en document. Pas de SVG, pas de HTML, pas d’archives, pas de fichiers Office : une fiche technique part en PDF. Le type détecté et le kind de l’entrée doivent concorder, sinon l’entrée est ignorée.
| Quoi | Limite |
|---|---|
| Une image | 20 MiB |
| Un document | 50 MiB |
| Une récupération de média | 60 s |
| Entrées média dans un enregistrement | 20 |
La licence est vérifiée comme une chaîne avant toute récupération, et doit être exactement l’un de sept identifiants SPDX, ceux qui permettent à chacun de redistribuer le fichier tel quel, ce que fait l’index :
CC0-1.0 CC-BY-4.0 CC-BY-SA-4.0 CC-BY-ND-4.0
CC-BY-3.0 CC-BY-SA-3.0 CC-BY-ND-3.0Tout ici se fait par entrée. Une entrée refusée, trop grande ou injoignable ne coûte que cette entrée : votre enregistrement n’est jamais invalide à cause de ses médias, ses autres assertions tiennent, et aucun échec de média ne vous fait passer en lapse. Elle est comptée dans media_failed et retentée à la prochaine exploration de cet enregistrement. Une chose fonctionne à l’inverse des flux : la règle du même hôte ne s’applique pas ici, vos images peuvent donc être sur un CDN. Ce qui est ingéré et la liste des licences donnent les raisons.
Distributeurs, registres, jeux de données ouverts et contributeurs sont des sources comme les autres. Leurs assertions leur sont attribuées et n’écrasent jamais les vôtres ; en cas de désaccord, les deux assertions sont visibles côte à côte avec leurs sources.