feat: Phase 1 MVP — complete AgentIdP implementation
Implements all P0 features per OpenSpec change phase-1-mvp-implementation: - Agent Registry Service (CRUD) — full lifecycle management - OAuth 2.0 Token Service (Client Credentials flow) - Credential Management (generate, rotate, revoke) - Immutable Audit Log Service Tech: Node.js 18+, TypeScript 5.3+ strict, Express 4.18+, PostgreSQL 14+, Redis 7+ Standards: OpenAPI 3.0 specs, DRY/SOLID, zero `any` types Quality: 18 unit test suites, 244 tests passing, 97%+ coverage OpenAPI: 4 complete specs (14 endpoints total) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
687
docs/openapi/credential-management.yaml
Normal file
687
docs/openapi/credential-management.yaml
Normal file
@@ -0,0 +1,687 @@
|
||||
openapi: 3.0.3
|
||||
|
||||
info:
|
||||
title: SentryAgent.ai — Credential Management Service
|
||||
version: 1.0.0
|
||||
description: |
|
||||
The Credential Management Service provides secure generation, listing,
|
||||
rotation, and revocation of OAuth 2.0 client credentials for registered
|
||||
AI agents.
|
||||
|
||||
Each agent can hold multiple credentials simultaneously to support
|
||||
zero-downtime rotation. A credential consists of a `client_id` (= `agentId`)
|
||||
and a `client_secret`.
|
||||
|
||||
**Security model**:
|
||||
- `client_secret` is returned **once only** — at creation or rotation time.
|
||||
- Secrets are stored as a bcrypt hash; plain-text is never persisted.
|
||||
- An agent may only manage its own credentials unless the caller holds an
|
||||
admin-scoped token.
|
||||
- Rotating a credential immediately revokes the previous `client_secret`.
|
||||
|
||||
**Auth**: Bearer token (JWT) required on all endpoints.
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
- url: https://api.sentryagent.ai/v1
|
||||
description: Production server
|
||||
|
||||
tags:
|
||||
- name: Credential Management
|
||||
description: Generate, list, rotate, and revoke agent credentials
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT access token obtained via `POST /token`.
|
||||
Include as: `Authorization: Bearer <token>`
|
||||
|
||||
schemas:
|
||||
CredentialStatus:
|
||||
type: string
|
||||
description: |
|
||||
Lifecycle status of a credential.
|
||||
- `active`: Credential is valid and can be used to obtain tokens.
|
||||
- `revoked`: Credential has been explicitly revoked and is permanently invalid.
|
||||
enum:
|
||||
- active
|
||||
- revoked
|
||||
example: active
|
||||
|
||||
Credential:
|
||||
type: object
|
||||
description: |
|
||||
A credential record for an AI agent. The `clientSecret` is **never**
|
||||
returned in this schema — it is only returned once in `CredentialWithSecret`
|
||||
at the moment of creation or rotation.
|
||||
required:
|
||||
- credentialId
|
||||
- clientId
|
||||
- status
|
||||
- createdAt
|
||||
properties:
|
||||
credentialId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Immutable, system-assigned unique identifier for this credential.
|
||||
readOnly: true
|
||||
example: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
|
||||
clientId:
|
||||
type: string
|
||||
format: uuid
|
||||
description: >
|
||||
The `agentId` this credential belongs to. Equal to the `agentId`
|
||||
path parameter.
|
||||
readOnly: true
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
status:
|
||||
$ref: '#/components/schemas/CredentialStatus'
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: ISO 8601 timestamp when the credential was created.
|
||||
readOnly: true
|
||||
example: "2026-03-28T09:00:00.000Z"
|
||||
expiresAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: |
|
||||
ISO 8601 timestamp when the credential expires.
|
||||
`null` indicates the credential does not expire (valid until revoked).
|
||||
example: "2027-03-28T09:00:00.000Z"
|
||||
revokedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: >
|
||||
ISO 8601 timestamp when the credential was revoked.
|
||||
`null` if the credential has not been revoked.
|
||||
readOnly: true
|
||||
example: null
|
||||
|
||||
CredentialWithSecret:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Credential'
|
||||
- type: object
|
||||
description: |
|
||||
Extended credential record returned **only** at creation or rotation time.
|
||||
The `clientSecret` is shown once and never retrievable again.
|
||||
Store it securely immediately.
|
||||
required:
|
||||
- clientSecret
|
||||
properties:
|
||||
clientSecret:
|
||||
type: string
|
||||
description: |
|
||||
The plain-text client secret. **Shown once only** — store securely immediately.
|
||||
This value is not persisted in plain text on the server.
|
||||
format: password
|
||||
example: "sk_live_7f3a2b1c9d8e4f0a6b5c3d2e1f0a9b8c"
|
||||
|
||||
GenerateCredentialRequest:
|
||||
type: object
|
||||
description: |
|
||||
Optional request body for generating new credentials.
|
||||
If `expiresAt` is omitted, the credential does not expire.
|
||||
properties:
|
||||
expiresAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: |
|
||||
Optional ISO 8601 expiry timestamp for the credential.
|
||||
Must be a future date. If omitted, the credential has no expiry.
|
||||
example: "2027-03-28T09:00:00.000Z"
|
||||
|
||||
PaginatedCredentialsResponse:
|
||||
type: object
|
||||
description: Paginated list of credentials for an agent.
|
||||
required:
|
||||
- data
|
||||
- total
|
||||
- page
|
||||
- limit
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Credential'
|
||||
total:
|
||||
type: integer
|
||||
description: Total number of credentials for this agent.
|
||||
example: 3
|
||||
page:
|
||||
type: integer
|
||||
description: Current page number (1-based).
|
||||
example: 1
|
||||
limit:
|
||||
type: integer
|
||||
description: Number of items per page.
|
||||
example: 20
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
description: Standard error response envelope.
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
description: Machine-readable error code.
|
||||
example: "CREDENTIAL_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
description: Human-readable description of the error.
|
||||
example: "Credential with the specified ID was not found."
|
||||
details:
|
||||
type: object
|
||||
description: Optional structured details about the error.
|
||||
additionalProperties: true
|
||||
example: {}
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Missing or invalid Bearer token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "UNAUTHORIZED"
|
||||
message: "A valid Bearer token is required to access this resource."
|
||||
|
||||
Forbidden:
|
||||
description: Valid token but insufficient permissions.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to manage credentials for this agent."
|
||||
|
||||
AgentNotFound:
|
||||
description: The specified agent does not exist.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
|
||||
CredentialNotFound:
|
||||
description: The specified credential does not exist.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "CREDENTIAL_NOT_FOUND"
|
||||
message: "Credential with the specified ID was not found."
|
||||
|
||||
TooManyRequests:
|
||||
description: Rate limit exceeded.
|
||||
headers:
|
||||
X-RateLimit-Limit:
|
||||
schema:
|
||||
type: integer
|
||||
example: 100
|
||||
X-RateLimit-Remaining:
|
||||
schema:
|
||||
type: integer
|
||||
example: 0
|
||||
X-RateLimit-Reset:
|
||||
schema:
|
||||
type: integer
|
||||
example: 1743155400
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "RATE_LIMIT_EXCEEDED"
|
||||
message: "Too many requests. Please retry after the rate limit window resets."
|
||||
|
||||
InternalServerError:
|
||||
description: Unexpected server error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
message: "An unexpected error occurred. Please try again later."
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/agents/{agentId}/credentials:
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
description: The unique UUID identifier of the agent.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
|
||||
post:
|
||||
operationId: generateCredential
|
||||
tags:
|
||||
- Credential Management
|
||||
summary: Generate new credentials for an agent
|
||||
description: |
|
||||
Generates a new `client_id` + `client_secret` credential pair for the
|
||||
specified agent.
|
||||
|
||||
**Important**: The `clientSecret` is returned **once only** in this response.
|
||||
It is not stored in plain text on the server and cannot be retrieved later.
|
||||
Store it securely immediately (e.g. in a secrets manager).
|
||||
|
||||
An agent may hold multiple active credentials simultaneously. This supports
|
||||
zero-downtime rotation: generate a new credential, update all consumers,
|
||||
then revoke the old one.
|
||||
|
||||
**Restrictions**:
|
||||
- The agent must be in `active` status.
|
||||
- An agent may manage its own credentials via a self-issued token.
|
||||
- Managing another agent's credentials requires an admin-scoped token.
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenerateCredentialRequest'
|
||||
example:
|
||||
expiresAt: "2027-03-28T09:00:00.000Z"
|
||||
responses:
|
||||
'201':
|
||||
description: |
|
||||
Credential generated successfully.
|
||||
**Save the `clientSecret` immediately — it will not be shown again.**
|
||||
headers:
|
||||
X-RateLimit-Limit:
|
||||
schema:
|
||||
type: integer
|
||||
example: 100
|
||||
X-RateLimit-Remaining:
|
||||
schema:
|
||||
type: integer
|
||||
example: 99
|
||||
X-RateLimit-Reset:
|
||||
schema:
|
||||
type: integer
|
||||
example: 1743155400
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CredentialWithSecret'
|
||||
example:
|
||||
credentialId: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
|
||||
clientId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
clientSecret: "sk_live_7f3a2b1c9d8e4f0a6b5c3d2e1f0a9b8c"
|
||||
status: "active"
|
||||
createdAt: "2026-03-28T09:00:00.000Z"
|
||||
expiresAt: "2027-03-28T09:00:00.000Z"
|
||||
revokedAt: null
|
||||
'400':
|
||||
description: Invalid request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
details:
|
||||
field: "expiresAt"
|
||||
reason: "expiresAt must be a future date-time."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
description: Insufficient permissions or agent is not active.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
forbidden:
|
||||
summary: Insufficient permissions
|
||||
value:
|
||||
code: "FORBIDDEN"
|
||||
message: "You do not have permission to manage credentials for this agent."
|
||||
agentNotActive:
|
||||
summary: Agent not in active status
|
||||
value:
|
||||
code: "AGENT_NOT_ACTIVE"
|
||||
message: "Credentials can only be generated for active agents."
|
||||
details:
|
||||
agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
status: "suspended"
|
||||
'404':
|
||||
$ref: '#/components/responses/AgentNotFound'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
get:
|
||||
operationId: listCredentials
|
||||
tags:
|
||||
- Credential Management
|
||||
summary: List credentials for an agent
|
||||
description: |
|
||||
Returns a paginated list of all credentials (active and revoked) for the
|
||||
specified agent. The `clientSecret` is **never** returned in list responses.
|
||||
|
||||
Results are ordered by `createdAt` descending (most recent first).
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: Page number (1-based). Defaults to `1`.
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
default: 1
|
||||
example: 1
|
||||
- name: limit
|
||||
in: query
|
||||
description: Number of results per page. Defaults to `20`, maximum `100`.
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
default: 20
|
||||
example: 20
|
||||
- name: status
|
||||
in: query
|
||||
description: Filter credentials by status.
|
||||
required: false
|
||||
schema:
|
||||
$ref: '#/components/schemas/CredentialStatus'
|
||||
responses:
|
||||
'200':
|
||||
description: Credential list returned successfully.
|
||||
headers:
|
||||
X-RateLimit-Limit:
|
||||
schema:
|
||||
type: integer
|
||||
example: 100
|
||||
X-RateLimit-Remaining:
|
||||
schema:
|
||||
type: integer
|
||||
example: 98
|
||||
X-RateLimit-Reset:
|
||||
schema:
|
||||
type: integer
|
||||
example: 1743155400
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedCredentialsResponse'
|
||||
example:
|
||||
data:
|
||||
- credentialId: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
|
||||
clientId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
status: "active"
|
||||
createdAt: "2026-03-28T09:00:00.000Z"
|
||||
expiresAt: "2027-03-28T09:00:00.000Z"
|
||||
revokedAt: null
|
||||
- credentialId: "d8e7f6a5-b4c3-2109-edcb-a98765432109"
|
||||
clientId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
status: "revoked"
|
||||
createdAt: "2026-01-15T08:00:00.000Z"
|
||||
expiresAt: null
|
||||
revokedAt: "2026-03-28T08:59:00.000Z"
|
||||
total: 2
|
||||
page: 1
|
||||
limit: 20
|
||||
'400':
|
||||
description: Invalid query parameters.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Invalid query parameter value."
|
||||
details:
|
||||
field: "status"
|
||||
reason: "Must be 'active' or 'revoked'."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/AgentNotFound'
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/agents/{agentId}/credentials/{credentialId}/rotate:
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
description: The unique UUID identifier of the agent.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
- name: credentialId
|
||||
in: path
|
||||
description: The unique UUID identifier of the credential to rotate.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
|
||||
|
||||
post:
|
||||
operationId: rotateCredential
|
||||
tags:
|
||||
- Credential Management
|
||||
summary: Rotate a credential
|
||||
description: |
|
||||
Rotates an existing credential by:
|
||||
1. Immediately revoking the current `client_secret`.
|
||||
2. Generating and returning a new `client_secret` for the same `credentialId`.
|
||||
|
||||
The `credentialId` remains the same after rotation; only the secret changes.
|
||||
The new `clientSecret` is returned **once only** and must be stored securely.
|
||||
|
||||
**Use case**: Periodic secret rotation or emergency rotation after
|
||||
credential compromise.
|
||||
|
||||
Only `active` credentials can be rotated. Attempting to rotate a `revoked`
|
||||
credential returns `409 Conflict`.
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenerateCredentialRequest'
|
||||
example:
|
||||
expiresAt: "2028-03-28T09:00:00.000Z"
|
||||
responses:
|
||||
'200':
|
||||
description: |
|
||||
Credential rotated successfully.
|
||||
**Save the new `clientSecret` immediately — it will not be shown again.**
|
||||
The previous secret is permanently invalidated.
|
||||
headers:
|
||||
X-RateLimit-Limit:
|
||||
schema:
|
||||
type: integer
|
||||
example: 100
|
||||
X-RateLimit-Remaining:
|
||||
schema:
|
||||
type: integer
|
||||
example: 97
|
||||
X-RateLimit-Reset:
|
||||
schema:
|
||||
type: integer
|
||||
example: 1743155400
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CredentialWithSecret'
|
||||
example:
|
||||
credentialId: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
|
||||
clientId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
clientSecret: "sk_live_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d"
|
||||
status: "active"
|
||||
createdAt: "2026-03-28T09:00:00.000Z"
|
||||
expiresAt: "2028-03-28T09:00:00.000Z"
|
||||
revokedAt: null
|
||||
'400':
|
||||
description: Invalid request body.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "VALIDATION_ERROR"
|
||||
message: "Request validation failed."
|
||||
details:
|
||||
field: "expiresAt"
|
||||
reason: "expiresAt must be a future date-time."
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Agent or credential not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
agentNotFound:
|
||||
summary: Agent not found
|
||||
value:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
credentialNotFound:
|
||||
summary: Credential not found
|
||||
value:
|
||||
code: "CREDENTIAL_NOT_FOUND"
|
||||
message: "Credential with the specified ID was not found."
|
||||
'409':
|
||||
description: Credential is already revoked and cannot be rotated.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "CREDENTIAL_ALREADY_REVOKED"
|
||||
message: "Revoked credentials cannot be rotated. Generate a new credential instead."
|
||||
details:
|
||||
credentialId: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
|
||||
revokedAt: "2026-03-20T10:00:00.000Z"
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
|
||||
/agents/{agentId}/credentials/{credentialId}:
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
description: The unique UUID identifier of the agent.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
- name: credentialId
|
||||
in: path
|
||||
description: The unique UUID identifier of the credential to revoke.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
|
||||
|
||||
delete:
|
||||
operationId: revokeCredential
|
||||
tags:
|
||||
- Credential Management
|
||||
summary: Revoke a credential
|
||||
description: |
|
||||
Permanently revokes a credential, immediately preventing it from being
|
||||
used to obtain new tokens.
|
||||
|
||||
**Effects of revocation**:
|
||||
- The credential's status is set to `revoked`.
|
||||
- Any tokens issued using this credential remain valid until they expire
|
||||
naturally (token revocation is handled separately via `POST /token/revoke`).
|
||||
- The credential record is retained for audit purposes.
|
||||
- This operation is **irreversible** — a revoked credential cannot be re-activated.
|
||||
|
||||
Revoking an already-revoked credential returns `409 Conflict`.
|
||||
responses:
|
||||
'204':
|
||||
description: Credential revoked successfully. No response body.
|
||||
headers:
|
||||
X-RateLimit-Limit:
|
||||
schema:
|
||||
type: integer
|
||||
example: 100
|
||||
X-RateLimit-Remaining:
|
||||
schema:
|
||||
type: integer
|
||||
example: 96
|
||||
X-RateLimit-Reset:
|
||||
schema:
|
||||
type: integer
|
||||
example: 1743155400
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Agent or credential not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
examples:
|
||||
agentNotFound:
|
||||
summary: Agent not found
|
||||
value:
|
||||
code: "AGENT_NOT_FOUND"
|
||||
message: "Agent with the specified ID was not found."
|
||||
credentialNotFound:
|
||||
summary: Credential not found
|
||||
value:
|
||||
code: "CREDENTIAL_NOT_FOUND"
|
||||
message: "Credential with the specified ID was not found."
|
||||
'409':
|
||||
description: Credential is already revoked.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
example:
|
||||
code: "CREDENTIAL_ALREADY_REVOKED"
|
||||
message: "This credential has already been revoked."
|
||||
details:
|
||||
credentialId: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
|
||||
revokedAt: "2026-03-20T10:00:00.000Z"
|
||||
'429':
|
||||
$ref: '#/components/responses/TooManyRequests'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
Reference in New Issue
Block a user