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. One account only receives OTHER people's email addresses in the clear:
an administrator of the brand that issued that product. An administrator of
another brand receives the anonymized projection, exactly like a visitor. A
SealTrust superadmin receives the clear on every product, because that is the
operations account.
The same accounts, and only those, receive 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
600 calls per 60 second window, counted per calling IP address. The window is fixed. The limit is wide because hundreds of visitors at a trade fair or on a mobile network often share one address.
Calls on /v1/timeline/… and on /timeline/… feed the same counter. Switching
from one form to the other therefore does not raise the limit.
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and
X-RateLimit-Reset, which describe this limit. Going over returns 429, with
these three headers and a Retry-After in seconds.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | yes | The product's identifier. Three forms are accepted, see below. |
lang | string | no | The language of the category name: fr or en. Failing that, the server reads the Accept-Language header, then falls back to en. |
A single query parameter, lang, and it is optional. It changes one thing in the
response: the language of category_name. Everything else is identical whatever
its value.
The server picks the language in this order: the lang parameter when it is fr
or en, otherwise the first recognised tag of the Accept-Language header,
otherwise en. A value the server does not recognise is ignored without an
error: you get en, and not a 422.
#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_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())#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",
"serial": "H897RFWJ4972",
"product_name": "Model 1 bag",
"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://example-de-contenu-non-reel",
"image_url": "https://example-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-example",
"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"
}The response counts fifteen 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. |
serial | string or null | The serial printed on the product, whatever identifier the address used (serial, token identifier or chip fingerprint). It is what the claim by serial expects. null for a product that has none. |
product_name | string | The product's name, or null. |
brand_id | integer | Deprecated: the brand's internal number, kept during the transition, or null if the product is attached to none. Read brand_code. |
brand_code | string | The brand's public code, ten characters, or null if the product is attached to none. It is the identifier to quote and to keep. |
brand_name | string | The brand's name, or null if the product is attached to none. |
brand_hide_powered_by | boolean | true when the "Powered by SealTrust" wording must disappear. Two conditions, and both are required: the brand's plan includes white labeling and the brand has chosen to hide the wording in its console. By default the wording stays, including for a brand that has just subscribed to Inside and changed nothing. |
brand_app_url | string or null | The address of the brand's own mobile application, when it has one. null otherwise. Public pages offer hers when it is set, ours otherwise. |
category_id | integer or null | The identifier of the product's category. null when no category is attached. This field returned null on every product for a long time, including those that did have a category: it was declared in the shape of the response and never filled. Fixed on 31 August 2026. |
category_name | string or null | The name of the product's category, in the caller's language: see the lang parameter above. null when no category is attached. |
metadata_uri | string | The address of the product's metadata, or null. |
image_url | string | The address of the product's image, or null. It is the light copy of the photo (WebP, 800 pixels at most on the long side, 100 KB at most, no EXIF data) when it exists, else the original. An address of another site is returned as given. |
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. |
identity_included | boolean | true when the piece has its identity: claim, transfer, resale. A piece created on the Compliance + Identity offer keeps it, even if its brand later moves to the Compliance offer. false for a piece without identity: it is neither claimed nor transferred, and the public page offers neither. null when the answer does not say, which never means a refusal. |
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 twenty-two 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, event for a step in the life of the product. |
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. |
from_role | string or null | mint when the party giving up ownership is the minting, vault when it is the brand's vault, null otherwise. A page translates this role rather than from_display, which is in English. |
to_role | string or null | The same role, 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 | The identifier of the life step, on an entry of type event. null elsewhere. |
event_type | string | The nature of the step: quality_control, certification, warranty_activation, after_sale_service, and others depending on what the brand records. Translate from this key. null outside an event entry. |
label | string | The title of the step, as the brand wrote it. A readable fallback, not a value to interpret. null outside an event entry. |
proof_level | string | What this step rests on. null when the brand did not fill it in. |
meta | object | The details specific to the step. Wallet addresses are removed from it, and the address of the person who acted is masked. null when there is nothing to say. |
occurrences | integer | How many verifications this entry stands for. It is 1 everywhere else. |
first_timestamp | string | The date of the oldest verification in the group, when occurrences is above 1. null otherwise. |
#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. |
| 404 | The call arrives on a brand's verified domain name, and the product belongs to another brand. detail is No events found for this UID. | Call this address on the domain of the brand concerned, or on api.sealtrust.io. The message is the same as for an unknown identifier, so as not to confirm that it exists. |
| 429 | You have made more than 600 calls from your IP address in the current 60 second window. detail equals Rate limit exceeded: 600 requests per 60s. | Wait the number of seconds given by Retry-After, then retry. |
| 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.