Method GET/qr /verify
Verify an item from the signed parameters carried by a verification address of the old form. Public endpoint, no API key.
On this page
You get an authenticity verdict for an item, from the signed parameters of a verification address, without any API key.
The full address is https://api.sealtrust.io/v1/qr/verify. The same route
exists without the /v1 prefix, and the /v1 form is the one recommended for
a new integration.
This endpoint replies 200 even when the verdict is negative. A tampered QR
code, a QR code that is too old, an item not yet minted: in all three cases the
response is a 200 whose valid field is false. The 4xx and 5xx codes are
reserved for the cases where no verdict can be returned.
#
None, public endpoint. It expects no API key, no session cookie, and no
Authorization header.
Call it from your server. Our cross-origin sharing policy allows only the
public site and the SealTrust console. From a web page hosted on another
domain, the browser blocks the response, and the X-RateLimit-Remaining header
is never exposed to a page script anyway.
What is authoritative here is the s value. It is the signature that we
affixed when producing the verification address. Without it, or with a modified
value, the verdict is negative.
#Call limit
30 calls per 60-second window, counted per calling IP address. The limit is
shared by all the addresses that start with /qr, and it applies to
/qr/verify as well as to /v1/qr/verify.
The responses carry the X-RateLimit-Limit, X-RateLimit-Remaining and
X-RateLimit-Reset headers, the last one giving the reset time in seconds
since January 1, 1970. Going over returns 429 with Retry-After in addition,
in seconds.
Do not make your code depend on the presence of these headers: read them if they are there, and carry on if they are missing.
This endpoint consumes no quota of your plan.
#Path and query parameters
This endpoint has no path parameter.
| Name | Type | Required | Description |
|---|---|---|---|
u | string | yes | The fingerprint of the item: 0x followed by 64 hexadecimal characters. Edge spaces are removed, case does not matter, and the 0x prefix is added if it is missing. |
ts | integer | yes | The timestamp written into the address at the moment it was produced, in seconds since January 1, 1970. |
s | string | yes | The signature, as it appears in the address. |
t | string | no | The identifier of the token. Old addresses omit it. The server finds the real identifier from u anyway, and that is the one it returns. |
c | string | no | The short reference of the contract carried by the address. This endpoint accepts it and never reads it. The presence or the absence of c changes nothing in the response. |
src | string | no | Present in the addresses of the old form, with the value qr. This endpoint does not declare it and takes no account of it. |
latitude | number | no | Latitude transmitted by the scanning device, between -90 and 90. Rounded to two decimal places, that is cells of about 1.1 km, before any recording. The fine position exists at no moment on our side. |
longitude | number | no | Longitude transmitted by the scanning device, between -180 and 180. Rounded the same way. |
latitude and longitude go in pairs. If you send only one of them, it is
discarded and the scan is recorded without coordinates.
The response never returns coordinates: at best an area label. When you send no
coordinates, the city and the country of that label are deduced from the
calling IP address, through a lookup database read on our side. Your IP address
is sent to no outside service. If the position of the scan matters to you, send
latitude and longitude. Otherwise, know that the recorded area will be that
of the calling machine.
#Request body
None. This is a GET request, everything goes through the query parameters.
#Example request
curl -i -G https://api.sealtrust.io/v1/qr/verify \
--data-urlencode "u=0x0000000000000000000000000000000000000000000000000000000000000000" \
--data-urlencode "t=1024" \
--data-urlencode "ts=1755000000" \
--data-urlencode "c=0x00000000" \
--data-urlencode "s=0000000000000000000000000000000000000000000000000000000000000000"// To be run on your server, with Node 18 or newer.
// From a web page on another domain, the browser blocks the response.
const parametres = new URLSearchParams({
u: "0x0000000000000000000000000000000000000000000000000000000000000000",
t: "1024",
ts: "1755000000",
c: "0x00000000",
s: "0000000000000000000000000000000000000000000000000000000000000000",
});
const response = await fetch(
`https://api.sealtrust.io/v1/qr/verify?${parametres.toString()}`,
);
console.log(response.status);
// This header is readable only from a server call, and it can be missing.
console.log(response.headers.get("X-RateLimit-Remaining"));
console.log(await response.json());import requests
response = requests.get(
"https://api.sealtrust.io/v1/qr/verify",
params={
"u": "0x0000000000000000000000000000000000000000000000000000000000000000",
"t": "1024",
"ts": "1755000000",
"c": "0x00000000",
"s": "0000000000000000000000000000000000000000000000000000000000000000",
},
timeout=30,
)
print(response.status_code)
# This header can be missing, so read it without requiring its presence.
print(response.headers.get("X-RateLimit-Remaining"))
print(response.json())#Example response
HTTP code 200, positive verdict.
{
"valid": true,
"message": "Authentic product: QR code and blockchain verified.",
"uid_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"token_id": "1024",
"product_name": "Sac de voyage Exemple SAS",
"brand_name": "Exemple SAS",
"image_url": null,
"contract_address": "0x0000000000000000000000000000000000000000",
"scan_area": "Lyon, FR"
}Here the item has no cover image, so image_url is null. When it has one,
the field carries its public address. The scan_area field is filled in
because the call could be located. It is null otherwise.
HTTP code 200 as well when the verdict is negative, and the response does not have the same shape depending on the verdict.
Three negative verdicts are returned before any read on the chain: signature
refused, address too old, item not yet minted. These three responses carry only
uid_hash.
{
"valid": false,
"message": "Invalid QR code signature. This QR code may be tampered with.",
"uid_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"token_id": null,
"product_name": null,
"brand_name": null,
"image_url": null,
"contract_address": null,
"scan_area": null
}The fourth negative verdict is returned after the read on the chain, when the
fingerprint read does not match. This response describes the item, like the
positive verdict. Only scan_area stays null, because the area is computed
only on a positive verdict.
{
"valid": false,
"message": "Invalid product: blockchain verification failed.",
"uid_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"token_id": "1024",
"product_name": "Sac de voyage Exemple SAS",
"brand_name": "Exemple SAS",
"image_url": null,
"contract_address": "0x0000000000000000000000000000000000000000",
"scan_area": null
}The nine fields of the response.
| Field | Type | Description |
|---|---|---|
valid | boolean | The verdict. It is the only field on which to build your logic. |
message | string | A sentence in English that explains the verdict. Base your decisions on valid, never on this text. |
uid_hash | string or null | The fingerprint of the item, normalized: lowercase, 0x prefix. Filled in as soon as the item has been found, including on a negative verdict. |
token_id | string or null | The identifier of the token, in base 10, in the form of a string. It is the one recorded on our side, which can differ from the t that you sent. Filled in on a positive verdict and on the Invalid product: blockchain verification failed. verdict |
product_name | string or null | The name of the item. Same presence rule as token_id. |
brand_name | string or null | The name of the owning brand. Returned in the same cases as token_id, and null when the item is attached to no brand. |
image_url | string or null | The public address of the cover image. null when the item and its model have none. |
contract_address | string or null | The address of the contract that carries this token, in lowercase. Same presence rule as token_id. |
scan_area | string or null | The area of the scan. City, CC when the city and the two-letter country code are both known, for example Lyon, FR. When only one part is known, the field carries only that one, Lyon or FR. null when neither of the two is known. Filled in only on a positive verdict. Never coordinates, never a postal address. |
#The six possible verdicts
valid | message | What it means |
|---|---|---|
true | Authentic product: QR code and blockchain verified. | The signature is recognized and the fingerprint recorded on the chain matches. |
false | Invalid product: blockchain verification failed. | The signature is recognized, but the fingerprint read on the chain does not match. |
false | Invalid QR code signature. This QR code may be tampered with. | The signature does not match the one we expect for this item. |
false | QR code has expired. Please request a new QR code. | The ts timestamp differs from the current time by more than 30 days, ahead as well as behind. Produce a new verification address from your console. |
false | This product has not been minted yet. | The item exists on our side, its minting has never been submitted. Calling back later will change nothing. |
false | Mint submitted, waiting for on-chain confirmation. | The minting has been submitted and is waiting for its confirmation. Call back in a few minutes. |
#Errors
| Code | Condition | What to do |
|---|---|---|
| 404 | No live item carries this fingerprint. Message Product not found for this QR code. The item has been destroyed, or withdrawn from the catalog. | Check the value of u. If the item has been destroyed or withdrawn from the catalog, this code is final. |
| 404 | The chain does not know this token. Message Unknown product: this token_id does not exist. | Nothing to correct on the call side. Contact the brand: the item is recorded on our side with a token identifier that the contract does not carry. |
| 422 | A required parameter is missing (u, ts or s), ts is not an integer, or latitude and longitude are out of their bounds. | The body of the response lists the parameters at fault and the reason for each refusal. Correct and call again. |
| 429 | More than 30 calls in 60 seconds from the same IP address. Message Rate limit exceeded: 30 requests per 60s. | Wait the number of seconds given by Retry-After. Spread out your calls instead of sending them in bursts. |
| 500 | Unexpected error on the server. Fixed body {"detail": "Internal Server Error"}. | Try again. The X-Request-Id header identifies the call; send it to us if it repeats. |
| 503 | The read on the chain failed. Message Error during blockchain verification. | Try again in a few moments. No verdict was returned, and the scan was not recorded. |
#See also
GET /p/{serial}, translate the printed serial number into the address of a consumer page.GET /resolve/{identifier}, read in one call everything a product page displays.- Physical identification, QR and NFC, choose the physical carrier and the exact form of the GS1 Digital Link.
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.