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
| Nom | Type | Obligatoire | Description |
|---|---|---|---|
identifier | string | oui | L'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.
| Ordre | Forme | Reconnue à | Sensible à la casse |
|---|---|---|---|
| 1 | Empreinte de l'article | 0x suivi de 64 caractères hexadécimaux, soit 66 caractères | non |
| 2 | Identifiant du jeton | Toute valeur, comparée telle quelle à l'identifiant de jeton enregistré | oui |
| 3 | Numéro de série imprimé | 12 caractères de l'alphabet Crockford Base32, qui exclut les lettres I, L, O et U | non |
| 4 | Numéro de certificat | La valeur exacte du champ certificate_number, par exemple ST-CERT-000000000000 | oui |
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/0x0000000000000000000000000000000000000000000000000000000000000000const identifiant =
"0x0000000000000000000000000000000000000000000000000000000000000000";
const response = await fetch(
`https://api.sealtrust.io/v1/resolve/${encodeURIComponent(identifiant)}`,
);
console.log(response.status);
console.log(response.headers.get("X-RateLimit-Remaining") ?? "inconnu");
console.log(await response.json());import requests
from urllib.parse import quote
identifiant = "0x0000000000000000000000000000000000000000000000000000000000000000"
response = requests.get(
f"https://api.sealtrust.io/v1/resolve/{quote(identifiant, safe='')}",
timeout=30,
)
print(response.status_code)
print(response.headers.get("X-RateLimit-Remaining", "inconnu"))
print(response.json())#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.
{
"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.
{
"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
| Champ | Type | Description |
|---|---|---|
token_id | string ou null | L'identifiant du jeton, sous forme de chaîne. null tant que la frappe n'a pas été confirmée sur la chaîne. |
uid_hash | string ou null | L'empreinte de l'article, telle qu'elle est enregistrée. |
product_name | string ou null | Le nom de l'article. |
brand_name | string ou null | Le nom de la marque propriétaire. null si aucune marque n'est rattachée. |
category_name | string ou null | Le nom de la catégorie. null si aucune catégorie n'est rattachée. |
sku | null | Toujours null. Le champ figure dans la réponse et n'est jamais renseigné par ce point d'entrée. |
description | null | Toujours null. Même remarque que pour sku. |
metadata_uri | string ou null | L'adresse des métadonnées de l'article. |
created_at | string ou null | Date de création de l'enregistrement, au format ISO 8601 en temps universel. |
tx_hash | string ou null | La transaction de frappe. Elle ne change pas quand l'article est transféré, donc c'est le lien de preuve d'origine. |
contract_address | string ou null | L'adresse du contrat qui porte ce jeton. |
certificate | objet ou null | Le certificat en cours de validité. null si l'article n'en a aucun. |
passport | objet ou null | Le passeport publié, filtré au niveau consommateur. null si aucun passeport publié n'existe. |
media | tableau | Les médias de l'article. Tableau vide si aucun. |
events | tableau | L'historique public de l'article. Tableau vide si aucun. |
merkle_anchor | objet ou null | La preuve d'appartenance de l'article à un lot ancré sur Base. |
passport_anchor | objet ou null | L'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.
| Champ | Type | Description |
|---|---|---|
certificate_number | string | Le numéro du certificat. C'est aussi l'une des quatre formes d'identifiant acceptées par ce point d'entrée. |
status | string | Vaut 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_at | string | Date d'émission, au format ISO 8601. |
expires_at | string ou null | Date 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_name | string ou null | Le 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.
| Champ | Type | Description |
|---|---|---|
schema_version | string | La version du schéma de données du passeport. |
passport_version | integer | Le numéro de version du passeport, incrémenté à chaque publication. |
data | objet | Le 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_at | string ou null | Date de publication de cette version. |
data_hash | string ou null | L'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.
| Champ | Type | Description |
|---|---|---|
id | integer | L'identifiant du média. 0 pour l'image de couverture de dernier recours. |
file_name | string | Le nom du fichier. |
media_type | string | image, video, document ou 3d_model. |
url | string | L'adresse publique du fichier. Le serveur retire de la liste un média dont il ne peut pas construire l'adresse. |
alt_text | string ou null | Le 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.
| Champ | Type | Description |
|---|---|---|
id | integer | L'identifiant de l'événement. |
event_type | string | Le 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. |
description | string ou null | Le texte libre saisi par l'auteur de l'événement. |
occurred_at | string | Date de l'événement, au format ISO 8601. |
actor_name | string ou null | Le 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.
| Champ | Type | Description |
|---|---|---|
anchor_tx_hash | string | La transaction qui a inscrit la racine sur Base. |
root | string | La racine ancrée. |
leaf_index | integer | La position de la feuille de cet article, comptée à partir de 0. |
leaf | string | L'empreinte de la feuille de cet article. |
proof | tableau de string | Les 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.
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.
| Champ | Type | Description |
|---|---|---|
tx_hash | string | La transaction qui a inscrit la racine sur Base. |
basescan_url | string ou null | Le lien direct vers cette transaction sur l'explorateur de la chaîne. |
merkle_root | string ou null | La racine ancrée. |
anchored_at | string ou null | Date de l'ancrage, au format ISO 8601. |
passport_version | integer ou null | La version du passeport couverte par cet ancrage. |
data_hash_matches | boolean ou null | true 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
| Code | Condition | Que faire |
|---|---|---|
| 404 | Aucun 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. |
| 404 | Aucun identifiant n'a été fourni, l'appel s'arrête à /v1/resolve. Message Not Found. | Ajoutez l'identifiant dans le chemin. |
| 405 | Une 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. |
| 429 | Plus 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. |
| 500 | Erreur 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
GET /products/{uid}/public, lire les informations publiques d'un produit.GET /timeline/{identifier}, lire l'historique public d'un produit.GET /certificate/{identifier}, lire le certificat d'authenticité d'un article.GET /passport/{identifier}/proof, rassembler les preuves publiques du passeport d'un article.
Cette page vous a-t-elle été utile ?
Votre réponse ouvre un courriel pré-rempli dans votre messagerie, à destination de contact@sealtrust.io. Vous le relisez avant de l'envoyer.