Method GET/brand /{brand_id} /did.json
Retrieve a brand's identity document: the list of its public signing keys, in did:web format. Public endpoint.
On this page
You retrieve the public keys a brand uses to sign its digital product passports. By the end of this page, you will know how to request this document, find in it the key that signed a given credential, and understand what the absence of a key means.
Full address:
GET https://api.sealtrust.io/v1/brand/{brand_id}/did.jsonThe same endpoint also answers without the /v1 prefix, at
https://api.sealtrust.io/brand/{brand_id}/did.json. Both addresses call the
same code. Use the /v1 form for a new integration.
This document is what a verifier goes and fetches on its own. The signed
credential of a passport carries an issuer identifier of the form
did:web:api.sealtrust.io:brand:4242. The public rule of the did:web format
translates that identifier into the address
https://api.sealtrust.io/brand/4242/did.json, that is, this endpoint. Any
standard did:web library performs that translation without knowing anything
about SealTrust.
#
None, this is a public endpoint. You send no API key, no session and no origin header. The response is the same for everyone.
The document contains public keys only. No private key ever leaves our system, and none can be reconstructed from this document.
#Rate limit
This endpoint has no limit of its own. It shares a general counter with the other routes that have no limit of their own, counted per calling network address over a 60 second window.
Handle the 429 code in your client and honor the Retry-After header it
carries. The value of the general counter can change without notice, so do not
hard-code any number in your code.
The response carries three headers that describe this counter.
| Header | Contents |
|---|---|
X-RateLimit-Limit | the limit the general counter announces for the window |
X-RateLimit-Remaining | what is left to you in the current window |
X-RateLimit-Reset | the timestamp of the end of the window, in seconds |
This document changes only when the brand rotates its signing keys, which is
rare. Keep a copy on your side. Refresh it when a credential carries a kid
that your copy does not contain.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
brand_id | integer | yes | The brand's numeric identifier. It is the number that follows brand: in the issuer identifier of the signed credential. |
This endpoint has no query parameter.
#Headers
No header is required.
#Request body
None. This request has no body.
#Example request
Identity document of the brand whose identifier is 4242.
curl -i https://api.sealtrust.io/v1/brand/4242/did.jsonconst reponse = await fetch(
"https://api.sealtrust.io/v1/brand/4242/did.json",
);
if (reponse.status === 404) {
console.log("Aucune marque ne porte cet identifiant.");
} else if (reponse.ok) {
const document = await reponse.json();
console.log("Identifiant de la marque :", document.id);
console.log("Clefs publiées :", document.verificationMethod.length);
for (const methode of document.verificationMethod) {
console.log(methode.id, methode.type, methode.publicKeyJwk.crv);
}
// Find the key that signed a given credential.
const kid = "did:web:api.sealtrust.io:brand:4242#key-2";
const clef = document.verificationMethod.find((m) => m.id === kid);
if (clef) {
console.log("Clef de signature trouvée :", clef.publicKeyJwk);
} else {
console.log("Cette clef n'est plus publiée. Signature à rejeter.");
}
} else {
console.log(reponse.status, await reponse.json());
}import requests
response = requests.get(
"https://api.sealtrust.io/v1/brand/4242/did.json",
timeout=30,
)
if response.status_code == 404:
print("Aucune marque ne porte cet identifiant.")
elif response.ok:
document = response.json()
print("Identifiant de la marque :", document["id"])
print("Clefs publiées :", len(document["verificationMethod"]))
for methode in document["verificationMethod"]:
print(methode["id"], methode["type"], methode["publicKeyJwk"]["crv"])
# Find the key that signed a given credential.
kid = "did:web:api.sealtrust.io:brand:4242#key-2"
clef = next(
(m for m in document["verificationMethod"] if m["id"] == kid),
None,
)
if clef:
print("Clef de signature trouvée :", clef["publicKeyJwk"])
else:
print("Cette clef n'est plus publiée. Signature à rejeter.")
else:
print(response.status_code, response.json())#Example response
HTTP code 200. The body is JSON, served with the header
Content-Type: application/json.
Here, the brand Exemple SAS has rotated its key once. Both versions remain
published, so the credentials signed under the older one remain verifiable.
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:web:api.sealtrust.io:brand:4242",
"verificationMethod": [
{
"id": "did:web:api.sealtrust.io:brand:4242#key-1",
"type": "JsonWebKey2020",
"controller": "did:web:api.sealtrust.io:brand:4242",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"y": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
}
},
{
"id": "did:web:api.sealtrust.io:brand:4242#key-2",
"type": "JsonWebKey2020",
"controller": "did:web:api.sealtrust.io:brand:4242",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
"y": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
}
}
],
"assertionMethod": [
"did:web:api.sealtrust.io:brand:4242#key-1",
"did:web:api.sealtrust.io:brand:4242#key-2"
],
"authentication": [
"did:web:api.sealtrust.io:brand:4242#key-1",
"did:web:api.sealtrust.io:brand:4242#key-2"
]
}The five top-level fields are always present.
| Field | Type | Description |
|---|---|---|
@context | string[] | The two vocabularies that give the document's fields their meaning. Always these two values, in this order. |
id | string | The brand's identifier, in did:web format. It is the value carried by the issuer field of a signed credential. |
verificationMethod | object[] | One entry per published public key, from the oldest version to the most recent. |
assertionMethod | string[] | The identifiers of the keys allowed to sign a credential. Repeats the same entries as verificationMethod, in the same order. |
authentication | string[] | The identifiers of the keys allowed to prove control of this identifier. Repeats the same entries. |
#An entry of verificationMethod
| Field | Type | Description |
|---|---|---|
id | string | The key's identifier, of the form <brand identifier>#key-<version number>. |
type | string | Always JsonWebKey2020. |
controller | string | The identifier of the brand that controls this key. Always equals the document's id field. |
publicKeyJwk | object | The public key itself, in JWK format. |
publicKeyJwk carries four fields: kty equals EC, crv equals P-256, x
and y are the two coordinates of the public point, encoded in base64url. These
values are passed as they are to a JWS verification library.
#Finding the key that signed a credential
The header of a passport credential carries a kid field. That kid equals
exactly one of the id values of verificationMethod, for example
did:web:api.sealtrust.io:brand:4242#key-2. You look that value up in the
list, you take the matching publicKeyJwk, and you check the signature with
the ES256 algorithm.
If the kid is not in the list, the signature must be rejected.
#What the absence of a key means
A revoked key is removed from the document. Every credential signed under that version stops being verifiable, and that is the intended result.
A key replaced by a more recent version stays published. It is no longer used to sign new credentials, and the older ones keep verifying.
verificationMethod can be an empty list, with assertionMethod and
authentication empty too. That means the brand has not published any signing
key yet. No credential from that brand is verifiable then.
#When the id field does not match the address called
A brand can carry its identity on its own domain name. The document's id
field then equals did:web:<its domain>, and the reference document is found
at https://<its domain>/.well-known/did.json.
In that case, take the id field of the credential you are verifying as your
starting point, apply the did:web translation rule, and go and fetch the
document at the resulting address. Never build the address yourself from the
brand identifier.
#Errors
The body of an error response carries a detail field. Every response from
this endpoint, successful or in error, carries an X-Request-Id header.
| Code | Condition | What to do |
|---|---|---|
| 404 | No brand carries this identifier. detail equals Brand not found. | Check the number that follows brand: in the issuer identifier. Reject the signature: an issuer whose identity document cannot be found proves nothing. |
| 422 | The value sent in the path is not an integer. detail carries the list of validation errors, with the name of the parameter at fault. | Correct the identifier. A brand identifier is written in digits only. |
| 422 | The identity document of the brand found cannot be built. | Report it to support, giving the identifier you called. There is nothing you can correct on your side. |
| 500 | An unexpected error occurred while processing your call. detail equals Internal Server Error. | Retry. If the error persists, contact support, giving the value of X-Request-Id. |
This endpoint returns neither 401 nor 403: it is public and reads no authorization.
#See also
GET /.well-known/did.json, serve a brand's identity document on its own domain.GET /passport/{identifier}/vc, retrieve the signed credential of the passport, in SD-JWT-VC format.GET /passport/{identifier}/vc/verify, check the credential's signature and read the disclosed data.- Trust and proofs, what each proof establishes and how a third party redoes the verification.
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.