# W3C Decentralized Identifiers (DIDs) — Specification **Workstream**: 2 of 6 **Phase**: 3 — Enterprise **Author**: Virtual Architect **Date**: 2026-03-29 --- ## Overview Issue a W3C `did:web` identifier for every registered agent and serve DID Documents over HTTPS. The AgentIdP instance itself has a root DID Document at `/.well-known/did.json`. Each agent has an individual DID Document at `/agents/:id/did`. A DID resolution endpoint wraps the standard resolution workflow. Agent cards in AGNTCY format are derivable from DID Documents. The `did:web` method resolves to `https:///.well-known/did.json` (instance) and `https:///agents//did` (per-agent). All DID Documents are W3C DID Core 1.0 compliant. --- ## API Endpoints ### GET /.well-known/did.json Root DID Document for the AgentIdP instance. No authentication required — this is a public discovery endpoint. ```yaml GET /.well-known/did.json No authentication required Responses: 200 OK: Content-Type: application/json schema: type: object description: W3C DID Core 1.0 compliant DID Document required: [id, "@context", verificationMethod, authentication] properties: "@context": type: array items: type: string example: - "https://www.w3.org/ns/did/v1" - "https://w3id.org/security/suites/jws-2020/v1" id: type: string description: DID for this AgentIdP instance example: "did:web:idp.sentryagent.ai" controller: type: string example: "did:web:idp.sentryagent.ai" verificationMethod: type: array items: $ref: '#/components/schemas/VerificationMethod' authentication: type: array items: type: string description: References to verification methods for authentication assertionMethod: type: array items: type: string service: type: array items: $ref: '#/components/schemas/DIDService' example: "@context": - "https://www.w3.org/ns/did/v1" id: "did:web:idp.sentryagent.ai" controller: "did:web:idp.sentryagent.ai" verificationMethod: - id: "did:web:idp.sentryagent.ai#key-1" type: "JsonWebKey2020" controller: "did:web:idp.sentryagent.ai" publicKeyJwk: kty: "EC" crv: "P-256" x: "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU" y: "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" authentication: - "did:web:idp.sentryagent.ai#key-1" service: - id: "did:web:idp.sentryagent.ai#agent-registry" type: "AgentIdentityProvider" serviceEndpoint: "https://idp.sentryagent.ai" 500 Internal Server Error: schema: $ref: '#/components/schemas/ErrorResponse' ``` --- ### GET /agents/:id/did Per-agent DID Document. No authentication required — DID Documents are public. ```yaml GET /agents/{agentId}/did No authentication required Path Parameters: agentId: type: string description: Agent ID Responses: 200 OK: Content-Type: application/json schema: type: object description: W3C DID Core 1.0 compliant per-agent DID Document example: "@context": - "https://www.w3.org/ns/did/v1" - "https://w3id.org/agntcy/v1" id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890" controller: "did:web:idp.sentryagent.ai" verificationMethod: - id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#key-1" type: "JsonWebKey2020" controller: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890" publicKeyJwk: kty: "EC" crv: "P-256" x: "abc123" y: "def456" authentication: - "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#key-1" service: - id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#agent-card" type: "AgentCard" serviceEndpoint: "https://idp.sentryagent.ai/agents/agt_01HXK7Z9P3FKWABCDEF67890/did/card" agntcy: agentId: "agt_01HXK7Z9P3FKWABCDEF67890" agentType: "orchestrator" capabilities: - "task-planning" - "tool-use" deploymentEnv: "production" owner: "acme-ai" version: "1.2.0" 404 Not Found: schema: $ref: '#/components/schemas/ErrorResponse' example: code: "AGENT_NOT_FOUND" message: "Agent not found" 410 Gone: schema: $ref: '#/components/schemas/ErrorResponse' example: code: "AGENT_DECOMMISSIONED" message: "Agent has been decommissioned — DID Document is no longer active" ``` --- ### GET /agents/:id/did/resolve DID resolution endpoint: resolves any `did:web` DID and returns the DID resolution result in W3C DID Resolution format. This enables external systems to use AgentIdP as a resolver for agent DIDs. Authentication required (`agents:read` scope). ```yaml GET /agents/{agentId}/did/resolve Authorization: Bearer Path Parameters: agentId: type: string Responses: 200 OK: Content-Type: application/ld+json;profile="https://w3id.org/did-resolution" schema: type: object required: [didDocument, didDocumentMetadata, didResolutionMetadata] properties: didDocument: type: object description: The resolved DID Document didDocumentMetadata: type: object properties: created: type: string format: date-time updated: type: string format: date-time deactivated: type: boolean didResolutionMetadata: type: object properties: contentType: type: string example: "application/did+ld+json" retrieved: type: string format: date-time example: didDocument: "@context": ["https://www.w3.org/ns/did/v1"] id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890" didDocumentMetadata: created: "2026-03-29T12:00:00Z" updated: "2026-03-29T12:00:00Z" deactivated: false didResolutionMetadata: contentType: "application/did+ld+json" retrieved: "2026-03-29T14:00:00Z" 401 Unauthorized: schema: $ref: '#/components/schemas/ErrorResponse' 404 Not Found: schema: $ref: '#/components/schemas/ErrorResponse' ``` --- ### GET /agents/:id/did/card AGNTCY-format agent card derived from DID Document. Returns a JSON object representing the agent's identity and capabilities in the AGNTCY agent card format. No authentication required. ```yaml GET /agents/{agentId}/did/card No authentication required Responses: 200 OK: Content-Type: application/json schema: type: object description: AGNTCY-format agent card properties: did: type: string name: type: string agentType: type: string capabilities: type: array items: type: string owner: type: string version: type: string deploymentEnv: type: string identityProvider: type: string description: DID of the issuing AgentIdP instance issuedAt: type: string format: date-time example: did: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890" name: "acme-orchestrator" agentType: "orchestrator" capabilities: ["task-planning", "tool-use"] owner: "acme-ai" version: "1.2.0" deploymentEnv: "production" identityProvider: "did:web:idp.sentryagent.ai" issuedAt: "2026-03-29T12:00:00Z" 404 Not Found: schema: $ref: '#/components/schemas/ErrorResponse' ``` --- ## Database Schema Changes ### New Table: agent_did_keys Stores the public/private key pair used to sign each agent's DID Document. The private key is stored in Vault; only the public key JWK is stored in PostgreSQL. ```sql CREATE TABLE agent_did_keys ( key_id VARCHAR(40) PRIMARY KEY, agent_id VARCHAR(40) NOT NULL UNIQUE REFERENCES agents(agent_id), organization_id VARCHAR(40) NOT NULL REFERENCES organizations(organization_id), public_key_jwk JSONB NOT NULL, vault_key_path VARCHAR(255) NOT NULL, -- Vault path where private key is stored key_type VARCHAR(20) NOT NULL DEFAULT 'EC', curve VARCHAR(10) NOT NULL DEFAULT 'P-256', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), rotated_at TIMESTAMPTZ, CONSTRAINT agent_did_keys_key_type_check CHECK (key_type IN ('EC', 'RSA')) ); CREATE INDEX idx_agent_did_keys_agent_id ON agent_did_keys(agent_id); CREATE INDEX idx_agent_did_keys_org_id ON agent_did_keys(organization_id); ``` ### New Column: agents.did ```sql ALTER TABLE agents ADD COLUMN did VARCHAR(255), ADD COLUMN did_created_at TIMESTAMPTZ; -- Populated automatically on agent creation -- Example value: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890" ``` --- ## Configuration | Environment Variable | Description | Default | |---------------------|-------------|---------| | `DID_WEB_DOMAIN` | Domain name for `did:web` construction | Required — derived from `HOST` if not set | | `DID_KEY_TYPE` | Cryptographic key type for DID keys | `EC` | | `DID_KEY_CURVE` | Elliptic curve for EC keys | `P-256` | | `DID_DOCUMENT_CACHE_TTL_SECONDS` | How long to cache DID Documents in Redis | `300` | --- ## Dependencies | Package | Version | Purpose | |---------|---------|---------| | `did-resolver` | `^4.1.0` | W3C DID resolution interface | | `web-did-resolver` | `^2.0.27` | DID:WEB method resolver | --- ## Security Considerations - DID Documents are public endpoints — no authentication, no rate-limit-sensitive data exposed - Private keys for DID signing are stored in Vault; never written to PostgreSQL - DID Document cache in Redis has a TTL — stale documents are evicted automatically - Decommissioned agents return HTTP 410 Gone with `deactivated: true` in DID Document metadata - DID rotation: when a credential is rotated, the DID Document key can optionally be rotated; the old key is retained in history - `GET /agents/:id/did/card` exposes only data already present in the agent registration — no new sensitive fields --- ## Acceptance Criteria - [ ] Every new agent registration automatically generates a `did:web` DID and key pair - [ ] Root DID Document at `/.well-known/did.json` is W3C DID Core 1.0 compliant (validated by `did-resolver`) - [ ] Per-agent DID Document returns correct `did:web` identifier and public key JWK - [ ] DID resolution endpoint returns W3C DID Resolution format - [ ] Decommissioned agent DID Document returns 410 Gone with `deactivated: true` - [ ] Agent card at `/agents/:id/did/card` matches AGNTCY agent card format - [ ] Private keys never appear in any API response or log - [ ] TypeScript strict, zero `any`, >80% test coverage on DIDService