Méthode GET/resolve/{identifier}

Lire en un seul appel tout ce qu'une page produit affiche : identité, certificat, passeport public, médias, historique et preuves d'ancrage. Point d'entrée public, sans clef d'API.

Sur cette page

Vous obtenez en un seul appel tout ce qu'une page produit affiche : l'identité de l'article, son certificat en cours, son passeport publié, ses médias, son historique et ses preuves d'ancrage sur la chaîne. Aucune clef d'API n'est demandée.

L'adresse complète est https://api.sealtrust.io/v1/resolve/{identifier}. La même route existe sans le préfixe /v1, et c'est la forme /v1 qui est recommandée pour une nouvelle intégration.

Un seul paramètre suffit : l'identifiant de l'article. Quatre formes sont acceptées, et vous n'avez pas à déclarer laquelle vous envoyez. Le serveur les essaie dans l'ordre.

#Autorisation

Aucune, point d'entrée public. Il n'attend ni clef d'API, ni cookie de session, ni en-tête Authorization. Un appel serveur à serveur est accepté.

#Plafond d'appels

60 appels par fenêtre de 60 secondes, comptés par adresse IP appelante. Le plafond est partagé par toutes les adresses qui commencent par /resolve, et il s'applique aussi bien à /resolve/{identifier} qu'à /v1/resolve/{identifier}. Le compteur est commun à toutes les valeurs d'identifiant : parcourir mille identifiants différents consomme mille appels du même budget.

Les réponses 200, 404, 405 et 429 portent les en-têtes X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset, ce dernier donnant l'heure de remise à zéro en secondes depuis le 1er janvier 1970. Une réponse 500 ne les porte pas. Lisez-les toujours avec une valeur de repli. Un dépassement renvoie 429 avec en plus Retry-After, en secondes.

Ce point d'entrée ne consomme aucun quota de votre offre.

Toute réponse porte l'en-tête Cache-Control: no-store, max-age=0. Ne mettez cette réponse dans aucun cache partagé. Pour réduire le nombre de vos appels, gardez le résultat dans votre propre cache applicatif, avec la durée de fraîcheur que votre usage tolère.

#Paramètres de chemin et de requête

NomTypeObligatoireDescription
identifierstringouiL'identifiant de l'article. Quatre formes acceptées, décrites ci-dessous. Le serveur retire les espaces de bord avant la recherche.

Ce point d'entrée n'a aucun paramètre de requête.

#Les quatre formes d'identifiant

Le serveur les essaie dans cet ordre et s'arrête à la première qui trouve un article.

OrdreFormeReconnue àSensible à la casse
1Empreinte de l'article0x suivi de 64 caractères hexadécimaux, soit 66 caractèresnon
2Identifiant du jetonToute valeur, comparée telle quelle à l'identifiant de jeton enregistréoui
3Numéro de série imprimé12 caractères de l'alphabet Crockford Base32, qui exclut les lettres I, L, O et Unon
4Numéro de certificatLa valeur exacte du champ certificate_number, par exemple ST-CERT-000000000000oui

Le numéro de série est celui que porte le QR code imprimé sur l'article. Le serveur le canonicalise avant la recherche : il ramène les lettres I et L au chiffre 1, et la lettre O au chiffre 0. Le serveur reconnaît donc quand même un numéro ressaisi à la main avec un I, un L ou un O à la place d'un 1 ou d'un 0.

Une forme qui ne trouve rien n'arrête pas la recherche. Une valeur de 66 caractères commençant par 0x qui ne correspond à aucune empreinte est ensuite essayée comme identifiant de jeton, puis comme numéro de série, puis comme numéro de certificat, avant le 404.

Seuls les articles encore au catalogue répondent. Le serveur traite comme introuvables un article détruit, un article remplacé par une frappe ultérieure et un article archivé.

Si plusieurs enregistrements correspondent à une empreinte, à un identifiant de jeton ou à un numéro de série, le serveur rend le plus récemment créé. Un numéro de certificat est unique, il désigne un seul article.

#Corps de la requête

Aucun. C'est une requête GET, tout passe par le chemin.

#Requête d'exemple

curl -i https://api.sealtrust.io/v1/resolve/0x0000000000000000000000000000000000000000000000000000000000000000

#Réponse d'exemple

Code HTTP 200OK

Code HTTP 200. Un article complet, avec certificat, passeport publié, un média, un événement et les deux preuves d'ancrage.

JSON
{
  "token_id": "1024",
  "uid_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "product_name": "Sac de voyage Exemple SAS",
  "brand_name": "Exemple SAS",
  "category_name": "Maroquinerie",
  "sku": null,
  "description": null,
  "metadata_uri": "ipfs://bafyexemple00000000000000000000000000000000000000000000000",
  "created_at": "2026-08-14T09:12:33.418000Z",
  "tx_hash": "0x4444444444444444444444444444444444444444444444444444444444444444",
  "contract_address": "0x0000000000000000000000000000000000000000",
  "certificate": {
    "certificate_number": "ST-CERT-000000000000",
    "status": "active",
    "issued_at": "2026-08-14T09:14:02.117043Z",
    "expires_at": null,
    "issuer_name": "Exemple SAS"
  },
  "passport": {
    "schema_version": "1.0",
    "passport_version": 3,
    "data": {
      "product_identity": {
        "gtin": "03701234567890",
        "model": "Sac de voyage",
        "brand": "Exemple SAS",
        "made_in": "FR",
        "production_facility": "Atelier Exemple SAS"
      },
      "materials": {
        "primary": {
          "name": "Full grain leather",
          "percentage": 70,
          "origin": "IT"
        },
        "certified_organic": false
      },
      "environmental_impact": {
        "carbon_footprint_kg_co2e": 18.7,
        "water_usage_liters": 2340,
        "energy_consumption_kwh": 45.2,
        "transport_distance_km": 850,
        "transport_mode": "road"
      },
      "circularity": {
        "recyclability_percentage": 62,
        "recycled_content_percentage": 0,
        "repairability_index": 7.8,
        "expected_lifetime_years": 15,
        "disassembly_instructions_url": "",
        "take_back_program": true
      },
      "compliance": {
        "eu_espr": true,
        "reach": true
      }
    },
    "published_at": "2026-08-18T07:03:11.902000Z",
    "data_hash": "0000000000000000000000000000000000000000000000000000000000000000"
  },
  "media": [
    {
      "id": 4821,
      "file_name": "sac-face.jpg",
      "media_type": "image",
      "url": "https://cdn.example.com/exemple-sas/sac-face.jpg",
      "alt_text": "Sac de voyage vu de face"
    }
  ],
  "events": [
    {
      "id": 9137,
      "event_type": "warranty_activation",
      "description": "Garantie activée à l'achat en boutique.",
      "occurred_at": "2026-08-19T14:32:07.481920Z",
      "actor_name": "Boutique Exemple SAS Lyon"
    }
  ],
  "merkle_anchor": {
    "anchor_tx_hash": "0x1111111111111111111111111111111111111111111111111111111111111111",
    "root": "0x2222222222222222222222222222222222222222222222222222222222222222",
    "leaf_index": 17,
    "leaf": "0x3333333333333333333333333333333333333333333333333333333333333333",
    "proof": [
      "0x5555555555555555555555555555555555555555555555555555555555555555",
      "0x6666666666666666666666666666666666666666666666666666666666666666"
    ]
  },
  "passport_anchor": {
    "tx_hash": "0x7777777777777777777777777777777777777777777777777777777777777777",
    "basescan_url": "https://basescan.org/tx/0x7777777777777777777777777777777777777777777777777777777777777777",
    "merkle_root": "0x2222222222222222222222222222222222222222222222222222222222222222",
    "anchored_at": "2026-08-18T07:05:44.220118Z",
    "passport_version": 3,
    "data_hash_matches": true
  }
}

Code HTTP 200 également pour un article minimal. Un article sans certificat en cours, sans passeport publié, sans média, sans historique et dont le lot n'a pas été ancré rend la même structure avec des valeurs vides.

JSON
{
  "token_id": null,
  "uid_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "product_name": "Sac de voyage Exemple SAS",
  "brand_name": "Exemple SAS",
  "category_name": null,
  "sku": null,
  "description": null,
  "metadata_uri": "ipfs://bafyexemple00000000000000000000000000000000000000000000000",
  "created_at": "2026-08-14T09:12:33.418000Z",
  "tx_hash": null,
  "contract_address": "0x0000000000000000000000000000000000000000",
  "certificate": null,
  "passport": null,
  "media": [],
  "events": [],
  "merkle_anchor": null,
  "passport_anchor": null
}

#Les champs de premier niveau

ChampTypeDescription
token_idstring ou nullL'identifiant du jeton, sous forme de chaîne. null tant que la frappe n'a pas été confirmée sur la chaîne.
uid_hashstring ou nullL'empreinte de l'article, telle qu'elle est enregistrée.
product_namestring ou nullLe nom de l'article.
brand_namestring ou nullLe nom de la marque propriétaire. null si aucune marque n'est rattachée.
category_namestring ou nullLe nom de la catégorie. null si aucune catégorie n'est rattachée.
skunullToujours null. Le champ figure dans la réponse et n'est jamais renseigné par ce point d'entrée.
descriptionnullToujours null. Même remarque que pour sku.
metadata_uristring ou nullL'adresse des métadonnées de l'article.
created_atstring ou nullDate de création de l'enregistrement, au format ISO 8601 en temps universel.
tx_hashstring ou nullLa transaction de frappe. Elle ne change pas quand l'article est transféré, donc c'est le lien de preuve d'origine.
contract_addressstring ou nullL'adresse du contrat qui porte ce jeton.
certificateobjet ou nullLe certificat en cours de validité. null si l'article n'en a aucun.
passportobjet ou nullLe passeport publié, filtré au niveau consommateur. null si aucun passeport publié n'existe.
mediatableauLes médias de l'article. Tableau vide si aucun.
eventstableauL'historique public de l'article. Tableau vide si aucun.
merkle_anchorobjet ou nullLa preuve d'appartenance de l'article à un lot ancré sur Base.
passport_anchorobjet ou nullL'ancrage du contenu du passeport.

#certificate

Le serveur rend le certificat le plus récemment émis parmi ceux qui sont encore actifs à l'instant de l'appel. Il écarte un certificat révoqué, et il écarte un certificat dont la date d'expiration est passée.

ChampTypeDescription
certificate_numberstringLe numéro du certificat. C'est aussi l'une des quatre formes d'identifiant acceptées par ce point d'entrée.
statusstringVaut toujours active sur ce point d'entrée. Un certificat révoqué ou périmé n'est pas rendu ici, le champ certificate vaut alors null. Pour lire l'état revoked ou expired, appelez GET /v1/certificate/{identifier}.
issued_atstringDate d'émission, au format ISO 8601.
expires_atstring ou nullDate de fin de validité. Aucun certificat émis par la plateforme n'en porte aujourd'hui, la valeur est toujours null. Ne construisez pas votre intégration sur une date de fin.
issuer_namestring ou nullLe nom de la marque qui a émis le certificat. Le serveur calcule ce champ à la lecture. null quand le certificat n'est rattaché à aucune marque.

#passport

Le passeport rendu est celui qui porte le numéro de version le plus élevé parmi les versions publiées. Le serveur cherche d'abord un passeport rattaché au modèle de l'article, puis un passeport rattaché à l'article lui-même. Le serveur ne rend ici ni les brouillons, ni les passeports réservés à la marque.

ChampTypeDescription
schema_versionstringLa version du schéma de données du passeport.
passport_versionintegerLe numéro de version du passeport, incrémenté à chaque publication.
dataobjetLe contenu du passeport, filtré au niveau consommateur. Sa structure dépend de la catégorie de produit et de ce que la marque a rempli.
published_atstring ou nullDate de publication de cette version.
data_hashstring ou nullL'empreinte du contenu, 64 caractères hexadécimaux, sans préfixe 0x. Cette empreinte entre dans la feuille de l'arbre dont la racine est inscrite sur la chaîne. Le champ passport_anchor.merkle_root rend cette racine.

Le filtrage retient les sections destinées au public et au client final, quand le passeport les contient : identité du produit, conformité ESPR, conformité REACH, marquage CE, étiquettes, spécification de batterie, circularité, impact environnemental, durabilité, efficacité énergétique, empreinte carbone, matières principales et mention de certification biologique.

Le serveur retire tout le reste avant l'envoi, y compris à l'intérieur d'une section partiellement retenue. Dans l'exemple ci-dessus, la section materials du passeport complet décrit aussi la doublure et la quincaillerie : ces deux entrées ne sortent pas par ce point d'entrée.

Une marque peut définir ses propres règles d'accès pour ses groupes de produits. Ces règles remplacent alors entièrement le découpage par défaut décrit ci-dessus, y compris pour le niveau public : une règle publique posée sur la nomenclature de fabrication la fait sortir par ce point d'entrée.

#media

Jusqu'à 20 entrées, dans l'ordre d'affichage défini par la marque.

Le serveur prend d'abord les médias rattachés à l'article. S'il n'y en a aucun, il prend ceux du modèle. En dernier recours, il rend l'image de couverture du modèle, seule, avec l'identifiant 0. Cette valeur 0 signale une entrée fabriquée pour l'occasion, sans enregistrement propre.

ChampTypeDescription
idintegerL'identifiant du média. 0 pour l'image de couverture de dernier recours.
file_namestringLe nom du fichier.
media_typestringimage, video, document ou 3d_model.
urlstringL'adresse publique du fichier. Le serveur retire de la liste un média dont il ne peut pas construire l'adresse.
alt_textstring ou nullLe texte alternatif saisi par la marque.

#events

Jusqu'à 20 entrées, de la plus récente à la plus ancienne. La liste est tirée des 60 derniers événements enregistrés, puis nettoyée.

Le serveur retire deux familles d'événements. Les étapes de rachat qui n'ont rien changé à l'objet : proposition, refus, expiration, accord non réglé. Et les transferts de propriété, qui ne figurent pas dans cette liste.

ChampTypeDescription
idintegerL'identifiant de l'événement.
event_typestringLe type d'événement. Valeurs possibles : repair, warranty_activation, warranty_extension, resale, return, inspection, recall, end_of_life, custom, quality_control, reconditioning, distribution, after_sale_service, maintenance, certification, recycling, donation, destruction.
descriptionstring ou nullLe texte libre saisi par l'auteur de l'événement.
occurred_atstringDate de l'événement, au format ISO 8601.
actor_namestring ou nullLe nom de l'auteur de l'événement. Vaut null dès que la valeur enregistrée contient un @. Ce point d'entrée est entièrement public et accepte le numéro de série imprimé sur l'étiquette : tenir l'objet ne doit pas donner l'adresse e-mail de son propriétaire, et une adresse tronquée se devinerait. Un nom choisi par l'acteur, lui, reste affiché.

#merkle_anchor

Présent seulement si trois conditions sont réunies : l'article appartient à un lot dont la racine a été ancrée sur Base, l'article porte un identifiant de jeton, et la racine recalculée aujourd'hui est identique à la racine ancrée. Si le lot a changé depuis l'ancrage, la preuve serait invérifiable sur la chaîne et le champ vaut null. Le serveur ne rend jamais une preuve trompeuse.

ChampTypeDescription
anchor_tx_hashstringLa transaction qui a inscrit la racine sur Base.
rootstringLa racine ancrée.
leaf_indexintegerLa position de la feuille de cet article, comptée à partir de 0.
leafstringL'empreinte de la feuille de cet article.
prooftableau de stringLes empreintes sœurs, de bas en haut, qui permettent de recalculer la racine à partir de la feuille.

Vous pouvez vérifier cette preuve vous-même, sans nous faire confiance. La recomposition part de leaf, applique les entrées de proof dans l'ordre, et doit aboutir à root.

L'empreinte utilisée est keccak256. La convention de couple est celle d'OpenZeppelin : à chaque étage, vous concaténez les 32 octets de la valeur courante et les 32 octets de l'entrée de proof dans l'ordre croissant, puis vous appliquez keccak256 au résultat.

Recomposer la racine
from eth_utils import keccak  # pip install eth-utils

root = "0x2222222222222222222222222222222222222222222222222222222222222222"
leaf = "0x3333333333333333333333333333333333333333333333333333333333333333"
proof = [
    "0x5555555555555555555555555555555555555555555555555555555555555555",
    "0x6666666666666666666666666666666666666666666666666666666666666666",
]

node = bytes.fromhex(leaf[2:])
for entree in proof:
    voisin = bytes.fromhex(entree[2:])
    node = keccak(node + voisin) if node < voisin else keccak(voisin + node)

print("0x" + node.hex() == root)

Les valeurs ci-dessus sont inventées, donc ce programme affiche False. Remplacez-les par celles d'une réponse réelle et il affiche True.

#passport_anchor

Présent seulement si cette version exacte du passeport a été ancrée et si la transaction d'ancrage existe. Ce champ date le contenu du passeport. Le champ merkle_anchor ci-dessus date l'article. Les deux restent séparés parce qu'ils ne prouvent pas la même chose.

ChampTypeDescription
tx_hashstringLa transaction qui a inscrit la racine sur Base.
basescan_urlstring ou nullLe lien direct vers cette transaction sur l'explorateur de la chaîne.
merkle_rootstring ou nullLa racine ancrée.
anchored_atstring ou nullDate de l'ancrage, au format ISO 8601.
passport_versioninteger ou nullLa version du passeport couverte par cet ancrage.
data_hash_matchesboolean ou nulltrue quand le contenu stocké aujourd'hui correspond à ce qui a été ancré. false signale que le passeport a changé depuis.

Aucune preuve d'inclusion n'accompagne l'ancrage du passeport. Pour vérifier l'appartenance vous-même, appelez GET /v1/passport/{identifier}/proof, qui rend la feuille, sa position et les empreintes sœurs.

#Erreurs

CodeConditionQue faire
404Aucun article au catalogue ne correspond à cet identifiant, dans aucune des quatre formes. Message Product not found. Un article détruit, remplacé ou archivé donne la même réponse.Vérifiez la valeur envoyée. Si l'article a été détruit, remplacé ou retiré du catalogue, ce code est définitif.
404Aucun identifiant n'a été fourni, l'appel s'arrête à /v1/resolve. Message Not Found.Ajoutez l'identifiant dans le chemin.
405Une méthode autre que GET a été envoyée sur ce chemin. Message Method Not Allowed. La réponse porte l'en-tête Allow: GET.Ce point d'entrée ne répond qu'en GET.
429Plus de 60 appels en 60 secondes depuis la même adresse IP. Message Rate limit exceeded: 60 requests per 60s.Attendez le nombre de secondes indiqué par Retry-After. Répartissez vos appels dans le temps.
500Erreur inattendue du serveur. Corps figé {"detail": "Internal Server Error"}.Réessayez. L'en-tête X-Request-Id identifie l'appel, transmettez-le nous s'il se répète.

Une valeur qui ne ressemble à aucune des quatre formes attendues reçoit un 404.

#Voir aussi

Votre réponse ouvre un courriel pré-rempli dans votre messagerie, à destination de contact@sealtrust.io. Vous le relisez avant de l'envoyer.

Proposer une correctionSignaler un problème