Method GET/timeline /{identifier}
Read the public history of a product: its verifications and its changes of ownership, from the printed number, the token identifier or the chip hash. Public endpoint.
On this page
You read the history of a single product. By the end of this page, you will know how to retrieve the list of its verifications and its changes of ownership, sorted from the most recent to the oldest, along with the record of the product this history belongs to.
Full address:
GET https://api.sealtrust.io/v1/timeline/{identifier}The same endpoint also answers without the /v1 prefix, at
https://api.sealtrust.io/timeline/{identifier}. Both addresses call the same
code. Use the /v1 form for a new integration.
#
None. This endpoint is public.
You do not need an API key. If you send one in the Authorization header, we
decode it as a console session token. An API key is not a session token: the
read fails silently, and we treat the call as an anonymous call. You therefore
receive the same response with or without an API key.
A console session token, on the other hand, changes the response. We read it
from the Authorization header as well as from the session cookie set by the
console. The brand administrator and the product's current owner then receive
the email addresses in the clear. The administrator additionally receives the
wallet addresses in from_address and to_address.
On an anonymous call, you receive an anonymized projection:
- we do not return the wallet addresses,
from_addressandto_addressequalnull; - we mask the email addresses: the value returned keeps the first two characters
of the local part, then
..., then six stable characters, for examplema...3f9c1d.
#Rate limit
30 calls per 60 second window, counted per calling IP address. The window is fixed.
Calls on /v1/timeline/… and on /timeline/… feed the same counter. Switching
from one form to the other therefore does not raise the limit.
Going over returns 429, with a body that restates the limit. That response
carries no Retry-After. It does carry x-ratelimit-limit,
x-ratelimit-remaining and x-ratelimit-reset, whose values do not describe
this endpoint's limit.
The 200 responses carry these same three headers, with the same values. Do not use these headers here to set your pace. On a 429, wait for the end of the current window, that is, at most 60 seconds.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | yes | The product's identifier. Three forms are accepted, see below. |
This endpoint has no query parameter.
#The three accepted forms of identifier
This parameter accepts three forms. You send the one you have at hand.
| Form | What it looks like | Where you find it |
|---|---|---|
| Serial number | 12 characters, digits and capital letters, in an alphabet that excludes I, L, O and U | Printed on the product, it is what its QR carries |
| Token identifier | A string of digits, often very long | Returned by our responses in the token_id field |
| Chip hash | 0x followed by 64 hexadecimal characters | Returned by our responses in the uid_hash field |
We recognize the serial number whatever the case. We read the characters I and
L as a 1, and the character O as a 0, in order to accept a number copied
by hand from a label. We do not recover the U: it belongs neither to the
alphabet of serial numbers nor to the translated characters, and an identifier
that contains one designates no product.
#Headers
No header is required.
#Request body
None. This request has no body.
#Example request
Reading the history of the product whose printed number is EXEMP1E00001.
curl -i https://api.sealtrust.io/v1/timeline/EXEMP1E00001import { SealTrustClient } from "@sealtrust-io/sdk";
// This endpoint is public and ignores the key, but the SDK client
// refuses to be constructed without it.
const sealtrust = new SealTrustClient({
apiKey: "st_test_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())#Example response
HTTP code 200.
This product has been minted, it has not been claimed by a customer yet, and it has been scanned once by QR.
{
"token_id": "7719472615821079694904732333912527190217998977709370935963838933860875309329",
"product_name": "Sac modèle 1",
"brand_id": 42,
"brand_name": "Exemple SAS",
"category_id": null,
"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,
"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",
"comment": "Factory mint",
"event_id": null
}
],
"contract_address": "0x2222222222222222222222222222222222222222"
}The response counts twelve fields and nothing else. You receive null for the
fields without a value, they do not disappear from the response.
| Field | Type | Description |
|---|---|---|
token_id | string | The identifier of the token on the chain, returned as text. Equals the empty string when the product does not have a token identifier yet. |
product_name | string | The product's name, or null. |
brand_id | integer | The number of the brand the product belongs to, or null if the product is attached to none. |
brand_name | string | The brand's name, or null if the product is attached to none. |
category_id | integer | Always null on this endpoint. The field is declared in the shape of the response, and this endpoint never fills it. |
category_name | string | The name of the product's category, or null. |
metadata_uri | string | The address of the product's metadata, or null. |
image_url | string | The address of the product's image, or null. |
current_owner_email | string | The email address of the current owner, masked. null when no owner is known. |
current_owner_is_vault | boolean | true when the product is still held by the brand's vault, and therefore not yet claimed by a customer. |
timeline | object[] | The events, from the most recent to the oldest. See the table below. |
contract_address | string | The address of the contract that carries this product on the chain, or null. |
The timeline array is not paginated and has no maximum size. It contains every
verification and every movement recorded for this product. A heavily scanned
product therefore returns a large response. Size your read accordingly.
#An event of the timeline
Each entry counts sixteen fields. The fields that have no meaning for the type of
event equal null.
| Field | Type | Description |
|---|---|---|
type | string | verify for a verification, transfer for a change of ownership. These are the only two values. |
token_id | string | The identifier of the token concerned. |
uid_hash | string | The chip hash concerned, 0x followed by 64 hexadecimal characters. |
contract_address | string | The address of the contract concerned. |
timestamp | string | Date and time of the event, in universal time, in the format ISO 8601. |
signature | string | The signature recorded with the verification. null on a change of ownership. |
is_valid | boolean | The result of the verification. null on a change of ownership. |
source | string | Where the verification comes from. The values written today are qr, sdm-url and sdm-json. null on a change of ownership. |
from_address | string | Always null on a public call. |
to_address | string | Always null on a public call. |
from_email | string | The email address of the party giving up ownership, masked. Equals Mint when the event is the minting of the product. |
to_email | string | The email address of the receiving party, masked. |
from_display | string | A display-ready label for the party giving up ownership. Mint for the minting, Vault for the brand's vault, otherwise the masked email address or a truncated wallet address. |
to_display | string | The same label, for the receiving party. |
comment | string | A short sentence in English that sums up the event: Scanned, Scanned by <masked email>, Transfer, Transferred from <masked email> to <masked email> or Factory mint. |
event_id | integer | Always null on this endpoint. The field is declared in the shape of the response, and this endpoint never fills it. |
#Errors
The body of an error response carries a detail field.
| Code | Condition | What to do |
|---|---|---|
| 400 | The identifier sent has the shape of none of the three accepted forms: it contains something other than digits and letters, or it exceeds 64 characters. detail equals Invalid UID hash format (must be 0x + 64 hex characters). | Send a serial number, a token identifier or a chip hash. Check that no space or punctuation character is left over. |
| 400 | The identifier starts with 0x and is 66 characters long, but it contains a character that is not hexadecimal. Same value of detail. | A chip hash accepts only the digits 0 to 9 and the letters a to f. |
| 404 | The identifier is well formed, but no product matches it, or no event has been recorded for it. detail equals No events found for this UID. | Check the number you copied. A product removed from the catalog answers with the other 404 message, described on the next row. A destroyed product queried by its printed number or by its token identifier answers here. |
| 404 | Events exist for this identifier, but no product still in the catalog is attached to it. detail equals Product not found for this UID. | The product has been removed from the catalog. Its history is no longer served. |
| 429 | You have made more than 30 calls from your IP address in the current 60 second window. detail equals Rate limit exceeded: 30 requests per 60s. | Wait for the end of the window, at most 60 seconds, then retry. The x-ratelimit-* headers of this response describe another counter, do not use them to compute your wait. |
| 500 | An unexpected error occurred while processing your call. detail equals Internal Server Error. | Retry. If the error persists, contact support, giving the time of the call. |
#See also
GET /resolve/{identifier}, read in one call everything a product page displays.GET /products/{uid}/public, read the public information of a product.GET /p/{serial}, translate the printed serial number into a consumer page address.- Core concepts, tell model, batch and item apart before ordering a single label.
Was this page helpful?
Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.