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

Source: https://docs.sealtrust.io/en/reference/get-well-known-did-json/

---

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:

```http
GET https://id.exemple-sas.example/.well-known/did.json
```

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

> [!INFO] What delegated mode is for
> A decentralized identity of the form `did:web:id.exemple-sas.example`
> resolves, according to the `did:web` specification of the W3C, to
> `https://id.exemple-sas.example/.well-known/did.json`. The brand points this
> host name to us with a DNS record of type `CNAME`.
> The document is then served at this address, and the rotation of the keys of
> the brand is reflected there without it having to republish a file. The
> brand keeps its identity, since the domain name belongs to it and it can
> point it elsewhere.

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.

## Authorization

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.

> [!ATTENTION] Cross-origin sharing is not open to third parties
> The origins allowed to call the API from a browser are those of SealTrust. A
> third party web page that calls this endpoint from the browser will see its
> request refused by the browser itself. Resolve this document from your
> server.

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

> [!ATTENTION] Keep your own copy of the document
> The response forbids caching, with the header
> `Cache-Control: no-store, max-age=0`. A `did:web` resolver that respects
> this header therefore calls this address again at every verification of a
> passport, and consumes your budget of calls. Keep the document in your own
> system, and call this address again when you encounter a key identifier that
> your copy does not know. An identity document changes only when a key is
> created or revoked.

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

> [!INFO] The TypeScript SDK does not cover this endpoint
> No method of the `@sealtrust-io/sdk` package calls this address. The
> TypeScript example below uses `fetch`, with no dependency.

:::onglets
```bash title="curl"
curl -i https://id.exemple-sas.example/.well-known/did.json
```
```typescript
const 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());
}
```
```python
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.

```json
{
  "@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. |

> [!ATTENTION] The content type served is `application/json`
> Some `did:web` resolvers require the type `application/did+json` and refuse
> the response otherwise. Configure yours to accept `application/json`, or
> read the body without checking the content type.

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

> [!ATTENTION] A document served says nothing about the brand itself
> This endpoint publishes public keys. It does not say that a brand is
> legitimate, nor that a product is genuine. It gives you what you need to
> verify the signature of a passport yourself.

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

> [!ATTENTION] The domain must be declared before it is called
> A call carrying a domain name that is not declared with us receives a 400
> code. Have your delegated domain declared and checked by support before
> publishing your decentralized identity. A correct DNS record is not enough.

## 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`](/en/reference/get-brand-did-json/),
  retrieve the public signing keys of a brand.
- [`GET /passport/{identifier}/vc/verify`](/en/reference/get-passport-vc-verify/),
  check the signature of the credential and read the disclosed data.
- [Trust and proofs](/en/confiance-et-preuves/),
  what each proof establishes and how a third party redoes the verification.
