Method GET/passport /{identifier} /vc /verify
Check the signature of the digital passport delivered as a verifiable credential, and read the data revealed at the access tier requested. No session is required for the public tier.
On this page
- Authorization
- Where you call from
- The tier you request
- Rate limit
- Path and query parameters
- The three identifier forms accepted
- The six values of access_tier
- Headers
- Request body
- Example request
- Example response
- A check that fails is still a 200 response
- What flips verified to false
- Checking the signature yourself
- Model or lot passport
- Errors
- See also
You have the signature of a product's digital passport checked, and you get back the data that this signature covers. By the time you leave this page, you will know how to request this check from any product identifier, tell a check that fails apart from a request that fails, and know which data the response shows you depending on the access tier you request.
Full URL:
GET https://api.sealtrust.io/v1/passport/{identifier}/vc/verifyThe same endpoint also answers without the /v1 prefix, at
https://api.sealtrust.io/passport/{identifier}/vc/verify. Both URLs call the
same code. Use the /v1 form for a new integration.
The passport is delivered in the SD-JWT-VC format, a signed credential in which
each field can be revealed or withheld separately. The issuer is the brand,
identified by a did:web decentralized identifier. This endpoint reassembles
the credential at the access tier you request, checks its signature, and
returns the revealed data to you.
#
No API key scope is checked on this endpoint. Two checks apply all the same: the origin of your call, then the access tier you request.
#Where you call from
Call this endpoint from your server.
We refuse with a 403 any call that carries an Origin or Referer header
designating a domain other than ours. The detail field is then
Forbidden origin. A browser always sets one of these two headers, so a web
page hosted anywhere other than with us cannot call this URL from its visitor's
browser.
The session cookie only works from a page served by one of our domains. We
refuse with a 403 a call that carries this cookie without an Origin or
Referer header, with detail set to Origin or Referer header required.
From a server, present the session token in the Authorization: Bearer header.
#The tier you request
The public tier requires no account. The five other values of
the access_tier parameter require an account session, presented through the
Authorization: Bearer <session token> header or through the session cookie
set at sign-in.
| Tier requested | What is needed |
|---|---|
public | nothing For a battery passport, the fields that point 1 of Annex XIII makes public are added: hazardous substances, critical raw materials, carbon footprint and its class, due diligence report, post-consumer recycled content per metal and share of renewable content. Conversely the part numbers, the battery status and the expected lifetime in cycles are removed: points 2 and 4 reserve them to persons with a legitimate interest. For a textile passport, the name and the percentage of each material are added, the fibre composition that Regulation (EU) No 1007/2011 requires on the label. For a footwear passport, the material of the upper, the lining, the insole and the sole, which Directive 94/11/EC puts on the shoe. |
end_user | a session, whatever the account |
repairer | a session, and an active repairer accreditation on the brand of the product |
recycler | a session, and an active recycler accreditation on the brand of the product |
upstream | a session of an administrator of the product's brand, or the authority role |
authority | a session carrying the market surveillance authority role |
A session of an administrator of the product's brand opens the three professional tiers on its own products. The market surveillance authority role opens them as well, on every product. A client, repairer or recycler account that is still tied to the brand, a former administrator for instance, is not part of it: it receives 403, or the one tier of its accreditation.
A partner API key opens nothing here. The token received in the Authorization
header is decoded as a user account session token, and an API key is not one.
The read fails silently and the call goes on as an anonymous call.
#Rate limit
600 calls per 60 second slice, counted per calling IP address. The window is fixed.
This ceiling is shared by every read (GET) whose URL starts with /passport. The
/passport/… and /v1/passport/… forms feed the same counter, the /v1
prefix does not create a second budget.
Every accepted response carries three headers that describe this counter.
| Header | Content |
|---|---|
X-RateLimit-Limit | the ceiling applied over the window, here 600 |
X-RateLimit-Remaining | what is left to you in the current window |
X-RateLimit-Reset | the end timestamp of the window, in seconds |
Going over returns 429, with the same three headers and a Retry-After. On
this endpoint, Retry-After is worth the length of the window, that is 60
seconds.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | yes | The identifier of the product. Three forms are accepted, see below. |
access_tier | string | no | The access tier requested. Is public by default. Six values accepted, listed further down. We refuse any other value with a 422. |
#The three identifier forms accepted
| Form | What it looks like | Where you find it |
|---|---|---|
| Serial number | 12 characters, digits and letters | Printed on the product, it is what its QR carries |
| Token identifier | A run of digits, often a very long one | Returned by our responses in the token_id field |
| Identifier hash | 0x followed by 64 hexadecimal characters | Returned by our responses in the uid_hash field |
The identifier hash exists for a QR only product as much as for a product with an NFC chip. The server draws it at random for a QR only product. It derives it from the identifier of the chip for a product with an NFC chip. Both forms therefore look the same and are requested in the same way.
We recognize the form from the way it is written. A value that starts with 0x
and is exactly 66 characters long, we look up as an identifier hash. Any other
value, we look up first as a token identifier, then as a serial number when the
first lookup found nothing.
You write the identifier hash in whatever case you want. You write the serial
number in whatever case you want as well, and we canonicalize it the way the QR
resolver does: in it we read the letters I and L as a 1, the letter O
as a 0. So you can copy by hand a number read on a label.
The certificate of authenticity number is not accepted here.
A product destroyed on the chain or withdrawn from the catalog no longer resolves through this endpoint, and the response is then 404.
#The six values of access_tier
These tiers are different audiences, with no hierarchy between them. Each of the three professional tiers inherits the public tier and the end user tier, then adds what its profession requires.
| Value | What it adds to the fields revealed |
|---|---|
public | product identification, ESPR compliance, REACH and CE marking, recyclability percentage, recycled content percentage, labels, general battery specification, the Article 13(4) and 13(5) markings, usable extinguishing agent, link to the EU declaration of conformity and its structured content once the brand has filled it in, waste management information |
end_user | environmental impact, full circularity, primary material, certified organic material, durability, energy efficiency, carbon footprint |
repairer | bill of materials, disassembly instructions, repairability index, battery state of health |
recycler | material composition, substances of concern, disassembly instructions, battery state of health |
upstream | material composition, substances of concern, manufacturing, supply chain |
authority | the whole of the data, with no filtering |
The fields the console writes at the root of the passport each have their own tier, named one by one: see Passport access rules.
A brand can replace these rules with its own. The table above describes what applies in the absence of rules specific to the brand.
#Headers
No header is required for the public tier. The five other
tiers require the Authorization header or the session cookie. In every case,
respect the origin rule described above.
#Request body
None. This request has no body.
#Example request
Check of the credential of the product whose printed number is
EXEMPLE00001, at the public tier.
The three examples run from a server. None of them works in a visitor's
browser: the browser sets an Origin header that we refuse, and you receive a
403.
curl -i "https://api.sealtrust.io/v1/passport/EXEMPLE00001/vc/verify?access_tier=public"const url = new URL(
"https://api.sealtrust.io/v1/passport/EXEMPLE00001/vc/verify",
);
url.searchParams.set("access_tier", "public");
const response = await fetch(url);
if (response.status === 404) {
console.log("No signed attestation to check for this product.");
} else if (response.status === 403) {
console.log(
"Call refused: run this code from a server, never from a browser.",
);
} else if (response.ok) {
const result = await response.json();
console.log(result.verified, result.error);
console.log(result.issuer, result.key_version);
console.log(result.credential_subject);
} else {
console.log(response.status, await response.json());
}import requests
response = requests.get(
"https://api.sealtrust.io/v1/passport/EXEMPLE00001/vc/verify",
params={"access_tier": "public"},
timeout=30,
)
if response.status_code == 404:
print("No signed attestation to check for this product.")
elif response.status_code == 403:
print("Call refused: run this code from a server, never from a browser.")
elif response.ok:
result = response.json()
print(result["verified"], result["error"])
print(result["issuer"], result["key_version"])
print(result["credential_subject"])
else:
print(response.status_code, response.json())#Example response
HTTP code 200. The signature is valid and the tier requested is public.
{
"passport_id": 1,
"issuer": "did:web:api.sealtrust.io:brand:4242",
"vct": "https://schema.sealtrust.io/vct/digital-product-passport",
"key_version": 1,
"access_tier": "public",
"verified": true,
"error": null,
"credential_subject": {
"product_identity": {
"gtin": "03701234567891",
"model": "Cartable Exemple 32",
"brand": "Exemple SAS",
"made_in": "FR"
},
"compliance": {
"eu_espr": true,
"reach": true,
"ce_marking": false
},
"circularity": {
"recyclability_percentage": 62,
"recycled_content_percentage": 0
}
}
}The response has eight fields and nothing else.
| Field | Type | Description |
|---|---|---|
passport_id | integer | The identifier of the passport version the check applied to. |
issuer | string | The did:web decentralized identifier of the issuing brand. Always filled in on this endpoint. |
vct | string | The credential type. Is https://schema.sealtrust.io/vct/digital-product-passport when the brand has not declared another one. |
key_version | integer or null | The version number of the brand signing key that signed the credential. null when that number was not recorded at issuance. |
access_tier | string | The access tier requested, carried over as is. This endpoint never changes the tier requested. |
verified | boolean | true when the signature was checked successfully. |
error | string or null | null when verified is true. Is verification_failed otherwise. That is the only possible value. |
credential_subject | object or null | The data revealed at the tier requested, as the signature covers it. Is null as soon as verified is false. |
#A check that fails is still a 200 response
That is the point to remember from this page. A check failure is not an HTTP error. The response stays 200 and carries the verdict.
{
"passport_id": 1,
"issuer": "did:web:api.sealtrust.io:brand:4242",
"vct": "https://schema.sealtrust.io/vct/digital-product-passport",
"key_version": 1,
"access_tier": "public",
"verified": false,
"error": "verification_failed",
"credential_subject": null
}So always read verified. A 200 code says nothing on its own.
#What flips verified to false
| Cause | What it means |
|---|---|
| The signature does not match the content | The credential was modified after it was issued. |
| The issuer written in the credential is none of this brand's identities | The credential was issued under an identity foreign to the brand of the product. A brand that has changed hosting domain keeps its earlier credentials recognised, under its old identity as well as its new one. |
| The credential designates no readable key version | The header of the credential carries no usable key version number. |
| The key version cited by the credential does not exist for this brand | The key that signed is not known. |
| That key version was revoked | The brand withdrew this key. The credentials it signed are no longer recognized. |
The response does not say which of these causes applies. The error field is
verification_failed in all of these cases.
#Checking the signature yourself
You are not required to ask us for this verdict. The brand publishes its public
signing keys in a decentralized identity document, served publicly at
GET https://api.sealtrust.io/brand/{brand_id}/did.json. A brand that hosts
its identity on its own domain serves it at
https://<its domain>/.well-known/did.json.
With that document and any off-the-shelf did:web and SD-JWT-VC library, you
check the signature without going through us. That is what makes the passport
enforceable without depending on our availability.
#Model or lot passport
A passport of a model, or of a production lot, is bound to no item. It
therefore has no identifier to pass in {identifier}. Its credential is checked at the
GS1 address of the passport itself, next to the passport and its proof:
GET https://api.sealtrust.io/v1/passport/01/{gtin}/vc/verify
GET https://api.sealtrust.io/v1/passport/01/{gtin}/10/{lot}/vc/verifyThe first address targets the passport of the model that carries this GTIN. The
second targets the passport of lot {lot} of that model. Like the other
addresses of this page, each one also answers without the /v1 prefix.
| Name | Type | Required | Description |
|---|---|---|---|
gtin | string | yes | The GTIN of the model, 8, 12, 13 or 14 digits. Its check digit is verified. |
lot | string | on the second address | The lot number (GS1 application identifier 10), as printed, upper and lower case included. A reserved character, such as /, is percent-encoded. |
It is the only way to have checked the credential of a brand on the Compliance offer, which registers no item.
The access tier, the authorization rules, the rate limit and the fields of the response are those of this page. The response adds two fields, and a third one for a lot.
| Field | Type | Presence | Description |
|---|---|---|---|
level | string | on these two addresses | model for a model passport, batch for a lot passport. |
gtin | string | on these two addresses | The GTIN, brought to 14 digits. |
batch_code | string | on the lot address | The lot number. |
The credential of a model or a lot carries the identity of the model only. In
its product block, uid_hash and token_id are null.
The address of a lot targets the passport of that lot only. A lot without a published passport answers 404, and never with the credential of the model.
#Errors
The body of an error response carries a detail field.
The checks run in this order: origin of the call, resolution of the product, lookup of the published passport, check of the tier requested, presence of an issued credential, then identification of the issuing brand. The first step that fails gives the response.
| Code | Condition | What to do |
|---|---|---|
| 401 | access_tier=end_user is requested with no valid session. detail is End-user-tier access requires authentication. | Sign in with any account, or request the public tier. |
| 401 | access_tier=authority is requested with no valid session. detail is Authority-tier access requires authentication. | Sign in with an account carrying the market surveillance authority role. A partner API key will not do. |
| 401 | access_tier is repairer, recycler or upstream, and the call carries no valid session. detail is Professional-tier access requires authentication. | Present an account session token, or request the public tier. |
| 403 | The call carries an Origin or Referer header that does not designate one of our domains, which happens for any call sent from a web page hosted elsewhere. detail is Forbidden origin. | Call this endpoint from your server. A call sent by a visitor's browser cannot succeed. |
| 403 | The call carries the session cookie and has neither an Origin header nor a Referer header, which happens when a browser cookie is replayed on the command line. detail is Origin or Referer header required. | Remove the cookie and present the session token in the Authorization: Bearer header. |
| 403 | access_tier=authority is requested by a signed-in account that does not carry this role. detail is Authority-tier access is restricted to market surveillance authorities. | Request the tier that matches your clearance. |
| 403 | A professional tier is requested by a signed-in account that is neither an administrator of the product's brand, nor the holder of the matching accreditation on that brand. detail is This tier is restricted to the product's brand, partners holding the matching accreditation, and market-surveillance authorities. | Ask the brand for the accreditation that matches your profession, then request the tier of that profession. |
| 404 | No product matches this identifier, under any of the three accepted forms. detail is Product not found. | Check the identifier. A product destroyed on the chain or withdrawn from the catalog gives this same response. |
| 404 | The product exists, but no public passport is published for it nor for its model. detail is No published passport found for this product. | There is nothing to check. A passport reserved for the owner or for the brand also gives this response. |
| 404 | A public passport exists, but no signed credential was issued for this version. detail is No VC issued for this passport yet. | Ask the brand to issue the credential for this version of the passport. The passport stays readable through the read endpoints. |
| 404 | The brand of the passport could not be found. detail is Brand not found. | Contact support giving the identifier you called. No action on your side fixes this response. |
| 404 | The call arrives on a brand's verified domain name, and the product belongs to another brand. detail is Product not found. | 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. |
| 400 | On the address of a model or a lot, the check digit of the GTIN is wrong. detail is Invalid GTIN: the check digit does not match. | Copy the GTIN again from the label. |
| 404 | On the address of a model or a lot: no passport published with public visibility answers for this GTIN or this lot, several brands publish a passport under this GTIN, or the call arrives on the verified domain of another brand. detail is Unknown GS1 Digital Link in all these cases. | Check the GTIN and the lot number. The message is the same in all these cases, so as not to confirm that a GTIN exists. |
| 422 | access_tier is not one of the six accepted values. detail is a list, each entry carrying loc, type and msg. | Read loc to know which parameter is at fault, then fix its value. |
| 429 | The ceiling of 600 calls per 60 seconds is reached for your IP address, across every read starting with /passport. detail is Rate limit exceeded: 600 requests per 60s. | Wait the number of seconds given by Retry-After, then try again. |
| 500 | An unexpected error occurred while your call was being processed. detail is Internal Server Error. The response carries an X-Request-Id header. | Try again. If the error persists, contact support giving the value of X-Request-Id. |
#See also
GET /passport/{identifier}/vc, retrieve the signed credential of the passport, in the SD-JWT-VC format.GET /passport/{identifier}/vc/preview, see, without a signature, what an access tier would expose.GET /brand/{brand_id}/did.json, retrieve the public signing keys of a brand.GET /.well-known/did.json, serve the identity document of a brand on its own domain.
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.