PCA API Reference
REST endpoint reference for issuing, listing, fetching, revoking, and verifying Policy Commitment Attestation credentials. Plus the three public did:web and Status List 2021 endpoints used by external verifiers.
This page documents every endpoint in the Policy Commitment Attestation (PCA) surface. For the conceptual overview, see PCA Overview. For who-are-PCA-agents-anyway and how to register them, see Agent Identity for Real Teams.
Authentication
All endpoints under /api/agent/attestations/* require an API key passed as Authorization: Bearer dv_live_…. The key must have the appropriate pca:* permission for the action (see the table at the end of each endpoint section).
The three public endpoints (/tenants/[id]/did.json, /agents/[slug]/did.json, /api/governance/status-lists/[id]) are anonymous — required by the W3C did:web and Status List 2021 specs to be publicly accessible.
POST /api/agent/attestations
Issue a new signed PolicyCommitmentCredential. Requires pca:write.
Request
{
"subjectDid": "did:web:dictiva.com:agents:code-reviewer",
"statementId": "550e8400-e29b-41d4-a716-446655440000",
"statementRef": {
"uri": "https://dictiva.com/statements/STMT-AI-001",
"version": 1,
"hash": "sha256:b8a1d2c3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1"
},
"commitmentTier": "T4",
"targetTier": "T5",
"scope": {
"tenants": ["dictiva"],
"entityTypes": ["process", "assembly"],
"actionClasses": ["read", "propose_edit"],
"refusalRules": []
},
"evidence": [
{ "evidenceKind": "adr", "reference": "ADR-040" },
{ "evidenceKind": "memory_file", "reference": "memory/pca.md" },
{ "evidenceKind": "semantic_similarity_report","reference": "https://example.com/reports/r1.json", "digest": { "sha256": "sha256:9f8e..." } }
],
"chainOfAuthority": {
"issuedBy": "did:web:dictiva.com:users:alice",
"approvedBy": ["did:web:dictiva.com:users:bob"]
},
"revalidationInterval": "P90D"
}
Required fields
| Field | Type | Notes |
|---|---|---|
subjectDid | did:web: URI | Must reference an existing agent_identities row in your tenant. |
statementId | UUID | Local statement UUID. Resolve via search_statements MCP tool or GET /api/agent/statements. |
statementRef.uri | URL | Display URL — what the credential exposes. Free-form. |
statementRef.version | integer ≥ 1 | Pinned version. |
statementRef.hash | sha256:<hex> | Canonical hash of the statement body. The issuer recomputes against the current body and rejects on drift (statement_hash_mismatch). |
commitmentTier | T1 … T6 | Claimed maturity. Cumulative floors apply. |
evidence | EvidenceRef[] | Must satisfy cumulative floors for the claimed tier (see Maturity Ladder). |
chainOfAuthority.issuedBy | did:web: URI | Human or system that authorized the issuance. |
Optional fields
| Field | Default |
|---|---|
targetTier | (none) |
scope | {} (T5+ requires non-empty) |
revalidationInterval | P90D (ISO-8601 duration) |
chainOfAuthority.approvedBy | (none) |
Response — 201 Created
The full attestation row including the signed VC. Key fields:
{
"id": "uuid",
"tenantId": "uuid",
"agentIdentityId": "uuid",
"subjectDid": "did:web:...",
"statementId": "uuid",
"statementVersion": 1,
"statementHash": "sha256:...",
"commitmentTier": "T4",
"credential": { /* full signed W3C VC */ },
"issuedAt": "2026-04-25T...",
"expiresAt": "2026-07-24T...",
"revalidationInterval": "P90D",
"signatureAlgorithm": "ed25519",
"statusListUrl": "https://dictiva.com/api/governance/status-lists/...",
"statusListIndex": 42,
"evidence": [ /* per-row evidence */ ]
}
Errors
| Code | HTTP | Cause |
|---|---|---|
invalid_body | 400 | Zod validation failure — see details array. |
agent_not_found | 404 | subjectDid not in this tenant's agent_identities. |
statement_not_found | 404 | statementId doesn't exist or is soft-deleted. |
statement_version_not_found | 404 | The version doesn't exist for that statement. |
statement_hash_mismatch | 422 | Supplied hash doesn't match canonical hash at this version (drift). |
Tier floor not satisfied | 500 | Evidence doesn't satisfy cumulative floors. (Caller should validate first.) |
GET /api/agent/attestations
Paginated list. Requires pca:read.
Query params
| Param | Type | Default |
|---|---|---|
subject_did | string | — (filter) |
statement_id | UUID | — (filter) |
tier | T1 … T6 | — (filter) |
include_revoked | true / false | false |
page | int ≥ 1 | 1 |
per_page | int 1–100 | 20 |
Response — 200
{
"data": [ /* PolicyCommitmentAttestation[] */ ],
"pagination": {
"page": 1,
"perPage": 20,
"total": 137,
"totalPages": 7
}
}
GET /api/agent/attestations/:id
Single attestation by ID. Requires pca:read. Returns the same shape as a single element of the list response.
Errors
| Code | HTTP | Cause |
|---|---|---|
invalid_id | 400 | :id path parameter isn't a UUID. |
attestation_not_found | 404 | ID unknown, in a different tenant, or soft-deleted. |
POST /api/agent/attestations/:id/revoke
Revoke. Requires pca:revoke — highest-privilege action; only Owner / Admin / Policy Owner roles have it by default.
Request
{ "reason": "Agent decommissioned; credentials no longer authoritative." }
Response — 200
The updated attestation row, now showing:
revokedAt— server timestamprevokedReason— the reason providedcredential.credentialStatusstill references the same status list URL + index, but the bit at that index is now flipped to 1
Errors
| Code | HTTP | Cause |
|---|---|---|
attestation_not_found | 404 | Same as GET /:id. |
attestation_already_revoked | 409 | revoked_at is already set. Idempotency by design. |
POST /api/agent/attestations/verify
Stateless verification. Requires pca:read. Always returns HTTP 200 — the verified boolean in the response body is the signal.
Request
{ "credential": { /* signed W3C VC */ } }
Response — 200
{
"verified": true,
"claimedTier": "T4",
"effectiveTier": "T4",
"revocationStatus": "active",
"withinValidityWindow": true,
"evidenceChecks": [
{ "evidenceIndex": 0, "evidenceKind": "adr", "reference": "ADR-040", "expected": null, "actual": null, "resolved": false, "matched": true },
{ "evidenceIndex": 1, "evidenceKind": "memory_file", "reference": "memory/pca.md", "expected": null, "actual": null, "resolved": false, "matched": true },
{ "evidenceIndex": 2, "evidenceKind": "semantic_similarity_report", "reference": "https://example.com/r1.json", "expected": "sha256:9f...", "actual": "sha256:9f...", "resolved": true, "matched": true }
]
}
Five checks executed
- Issuer resolution — credential's
issuerresolved via tenant'stenant_signing_keys. Failure →issuer_unknown. - Cryptographic verification — VC Data Integrity / Ed25519 via DigitalBazaar. Failure →
cryptographic_verification_failed. - Validity window —
validFrom ≤ now ≤ validUntil. Failure →outside_validity_window. - Revocation lookup — Status List 2021 bit (preferred) → soft
revoked_atcolumn (fallback). Failure →credential_revoked. - Evidence digest resolution — for each evidence entry with a
digest.sha256claim, fetch the artifact (HTTPS only) and recompute SHA-256. Drift →evidence_digest_mismatch.
revocationStatus semantics
| Value | Meaning |
|---|---|
active | Credential is in our DB or its status list bit is 0. |
revoked | Status list bit is 1, OR the credential is in our DB with revoked_at set. |
unknown | Credential isn't in our DB and has no credentialStatus field (legacy / external issuer). The verifier can't determine revocation state. |
When revocationStatus === "unknown", the credential may still verify (verified: true) if the signature + window check pass — but operators should treat it as lower confidence than active.
Public endpoints (no auth)
GET /tenants/[id]/did.json
Returns the tenant's W3C DID document, used to verify any credential issued by that tenant. id must be a UUID.
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "did:web:dictiva.com:tenants:550e8400-e29b-41d4-a716-446655440000",
"verificationMethod": [
{
"id": "did:web:dictiva.com:tenants:550e8400-...#key-1",
"type": "Ed25519VerificationKey2020",
"controller": "did:web:dictiva.com:tenants:550e8400-...",
"publicKeyMultibase": "z6Mk..."
}
],
"assertionMethod": ["did:web:dictiva.com:tenants:550e8400-...#key-1"]
}
Headers: Content-Type: application/did+json, Cache-Control: public, max-age=300.
GET /agents/[slug]/did.json
Returns the agent's W3C DID document. Optional verificationMethod array — populated only when the agent self-signs (most agents are subjects only and have no key material).
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "did:web:dictiva.com:agents:code-reviewer",
"alsoKnownAs": ["mailto:code-reviewer@agents.dictiva.com"],
"service": [
{
"id": "did:web:dictiva.com:agents:code-reviewer#dictiva-workbench",
"type": "DictivaWorkbench",
"serviceEndpoint": "https://dictiva.com/workbench/teams/code-reviewer"
}
],
"selfDescribedRole": "PR code quality, security, conventions",
"model": "claude-opus-4-7"
}
GET /api/governance/status-lists/[id]
Returns the signed Status List 2021 credential. Verifiers fetch this to check whether a specific bit (the credential's statusListIndex) is flipped to 1 (revoked) or remains 0 (active).
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/vc/status-list/2021/v1"
],
"id": "https://dictiva.com/api/governance/status-lists/abc...",
"type": ["VerifiableCredential", "StatusList2021Credential"],
"issuer": "did:web:dictiva.com:tenants:...",
"validFrom": "2026-04-25T...",
"credentialSubject": {
"id": "https://dictiva.com/api/governance/status-lists/abc...#list",
"type": "StatusList2021",
"statusPurpose": "revocation",
"encodedList": "H4sIAAAAAAAA…"
},
"proof": {
"type": "Ed25519Signature2020",
"created": "...",
"verificationMethod": "...",
"proofPurpose": "assertionMethod",
"proofValue": "z..."
}
}
The encodedList is a 131,072-bit (16 KiB) bitstring, gzip-compressed and base64url-encoded. Verifiers decode, locate their bit at statusListIndex, and check whether it's set.
Headers: Content-Type: application/ld+json, Cache-Control: public, max-age=60.
Rate limits
Per-tenant, fixed-window 60s, Redis-backed:
| Plan | Requests/min |
|---|---|
| Community | 5 |
| Professional | 20 |
| Business | 100 |
| Enterprise | 500 |
The MCP Governance Server (which wraps these endpoints) requires Business+ entitlement. See the MCP Governance Server Guide.
OpenAPI
The full machine-readable spec is at GET /api/openapi.json. Interactive Scalar UI at GET /api/docs.
See also
- PCA Overview — concepts + maturity ladder
- Agent Identity for Real Teams — what counts as a PCA-grade agent + AGENTS.md templates
- MCP Governance Server Guide — agent-facing tools
- Agent API Integration Guide — quickstart
- API Keys Guide — Bearer-token authentication
- RBAC Guide — permissions + roles
- Public spec: policycommitment.dictiva.com