> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.adid.dev/llms.txt.
> For full documentation content, see https://docs.adid.dev/llms-full.txt.

# Creating a DID

##### When to use

You already have one DID (provisioned at signup), but you want a second — typically for **role separation** (one for personal credentials, one for agent operation, one for issuing). DIDs are cheap; create as many as your use case needs.

##### Before you begin

* You must be signed in.
* Your account quota allows the new DID (default: 25 DIDs/account; ops-tunable).

##### Steps

1. Navigate to **My DIDs** (`/dids`).
2. Click **Create DID** (top-right).
   3\. Choose key type:
   * **Ed25519** (default, recommended for credential signing) — see [§12.3 Cryptography](#cryptography).
   * **secp256k1** — useful if you need EVM-address compatibility or cross-chain interop.
3. (Optional) attach one or more **services** (each `{ id, type, serviceEndpoint }`) — these are written into the DID Document.
4. Click **Create**. The portal calls `POST /api/v1/dids` (`did.go:56`). The request body accepts only two fields: `keyType` (`"Ed25519"` or `"Secp256k1"`, defaults to `Ed25519`) and an optional `services[]` array.
5. Watch the status pill go from **Pending** → **Active** (typically 2–5 seconds on testnet).

##### API & SDK

<table>
  <tr>
    <th>cURL</th>

    <th>TypeScript SDK</th>
  </tr>

  <tr>
    <td>
      ```bash
      curl -X POST https://adid.dev/api/v1/dids \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{ "keyType":"Ed25519" }'
      ```
    </td>

    <td>
      ```ts
      const did = await client.createDID({
        keyType: 'ed25519',
        // services: [{ id: '#agent', type: 'AgentEndpoint', serviceEndpoint: 'https://agent.example.com' }],
      });
      console.log(did.did, did.txHash);
      ```
    </td>
  </tr>
</table>

Response:

```json
{
  "did":  "did:adi:0x4f3a8d2c...",
  "didDocument": { "@context": [...], "id": "did:adi:0x4f3a8d2c...", "verificationMethod": [...] },
  "status":  "active",
  "txHash":  "0xc2a8e9..."
}
```

##### Verify

The DID appears in `/dids`. Resolve it:

```bash
curl https://adid.dev/api/v1/dids/resolve/did:adi:0x4f3a8d2c...
```

##### Troubleshooting

| Code                     | Cause                | Fix                                                                 |
| ------------------------ | -------------------- | ------------------------------------------------------------------- |
| `429 QUOTA_EXCEEDED`     | More than 25 DIDs    | Deactivate unused ones, or contact ops to raise the quota.          |
| `503 CHAIN_UNREACHABLE`  | RPC down             | Retry; status `pending` will eventually reconcile when RPC returns. |
| Stuck on `pending` >60 s | Receipt not observed | See [§15.7](#rpc-issues).                                           |

#### 3.1.3. The DID Document Explained ##### Overview

A DID Document is a JSON-LD object that describes the DID's keys and endpoints. The shape conforms to [W3C DID Core 1.0 §5](https://www.w3.org/TR/did-core/#did-document-properties). On IDA, your DID Document is autogenerated when the DID is created and editable for service endpoints, delegates, and attributes.

##### Anatomy

```json
{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/ed25519-2020/v1"
  ],
  "id": "did:adi:0x9a2c...",
  "verificationMethod": [
    {
      "id": "did:adi:0x9a2c...#key-1",
      "type": "Ed25519VerificationKey2020",
      "controller": "did:adi:0x9a2c...",
      "publicKeyMultibase": "z6MkrJVnaZkeFzdQrK..."
    }
  ],
  "authentication":  ["did:adi:0x9a2c...#key-1"],
  "assertionMethod": ["did:adi:0x9a2c...#key-1"],
  "keyAgreement":    ["did:adi:0x9a2c...#key-1"],
  "capabilityInvocation": ["did:adi:0x9a2c...#key-1"],
  "capabilityDelegation": ["did:adi:0x9a2c...#key-1"],
  "service": [
    {
      "id":              "did:adi:0x9a2c...#didcomm-1",
      "type":            "DIDCommMessaging",
      "serviceEndpoint": "https://adid.dev/api/v1/didcomm/receive"
    }
  ]
}
```

| Property             | Purpose                                               | Mutability         |
| -------------------- | ----------------------------------------------------- | ------------------ |
| `@context`           | JSON-LD vocabulary, including the cryptographic suite | platform-managed   |
| `id`                 | The DID itself                                        | **immutable**      |
| `verificationMethod` | Public keys referenced by other properties            | rotatable (§3.1.5) |
| `authentication`     | Keys used to sign challenges (login, DIDComm)         | derived from VM    |
| `assertionMethod`    | Keys used to sign verifiable credentials              | derived from VM    |
| `keyAgreement`       | Keys used for ECDH (encrypted DIDComm)                | derived from VM    |
| `service`            | Endpoints (DIDComm, agent card, custom)               | editable (§3.1.4)  |

##### Why it matters

Every signature you make in the platform is verified against one of these public keys. If your DID Document is wrong (key revoked, endpoint stale), every relying party will see your verifications fail — silently. Treat the DID Document as your **public profile** for anything cryptographic.

##### How it fits

The DID Document is stored on chain in the **DIDRegistry** contract (see [§11.2](#did-registry)) and cached in the API's Postgres. The cache is invalidated by chain events (`DIDUpdated`, `DIDDeactivated`). Off-chain readers can either trust the cache (faster) or read directly from chain (more authoritative).

#### 3.1.4. Updating Service Endpoints ##### When to use

You want to add a service endpoint to your DID Document — typically a DIDComm receive URL, an agent card URL, or a custom service for a domain-specific protocol.

##### Before you begin

* You own the DID (you cannot edit someone else's).
* The endpoint URL is reachable from the public internet (or the receiver explicitly allows non-public URLs).

##### Steps

1. Open `/dids/:did` (DID Detail page).
2. Scroll to **Services**, click **Add service**.
   3\. Fill in:
   * **Type** — e.g. `DIDCommMessaging`, `LinkedDomains`, `AgentCard`.
   * **Service endpoint** — full URL.
   * **ID** (auto-generated, hash-suffixed).
3. Click **Save**. The portal calls `PUT /api/v1/dids/{did}` (`did.go:123`) with the merged DID Document. The API verifies you own the DID, signs the chain transaction (using your wallet's MPC key), and emits `DIDUpdated`.

##### API & SDK

<table>
  <tr>
    <th>cURL</th>

    <th>TypeScript SDK</th>
  </tr>

  <tr>
    <td>
      ```bash
      curl -X PUT https://adid.dev/api/v1/dids/did:adi:0x9a2c... \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{
          "service":[{
            "id":"#didcomm-2",
            "type":"DIDCommMessaging",
            "serviceEndpoint":"https://my-mediator.example.com/inbox"
          }]
        }'
      ```
    </td>

    <td>
      ```ts
      await client.updateDID('did:adi:0x9a2c...', {
        service: [{
          id: '#didcomm-2',
          type: 'DIDCommMessaging',
          serviceEndpoint: 'https://my-mediator.example.com/inbox',
        }],
      });
      ```
    </td>
  </tr>
</table>

##### Verify

```bash
curl https://adid.dev/api/v1/dids/resolve/did:adi:0x9a2c...
```

The new service object should be in the `service` array.

##### Troubleshooting

| Code                      | Cause                         | Fix                                                                      |
| ------------------------- | ----------------------------- | ------------------------------------------------------------------------ |
| `403 NOT_DID_OWNER`       | Your JWT does not own the DID | Sign in with the right account.                                          |
| `400 INVALID_SERVICE`     | Missing `type`, malformed URL | Re-check fields; `serviceEndpoint` must be a string or array of strings. |
| `409 SERVICE_ID_CONFLICT` | Duplicate `id`                | Choose a different `id` suffix.                                          |

#### 3.1.5. Key Rotation ##### When to use

* You suspect (or know) the key has been compromised.
* The platform's recommended rotation cadence (90 days for high-risk DIDs).
* You are migrating from secp256k1 to Ed25519 for credential signing.

##### Before you begin

* You own the DID.
* You will not need the *old* key for anything in the next 5 minutes (the rotation is immediate; old key is no longer authoritative).

##### Steps

1. Open `/dids/:did` and click **Rotate key**.
   2\. Confirm the key type for the new key (defaults to the same as the existing one).
2. Click **Rotate**. The portal calls `POST /api/v1/dids/{did}/rotate-key` (`did.go:197`).
3. Server-side:
   * Verifies caller owns the DID.
   * Generates a new keypair.
   * Builds a new DID Document with the new `verificationMethod`.
   * Submits `updateDID` to the DIDRegistry signed by the *current* (about-to-be-rotated) key. (After this transaction, the old key is no longer in `verificationMethod`.)
   * Inserts the old key into `did_key_history` for audit/replay diagnostics.

##### API & SDK

<table>
  <tr>
    <th>cURL</th>

    <th>TypeScript SDK</th>
  </tr>

  <tr>
    <td>
      ```bash
      curl -X POST https://adid.dev/api/v1/dids/did:adi:0x9a2c.../rotate-key \
        -H "Authorization: Bearer $ACCESS_TOKEN"
      ```
    </td>

    <td>
      ```ts
      await client.rotateKey('did:adi:0x9a2c...', 'Ed25519');
      // re-resolve to inspect the new verificationMethod
      const updated = await client.resolveDID('did:adi:0x9a2c...');
      ```
    </td>
  </tr>
</table>

##### Verify

Re-resolve the DID. The `verificationMethod` should now contain the new `publicKeyMultibase`. The `updated` field on the DID Document metadata should reflect the new timestamp.

##### Troubleshooting

| Code                         | Cause                                 | Fix                                            |
| ---------------------------- | ------------------------------------- | ---------------------------------------------- |
| `403 NOT_DID_OWNER`          | Caller is not the controller          | Sign in with the right account.                |
| `409 KEY_ROTATION_IN_FLIGHT` | A previous rotation has not finalised | Wait 30 s and retry.                           |
| `503 CHAIN_UNREACHABLE`      | RPC down                              | Retry; the API has idempotent retry semantics. |

> ⚠️ **Warning** — Any active credential **signed by the old key** remains valid as long as the old key is in the DID's key history. Verifiers SHOULD check `did_key_history` (exposed via the DID Document metadata) when validating older VCs. The Verification Engine in the portal does this automatically.

#### 3.1.6. Deactivating a DID ##### When to use

You no longer want anyone to trust this DID for new credentials/presentations. Deactivation is a soft-delete: the DID Document remains resolvable but is flagged `deactivated: true`, and the platform refuses to use it for issuance, presentation, or DIDComm.

##### Before you begin

* You own the DID.
* You have no active credentials *issued* by this DID that you intend to revoke first (revoke them before deactivating; afterwards the deactivated DID cannot be used to sign revocation entries).

##### Steps

1. Open `/dids/:did`.
2. Click **Deactivate** (red button at the bottom).
3. Confirm in the modal — type the DID into the confirmation field.
4. The portal calls `DELETE /api/v1/dids/{did}` (`did.go:166`). The handler emits `DIDDeactivated` on the registry.

##### API & SDK

<table>
  <tr>
    <th>cURL</th>

    <th>TypeScript SDK</th>
  </tr>

  <tr>
    <td>
      ```bash
      curl -X DELETE https://adid.dev/api/v1/dids/did:adi:0x9a2c... \
        -H "Authorization: Bearer $ACCESS_TOKEN"
      ```
    </td>

    <td>
      ```ts
      await client.deactivateDID('did:adi:0x9a2c...');
      ```
    </td>
  </tr>
</table>

##### Verify

Resolution still works, but the document metadata shows `deactivated: true`. Per [W3C DID Core 1.0 §5.2](https://www.w3.org/TR/did-core/#did-document-metadata), this is the canonical end state.

##### Troubleshooting

| Code                           | Cause                                       | Fix                                                           |
| ------------------------------ | ------------------------------------------- | ------------------------------------------------------------- |
| `403 NOT_DID_OWNER`            | Caller is not the controller                | Sign in correctly.                                            |
| `409 ALREADY_DEACTIVATED`      | DID was already deactivated                 | No action needed.                                             |
| `400 ACTIVE_CREDENTIALS_EXIST` | The DID is the issuer of active credentials | Revoke or transfer first; see [§4.4](#revocation-management). |

#### 3.1.7. Adding & Revoking Delegates ##### When to use

A delegate is a *different* DID authorised to act on behalf of yours for a specific purpose (e.g. an agent DID that may sign DIDComm messages on your behalf, or an organisational sub-DID that may issue credentials under your authority). Delegation is the cleaner alternative to sharing keys.

##### Before you begin

* You own the principal DID.
* The delegate's DID is resolvable.

##### Steps

1. Open `/dids/:did` and scroll to **Delegates**.
2. Click **Add delegate**.
   3\. Fill:
   * **Delegate** (`delegate`) — the delegate's DID or address, e.g. `did:adi:0x4f3a...`.
   * **Type** (`delegateType`) — e.g. `sigAuth`, `veriKey` (per ERC-1056 conventions).
   * **Validity** (`validity`) — number of seconds (default 86400 = 24 h).
3. Save. The portal calls `POST /api/v1/dids/{did}/delegates` (`did.go:290`). The body is the params object directly: `{ delegateType, delegate, validity }`.

##### API & SDK

<table>
  <tr>
    <th>cURL — add</th>

    <th>cURL — revoke</th>
  </tr>

  <tr>
    <td>
      ```bash
      curl -X POST https://adid.dev/api/v1/dids/did:adi:0x9a2c.../delegates \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{
          "delegateType":"sigAuth",
          "delegate":"did:adi:0x4f3a...",
          "validity": 86400
        }'
      ```
    </td>

    <td>
      ```bash
      curl -X DELETE https://adid.dev/api/v1/dids/did:adi:0x9a2c.../delegates \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{ "delegateType":"sigAuth", "delegate":"did:adi:0x4f3a..." }'
      ```
    </td>
  </tr>
</table>

##### Verify

```bash
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://adid.dev/api/v1/dids/did:adi:0x9a2c.../delegates
```

Returns an array `[{ delegate, delegateType, validUntil }]`.

##### Troubleshooting

| Code                     | Cause                                   | Fix                                     |
| ------------------------ | --------------------------------------- | --------------------------------------- |
| `400 INVALID_DELEGATE`   | DID format invalid                      | Check DID syntax.                       |
| `404 DELEGATE_NOT_FOUND` | DID not resolvable                      | Add the DID to a public registry first. |
| `409 DELEGATE_EXISTS`    | Same delegate + type already authorised | Revoke first, or use a different type.  |

#### 3.1.8. DID Attributes ##### When to use

You want to attach a small piece of structured data to your DID — e.g. a service URL, a name, a hash — without modifying the DID Document directly. This mirrors the ERC-1056 attribute mechanism: signed key/value pairs anchored on chain.

##### Before you begin

* You own the DID.
* The attribute name is short (≤32 bytes) and meaningful.

##### Steps

1. Open `/dids/:did` and scroll to **Attributes**.
2. Click **Set attribute**.
3. Enter:
   * **Name** — e.g. `did/svc/HubService`, `did/auth/email`.
   * **Value** — string or hex-encoded bytes.
   * **Validity** — seconds.
4. Save. Calls `POST /api/v1/dids/{did}/attributes` (`did.go:369`).

##### API & SDK

<table>
  <tr>
    <th>cURL — set</th>

    <th>cURL — revoke</th>
  </tr>

  <tr>
    <td>
      ```bash
      curl -X POST https://adid.dev/api/v1/dids/did:adi:0x9a2c.../attributes \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{
          "name":"did/svc/AgentCard",
          "value":"https://my.agent/.well-known/agent.json",
          "validity":2592000
        }'
      ```
    </td>

    <td>
      ```bash
      curl -X DELETE https://adid.dev/api/v1/dids/did:adi:0x9a2c.../attributes \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{
          "name":"did/svc/AgentCard"
        }'
      ```
    </td>
  </tr>
</table>

##### Verify

Resolve the DID; the attribute is exposed via the `didDocumentMetadata.attributes` array (or whatever the resolver returns — see [§3.1.9](#did-resolver)).

##### Troubleshooting

If a value isn't appearing, the most common cause is a stale cache. Force a re-resolve with:

```bash
curl https://adid.dev/api/v1/dids/resolve/did:adi:0x9a2c...?refresh=true
```

#### 3.1.9. Universal DID Resolver Tool ##### When to use

You need to fetch the DID Document for *any* DID — yours, someone else's, or a DID from a different method entirely. The universal resolver is the public, no-auth endpoint that does this.

##### Before you begin

Nothing required. The endpoint is unauthenticated.

##### Steps

1. Open the **DID Resolver** page in the portal (`/tools/did-resolver`) — or just call the endpoint directly.
2. Paste the DID into the input field.
3. Click **Resolve**.
4. The portal calls `GET /api/v1/dids/resolve/{did}` (`did.go:29`, registered at `router.go:74`).

##### Supported methods

| Method     | How it resolves                   | Source                         |
| ---------- | --------------------------------- | ------------------------------ |
| `did:adi`  | DIDRegistry.resolveDID()          | ADI chain (cached in Postgres) |
| `did:key`  | Multibase decode of public key    | local computation              |
| `did:web`  | HTTPS GET `/.well-known/did.json` | DNS + HTTPS                    |
| `did:ethr` | ERC-1056 EthereumDIDRegistry      | Ethereum mainnet RPC           |

##### API

<table>
  <tr>
    <th>cURL</th>

    <th>TypeScript SDK</th>
  </tr>

  <tr>
    <td>
      ```bash
      curl https://adid.dev/api/v1/dids/resolve/did:adi:0x9a2c...
      ```
    </td>

    <td>
      ```ts
      // Universal resolver — works for did:adi, did:key, did:web, did:ethr.
      const { didDocument, didDocumentMetadata } =
        await client.resolveAny('did:adi:0x9a2c...');

      // Or, for did:adi only (authenticated):
      // const didDocument = await client.resolveDID('did:adi:0x9a2c...');
      ```
    </td>
  </tr>
</table>

Response shape (per [W3C DID Resolution](https://www.w3.org/TR/did-core/#did-resolution)):

```json
{
  "didDocument":         { "@context":[...], "id":"did:adi:0x9a2c...", ... },
  "didDocumentMetadata": { "created":"...", "updated":"...", "deactivated":false, "txHash":"0x..." },
  "didResolutionMetadata":{ "contentType":"application/did+ld+json" }
}
```

##### Verify

Compare the returned key fingerprint against an out-of-band channel (e.g., the issuer's website, a printed business card). DID Documents are public, but their *binding* to a real-world entity is your responsibility.

##### Troubleshooting

| Code                  | Cause                      | Fix                                         |
| --------------------- | -------------------------- | ------------------------------------------- |
| `404 DID_NOT_FOUND`   | DID not registered         | Verify spelling; check method support.      |
| `502 UPSTREAM_ERROR`  | did:web HTTPS fetch failed | Check the host's `/.well-known/did.json`.   |
| `410 DID_DEACTIVATED` | DID is deactivated         | The document is still returned but flagged. |