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

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

---

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:

```http
GET https://api.sealtrust.io/v1/brand/{brand_id}/did.json
```

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

> [!INFO] You do not need us in order to verify
> This endpoint exists for exactly that: giving you the public keys so that you
> check a signature yourself, with the tool of your choice. The endpoint
> `GET /v1/passport/{identifier}/vc/verify` runs the same check on our side.
> Both paths exist and are independent.

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

The document contains public keys only. No private key ever leaves our system,
and none can be reconstructed from this document.

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

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

> [!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://api.sealtrust.io/v1/brand/4242/did.json
```
```typescript
const 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());
}
```
```python
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.

```json
{
  "@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`](/en/reference/get-well-known-did-json/),
  serve a brand's identity document on its own domain.
- [`GET /passport/{identifier}/vc`](/en/reference/get-passport-vc/),
  retrieve the signed credential of the passport, in SD-JWT-VC format.
- [`GET /passport/{identifier}/vc/verify`](/en/reference/get-passport-vc-verify/),
  check the credential's signature and read the disclosed data.
- [Trust and proofs](/en/confiance-et-preuves/),
  what each proof establishes and how a third party redoes the verification.
