Method GET/.well-known /did.json
Serve the decentralized identity document of a brand on its own domain name, in delegated mode. Public endpoint, resolved from the host name called.
On this page
You retrieve the decentralized identity document of a brand, published under the domain name of that brand. This document lists the public keys with which the brand signs its digital product passports. By the end of this page, you will know how to call this address, read every field of the document, and tell apart a domain that is not yet recognized from a recognized domain for which no brand has been declared.
Full address, with an example domain:
GET https://id.exemple-sas.example/.well-known/did.jsonThis endpoint does not read like the others. The path is always the same for
everyone. It is the host name called that designates the brand. The server
reads the Host header of your request, looks for the brand that declared
this domain name in delegated mode, and serves the document of that brand.
The same endpoint also answers under the /v1 prefix, at
https://id.exemple-sas.example/v1/.well-known/did.json. Both addresses call
the same code. No did:web resolver uses this second form: the specification
requires the path /.well-known/did.json at the root of the domain. Call the
form without a prefix.
#
None, this is a public endpoint. You send no API key, no session, and no
origin header. The response is the same for everyone. That is what allows a
third party verifier to check the signature of a passport with a standard
did:web library, without an account with us.
#Rate limit
No limit of its own for this endpoint. This path falls under the general counter of the API, counted per calling network address over a slice of 60 seconds.
This fallback counter is shared by every path that has no limit of its own.
The calls you make to one of them therefore eat into the budget of the others.
The /v1 prefix does not create a second budget.
Every response carries the headers X-RateLimit-Limit,
X-RateLimit-Remaining and X-RateLimit-Reset. Read X-RateLimit-Remaining
to know how many calls you have left in the current window.
X-RateLimit-Reset carries the moment of the switch to the next window, in
seconds since January 1, 1970.
Plan for the 429 code in your client and respect the Retry-After header it
carries. The value of the general counter can change without notice: set your
pace on the headers of your responses, and hard code no number in your code.
#Path and query parameters
This endpoint has no path parameter and no query parameter. The path is fixed and identical for every brand.
#Headers
| Name | Type | Required | Description |
|---|---|---|---|
Host | string | yes | The host name that designates the brand. It is the only element of the request that the server reads to choose the response. Your HTTP client fills it in automatically from the address you call. |
The server folds this host name to lowercase and strips the port number from
it before looking for the brand. ID.Exemple-SAS.example and
id.exemple-sas.example:443 therefore designate the same brand.
No other header is required.
#Request body
None. This request has no body.
#Example request
Identity document of the fictional brand Exemple SAS, published on its
delegated domain id.exemple-sas.example.
curl -i https://id.exemple-sas.example/.well-known/did.jsonconst reponse = await fetch(
"https://id.exemple-sas.example/.well-known/did.json",
);
if (reponse.status === 404) {
console.log("Aucune marque n'est publiée sous ce nom de domaine.");
} else if (reponse.ok) {
const document = await reponse.json();
console.log(document.id);
for (const methode of document.verificationMethod) {
console.log(methode.id, methode.publicKeyJwk.crv);
}
console.log(document.assertionMethod);
} else {
console.log(reponse.status, await reponse.json());
}import requests
response = requests.get(
"https://id.exemple-sas.example/.well-known/did.json",
timeout=30,
)
if response.status_code == 404:
print("Aucune marque n'est publiée sous ce nom de domaine.")
elif response.ok:
document = response.json()
print(document["id"])
for methode in document["verificationMethod"]:
print(methode["id"], methode["publicKeyJwk"]["crv"])
print(document["assertionMethod"])
else:
print(response.status_code, response.json())#Example response
HTTP code 200. A brand that has rotated its key once publishes two
verification methods, the old one and the new one.
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:web:id.exemple-sas.example",
"verificationMethod": [
{
"id": "did:web:id.exemple-sas.example#key-1",
"type": "JsonWebKey2020",
"controller": "did:web:id.exemple-sas.example",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"y": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
}
},
{
"id": "did:web:id.exemple-sas.example#key-2",
"type": "JsonWebKey2020",
"controller": "did:web:id.exemple-sas.example",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
"y": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
}
}
],
"assertionMethod": [
"did:web:id.exemple-sas.example#key-1",
"did:web:id.exemple-sas.example#key-2"
],
"authentication": [
"did:web:id.exemple-sas.example#key-1",
"did:web:id.exemple-sas.example#key-2"
]
}The response has five fields and nothing else.
| Field | Type | Description |
|---|---|---|
@context | string[] | The two vocabularies that give their meaning to the other fields. Always https://www.w3.org/ns/did/v1 then https://w3id.org/security/suites/jws-2020/v1, in that order. |
id | string | The decentralized identifier of the brand, of the form did:web: followed by the domain name called. It is the value carried by the issuer field of the passports signed by this brand. |
verificationMethod | object[] | The list of the public keys of the brand. One entry per key. See the table below. |
assertionMethod | string[] | The identifiers of the keys allowed to sign a passport, in the same order as verificationMethod. |
authentication | string[] | The same list of identifiers as assertionMethod. |
Each entry of verificationMethod carries four fields.
| Field | Type | Description |
|---|---|---|
id | string | The identifier of the key, of the form <identifier of the brand>#key-<version number>. It is the value carried by the kid header of a signed passport, and it is what tells you which key to use. |
type | string | Always JsonWebKey2020. |
controller | string | The decentralized identifier of the brand. Same value as the id field of the document. |
publicKeyJwk | object | The public key in JSON Web Key format. A key on the NIST P-256 elliptic curve: kty reads EC, crv reads P-256, x and y are the two coordinates of the public point, encoded in base64url. |
The response also carries these headers.
| Header | Content |
|---|---|
Content-Type | application/json. |
Cache-Control | no-store, max-age=0. The response must be kept in no intermediate cache. |
X-Request-Id | The identifier of your call on our side. Give this value to support when you report an unexpected response. |
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset | Your budget of calls, see the Rate limit section. |
#What the list of keys contains
The keys are returned from the oldest version to the most recent, by ascending version number.
A key taken out of service stays in the document as long as it has not been
revoked. That is intended: a passport signed under an older version keeps
verifying after a rotation. Take the key whose identifier matches the kid
header of the passport you are verifying, never the last one of the list.
A revoked key disappears from the document. The signatures produced with it then stop verifying, and that is the expected result.
A brand that has no key yet receives a valid document whose
verificationMethod, assertionMethod and authentication are empty lists.
Plan for this case in your code.
#Pointing your domain
Three ways of hosting the document exist, and only one goes through this address on our side.
| Mode | Where the document lives | Resolved address |
|---|---|---|
| Platform | on our side, under our domain name | https://api.sealtrust.io/brand/{brand_id}/did.json |
| Self-hosted | on your side, you serve the file yourself | https://your-domain/.well-known/did.json |
| Delegated | on our side, under your domain name | https://your-domain/.well-known/did.json |
Platform mode is the one applied by default. Delegated mode is the one this page describes. The mode applied to your brand is set by SealTrust. Write to contact@sealtrust.io to change it.
#Errors
The body of an error response carries a detail field, except for the 400
described below. Every response carries an X-Request-Id header, the
identifier of your call on our side.
| Code | Condition | What to do |
|---|---|---|
| 400 | The domain name called is not declared with us. The response is plain text, Invalid host header, with no detail field. A request with no Host header, or with an empty Host header, receives the same 400. | Contact support to have your delegated domain declared before putting the identity into service. |
| 404 | No brand has declared this domain name as its identity domain in delegated mode. detail reads No DID Document for this host. | Check the domain name called. A brand in platform mode or in self-hosted mode never answers here, even if its domain points to us. |
| 422 | The identity document of the brand found cannot be built. detail carries the reason for the refusal. | Report it to support, indicating the domain name called. There is nothing you can fix on your side. |
| 429 | Too many calls from your network address. The response carries the Retry-After header, in seconds. | Wait the number of seconds given by Retry-After, then retry. Space out your calls: the counter is shared with all the other paths that have no limit of their own. |
| 500 | An unexpected error occurred while processing your call. detail reads Internal Server Error. | Retry. If the error persists, contact support with the value of X-Request-Id. |
#See also
GET /brand/{brand_id}/did.json, retrieve the public signing keys of a brand.GET /passport/{identifier}/vc/verify, check the signature of the credential 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.