Méthode GET/timeline /{identifier}
Lire l'historique public d'un produit : ses vérifications et ses changements de propriétaire, à partir du numéro imprimé, de l'identifiant de jeton ou de l'empreinte de puce. Point d'entrée public.
Sur cette page
Vous lisez l'historique d'un seul produit. En quittant cette page, vous saurez récupérer la liste de ses vérifications et de ses changements de propriétaire, triée du plus récent au plus ancien, ainsi que la fiche du produit auquel cet historique appartient.
Adresse complète :
GET https://api.sealtrust.io/v1/timeline/{identifier}Le même point d'entrée répond aussi sans le préfixe /v1, à
https://api.sealtrust.io/timeline/{identifier}. Les deux adresses appellent le
même code. Utilisez la forme /v1 pour une nouvelle intégration.
#Autorisation
Aucune. Ce point d'entrée est public.
Vous n'avez pas besoin de clef d'API. Si vous en envoyez une dans l'en-tête
Authorization, nous la décodons comme un jeton de session de la console. Une
clef d'API n'est pas un jeton de session : la lecture échoue en silence, et
nous traitons l'appel comme un appel anonyme. Vous recevez donc la même réponse
avec ou sans clef d'API.
Un jeton de session de la console change en revanche la réponse. Nous le lisons
dans l'en-tête Authorization comme dans le cookie de session posé par la
console. Un seul compte reçoit les adresses e-mail des AUTRES en clair :
l'administrateur de la marque qui a émis ce produit. Un administrateur d'une
autre marque reçoit la projection anonymisée, exactement comme un visiteur. Un
superadmin SealTrust reçoit le clair sur tous les produits, parce que c'est le
compte d'exploitation.
Les mêmes comptes, et eux seuls, reçoivent les adresses de portefeuille dans
from_address et to_address.
Sur un appel anonyme, vous recevez une projection anonymisée :
- nous ne rendons pas les adresses de portefeuille,
from_addressetto_addressvalentnull; - nous masquons les adresses e-mail : la valeur rendue garde les deux premiers
caractères de la partie locale, puis
..., puis six caractères stables, par exemplema...3f9c1d.
#Plafond d'appels
600 appels par tranche de 60 secondes, comptés par adresse IP appelante. La fenêtre est fixe. Le plafond est large parce que des centaines de visiteurs d'un salon ou d'un réseau mobile partagent souvent une même adresse.
Les appels sur /v1/timeline/… et sur /timeline/… alimentent le même
compteur. Passer d'une forme à l'autre ne relève donc pas le plafond.
Chaque réponse porte X-RateLimit-Limit, X-RateLimit-Remaining et
X-RateLimit-Reset, qui décrivent ce plafond. Un dépassement renvoie 429,
avec ces trois en-têtes et un Retry-After en secondes.
#Paramètres de chemin et de requête
| Nom | Type | Obligatoire | Description |
|---|---|---|---|
identifier | string | oui | L'identifiant du produit. Trois formes sont acceptées, voir ci-dessous. |
lang | string | non | La langue du nom de catégorie : fr ou en. À défaut, le serveur lit l'en-tête Accept-Language, puis retombe sur en. |
Un seul paramètre de requête, lang, et il est optionnel. Il ne change qu'une
chose dans la réponse : la langue de category_name. Tout le reste est identique
quelle que soit sa valeur.
Le serveur choisit la langue dans cet ordre : le paramètre lang s'il vaut fr
ou en, sinon le premier tag reconnu de l'en-tête Accept-Language, sinon en.
Une valeur que le serveur ne reconnaît pas est ignorée sans erreur : vous
obtenez en, et non un 422.
#Les trois formes d'identifiant acceptées
Ce paramètre accepte trois formes. Vous envoyez celle que vous avez sous la main.
| Forme | À quoi elle ressemble | Où vous la trouvez |
|---|---|---|
| Numéro de série | 12 caractères, chiffres et lettres majuscules, dans un alphabet qui exclut I, L, O et U | Imprimé sur le produit, c'est ce que porte son QR |
| Identifiant de jeton | Une suite de chiffres, souvent très longue | Rendu par nos réponses dans le champ token_id |
| Empreinte de puce | 0x suivi de 64 caractères hexadécimaux | Rendue par nos réponses dans le champ uid_hash |
Nous reconnaissons le numéro de série quelle que soit la casse. Nous lisons les
caractères I et L comme un 1, et le caractère O comme un 0, pour
accepter un numéro recopié à la main depuis une étiquette. Nous ne rattrapons
pas le U : il ne fait partie ni de l'alphabet des numéros ni des caractères
traduits, et un identifiant qui en contient ne désigne aucun produit.
#En-têtes
Aucun en-tête n'est requis.
#Corps de la requête
Aucun. Cette requête n'a pas de corps.
#Requête d'exemple
Lecture de l'historique du produit dont le numéro imprimé est EXEMP1E00001.
curl -i https://api.sealtrust.io/v1/timeline/EXEMP1E00001import { SealTrustClient } from "@sealtrust-io/sdk";
// Ce point d'entrée est public et ignore la clef, mais le client du SDK
// refuse de se construire sans elle.
const sealtrust = new SealTrustClient({
apiKey: "st_live_0000000000000000000000000000000000000000000000",
});
const historique = await sealtrust.verify.timeline("EXEMP1E00001");
console.log(historique.product_name, historique.timeline.length);import requests
response = requests.get(
"https://api.sealtrust.io/v1/timeline/EXEMP1E00001",
timeout=30,
)
print(response.status_code)
print(response.json())#Réponse d'exemple
Code HTTP 200OK
Code HTTP 200.
Ce produit a été frappé, il n'a pas encore été réclamé par un client, et il a été scanné une fois par QR.
{
"token_id": "7719472615821079694904732333912527190217998977709370935963838933860875309329",
"serial": "H897RFWJ4972",
"product_name": "Sac modèle 1",
"brand_id": 42,
"brand_code": "7K3QXW9M2A",
"brand_name": "Exemple SAS",
"brand_hide_powered_by": false,
"brand_app_url": null,
"category_id": 7,
"category_name": "Maroquinerie",
"metadata_uri": "ipfs://exemple-de-contenu-non-reel",
"image_url": "https://exemple-sas.test/images/sac-modele-1.jpg",
"current_owner_email": null,
"current_owner_is_vault": true,
"identity_included": true,
"timeline": [
{
"type": "verify",
"token_id": "7719472615821079694904732333912527190217998977709370935963838933860875309329",
"uid_hash": "0x1111111111111111111111111111111111111111111111111111111111111111",
"contract_address": "0x2222222222222222222222222222222222222222",
"timestamp": "2026-08-18T14:02:11.482000Z",
"signature": "signature-exemple",
"is_valid": true,
"source": "qr",
"from_address": null,
"to_address": null,
"from_email": null,
"to_email": null,
"from_display": null,
"to_display": null,
"comment": "Scanned",
"event_id": null
},
{
"type": "transfer",
"token_id": "7719472615821079694904732333912527190217998977709370935963838933860875309329",
"uid_hash": "0x1111111111111111111111111111111111111111111111111111111111111111",
"contract_address": "0x2222222222222222222222222222222222222222",
"timestamp": "2026-08-12T09:30:00.000000Z",
"signature": null,
"is_valid": null,
"source": null,
"from_address": null,
"to_address": null,
"from_email": "Mint",
"to_email": null,
"from_display": "Mint",
"to_display": "Vault",
"from_role": "mint",
"to_role": "vault",
"comment": "Factory mint",
"event_id": null
}
],
"contract_address": "0x2222222222222222222222222222222222222222"
}La réponse compte quinze champs et rien d'autre. Vous recevez null pour les
champs sans valeur, ils ne disparaissent pas de la réponse.
| Champ | Type | Description |
|---|---|---|
token_id | string | L'identifiant du jeton sur la chaîne, rendu sous forme de texte. Vaut la chaîne vide quand le produit n'a pas encore d'identifiant de jeton. |
serial | string ou null | Le numéro de série imprimé sur le produit, quel que soit l'identifiant utilisé dans l'adresse (numéro de série, identifiant de jeton ou empreinte de puce). C'est lui qu'attend la revendication par numéro de série. null pour un produit qui n'en a pas. |
product_name | string | Le nom du produit, ou null. |
brand_id | integer | Déprécié : le numéro interne de la marque, gardé pendant la transition, ou null si le produit n'est rattaché à aucune. Lisez brand_code. |
brand_code | string | Le code public de la marque, dix caractères, ou null si le produit n'est rattaché à aucune. C'est l'identifiant à citer et à conserver. |
brand_name | string | Le nom de la marque, ou null si le produit n'est rattaché à aucune. |
brand_hide_powered_by | boolean | true quand la mention « Powered by SealTrust » doit disparaître. Deux conditions, et il faut les deux : l'offre de la marque comprend la marque blanche et la marque a choisi de masquer la mention dans sa console. Par défaut la mention reste affichée, y compris pour une marque qui vient de souscrire à l'offre Inside sans rien avoir décoché. |
brand_app_url | string ou null | L'adresse de l'application mobile de la marque, quand elle en a une. null sinon. Les pages publiques proposent la sienne quand elle est renseignée, la nôtre sinon. |
category_id | integer ou null | L'identifiant de la catégorie du produit. null quand aucune catégorie n'est rattachée. Ce champ a longtemps rendu null sur tous les produits, y compris ceux qui avaient bien une catégorie : il était déclaré dans la forme de la réponse et jamais rempli. Corrigé le 31 août 2026. |
category_name | string ou null | Le nom de la catégorie du produit, dans la langue de l'appelant : voir le paramètre lang plus haut. null quand aucune catégorie n'est rattachée. |
metadata_uri | string | L'adresse des métadonnées du produit, ou null. |
image_url | string | L'adresse de l'image du produit, ou null. C'est la copie allégée de la photo (WebP, 800 pixels au plus sur le grand côté, 100 Ko au plus, sans données EXIF) quand elle existe, sinon l'original. Une adresse d'un autre site est rendue telle quelle. |
current_owner_email | string | L'adresse e-mail du propriétaire actuel, masquée. null quand aucun propriétaire n'est connu. |
current_owner_is_vault | boolean | true quand le produit est encore détenu par le coffre de la marque, donc pas encore réclamé par un client. |
identity_included | boolean | true quand la pièce a son identité : revendication, transfert, revente. Une pièce créée sur l'offre Conformité + Identité la garde, même si sa marque passe ensuite sur l'offre Conformité. false pour une pièce sans identité : elle ne se revendique ni ne se transfère, et la page publique n'en propose rien. null quand la réponse ne le dit pas, ce qui ne vaut jamais un refus. |
timeline | object[] | Les événements, du plus récent au plus ancien. Voir le tableau ci-dessous. |
contract_address | string | L'adresse du contrat qui porte ce produit sur la chaîne, ou null. |
Le tableau timeline n'est pas paginé et n'a pas de taille maximale. Il
contient toutes les vérifications et tous les mouvements enregistrés pour ce
produit. Un produit très scanné rend donc une réponse volumineuse.
Dimensionnez votre lecture en conséquence.
#Un événement de la chronologie
Chaque entrée compte vingt-deux champs. Les champs qui n'ont pas de sens pour
le type d'événement valent null.
| Champ | Type | Description |
|---|---|---|
type | string | verify pour une vérification, transfer pour un mouvement de propriété, event pour une étape de la vie du produit. |
token_id | string | L'identifiant du jeton concerné. |
uid_hash | string | L'empreinte de puce concernée, 0x suivi de 64 caractères hexadécimaux. |
contract_address | string | L'adresse du contrat concerné. |
timestamp | string | Date et heure de l'événement, en temps universel, au format ISO 8601. |
signature | string | La signature enregistrée avec la vérification. null sur un mouvement de propriété. |
is_valid | boolean | Le résultat de la vérification. null sur un mouvement de propriété. |
source | string | D'où vient la vérification. Les valeurs écrites aujourd'hui sont qr, sdm-url et sdm-json. null sur un mouvement de propriété. |
from_address | string | Toujours null sur un appel public. |
to_address | string | Toujours null sur un appel public. |
from_email | string | L'adresse e-mail de la partie qui cède, masquée. Vaut Mint quand l'événement est la frappe du produit. |
to_email | string | L'adresse e-mail de la partie qui reçoit, masquée. |
from_display | string | Un libellé prêt à afficher pour la partie qui cède. Mint pour la frappe, Vault pour le coffre de la marque, sinon l'adresse e-mail masquée ou une adresse de portefeuille tronquée. |
to_display | string | Le même libellé, pour la partie qui reçoit. |
from_role | string ou null | mint quand la partie qui cède est la frappe, vault quand c'est le coffre de la marque, null sinon. Une page traduit ce rôle plutôt que from_display, écrit en anglais. |
to_role | string ou null | Le même rôle, pour la partie qui reçoit. |
comment | string | Une phrase courte en anglais qui résume l'événement : Scanned, Scanned by <e-mail masqué>, Transfer, Transferred from <e-mail masqué> to <e-mail masqué> ou Factory mint. |
event_id | integer | L'identifiant de l'étape de vie, sur une entrée de type event. null ailleurs. |
event_type | string | La nature de l'étape : quality_control, certification, warranty_activation, after_sale_service, et d'autres selon ce que la marque enregistre. Traduisez à partir de cette clé. null hors d'une entrée event. |
label | string | Le titre de l'étape, tel que la marque l'a écrit. Un repli lisible, pas une valeur à interpréter. null hors d'une entrée event. |
proof_level | string | Sur quoi repose cette étape. null quand la marque ne l'a pas renseigné. |
meta | object | Les détails propres à l'étape. Les adresses de portefeuille en sont retirées, et l'adresse de la personne qui a agi y est masquée. null quand il n'y a rien à dire. |
occurrences | integer | Le nombre de vérifications que cette entrée représente. Vaut 1 partout ailleurs. |
first_timestamp | string | La date de la plus ancienne vérification du groupe, quand occurrences dépasse 1. null sinon. |
#Erreurs
Le corps d'une réponse d'erreur porte un champ detail.
| Code | Condition | Que faire |
|---|---|---|
| 400 | L'identifiant envoyé n'a la forme d'aucune des trois formes acceptées : il contient autre chose que des chiffres et des lettres, ou il dépasse 64 caractères. detail vaut Invalid UID hash format (must be 0x + 64 hex characters). | Envoyez un numéro de série, un identifiant de jeton ou une empreinte de puce. Vérifiez qu'aucun espace ni caractère de ponctuation ne traîne. |
| 400 | L'identifiant commence par 0x et fait 66 caractères, mais il contient un caractère qui n'est pas hexadécimal. Même valeur de detail. | Une empreinte de puce n'accepte que les chiffres 0 à 9 et les lettres a à f. |
| 404 | L'identifiant est bien formé, mais aucun produit ne lui correspond, ou aucun événement n'a été enregistré pour lui. detail vaut No events found for this UID. | Vérifiez le numéro recopié. Un produit retiré du catalogue répond avec l'autre message 404, décrit à la ligne suivante. Un produit détruit interrogé par son numéro imprimé ou par son identifiant de jeton répond ici. |
| 404 | Des événements existent pour cet identifiant, mais aucun produit encore en catalogue ne s'y rattache. detail vaut Product not found for this UID. | Le produit a été retiré du catalogue. Son historique n'est plus servi. |
| 404 | L'appel arrive sur le nom de domaine vérifié d'une marque, et le produit appartient à une autre marque. detail vaut No events found for this UID. | Appelez cette adresse sur le domaine de la marque concernée, ou sur api.sealtrust.io. Le message est le même que pour un identifiant inconnu, pour ne pas confirmer qu'il existe. |
| 429 | Vous avez fait plus de 600 appels depuis votre adresse IP dans la fenêtre de 60 secondes en cours. detail vaut Rate limit exceeded: 600 requests per 60s. | Attendez le nombre de secondes indiqué par Retry-After, puis réessayez. |
| 500 | Une erreur inattendue s'est produite pendant le traitement de votre appel. detail vaut Internal Server Error. | Réessayez. Si l'erreur persiste, contactez le support en indiquant l'heure de l'appel. |
#Voir aussi
GET /resolve/{identifier}, lire en un appel tout ce qu'une page produit affiche.GET /products/{uid}/public, lire les informations publiques d'un produit.GET /p/{serial}, traduire le numéro de série imprimé en adresse de page consommateur.- Notions de base, distinguer modèle, lot et article avant de commander la moindre étiquette.
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.