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:

HTTP
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.

#Authorization

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_address and to_address equal null;
  • we mask the email addresses: the value returned keeps the first two characters of the local part, then ..., then six stable characters, for example ma...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

NameTypeRequiredDescription
identifierstringyesThe product's identifier. Three forms are accepted, see below.
langstringnoThe 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.

FormWhat it looks likeWhere you find it
Serial number12 characters, digits and capital letters, in an alphabet that excludes I, L, O and UPrinted on the product, it is what its QR carries
Token identifierA string of digits, often very longReturned by our responses in the token_id field
Chip hash0x followed by 64 hexadecimal charactersReturned 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/EXEMP1E00001

#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.

JSON
{
  "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.

FieldTypeDescription
token_idstringThe identifier of the token on the chain, returned as text. Equals the empty string when the product does not have a token identifier yet.
serialstring or nullThe 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_namestringThe product's name, or null.
brand_idintegerDeprecated: the brand's internal number, kept during the transition, or null if the product is attached to none. Read brand_code.
brand_codestringThe 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_namestringThe brand's name, or null if the product is attached to none.
brand_hide_powered_bybooleantrue 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_urlstring or nullThe 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_idinteger or nullThe 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_namestring or nullThe name of the product's category, in the caller's language: see the lang parameter above. null when no category is attached.
metadata_uristringThe address of the product's metadata, or null.
image_urlstringThe 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_emailstringThe email address of the current owner, masked. null when no owner is known.
current_owner_is_vaultbooleantrue when the product is still held by the brand's vault, and therefore not yet claimed by a customer.
identity_includedbooleantrue 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.
timelineobject[]The events, from the most recent to the oldest. See the table below.
contract_addressstringThe 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.

FieldTypeDescription
typestringverify for a verification, transfer for a change of ownership, event for a step in the life of the product.
token_idstringThe identifier of the token concerned.
uid_hashstringThe chip hash concerned, 0x followed by 64 hexadecimal characters.
contract_addressstringThe address of the contract concerned.
timestampstringDate and time of the event, in universal time, in the format ISO 8601.
signaturestringThe signature recorded with the verification. null on a change of ownership.
is_validbooleanThe result of the verification. null on a change of ownership.
sourcestringWhere the verification comes from. The values written today are qr, sdm-url and sdm-json. null on a change of ownership.
from_addressstringAlways null on a public call.
to_addressstringAlways null on a public call.
from_emailstringThe email address of the party giving up ownership, masked. Equals Mint when the event is the minting of the product.
to_emailstringThe email address of the receiving party, masked.
from_displaystringA 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_displaystringThe same label, for the receiving party.
from_rolestring or nullmint 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_rolestring or nullThe same role, for the receiving party.
commentstringA 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_idintegerThe identifier of the life step, on an entry of type event. null elsewhere.
event_typestringThe 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.
labelstringThe title of the step, as the brand wrote it. A readable fallback, not a value to interpret. null outside an event entry.
proof_levelstringWhat this step rests on. null when the brand did not fill it in.
metaobjectThe 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.
occurrencesintegerHow many verifications this entry stands for. It is 1 everywhere else.
first_timestampstringThe 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.

CodeConditionWhat to do
400The 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.
400The 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.
404The 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.
404Events 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.
404The 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.
429You 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.
500An 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

Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.

Suggest a correctionReport a problem