openapi: 3.0.3

info:
  title: SentryAgent.ai — Agent Registry Service
  version: 1.0.0
  description: |
    The Agent Registry Service provides full lifecycle management for AI agent
    identities on the SentryAgent.ai AgentIdP platform. Every AI agent is treated
    as a first-class non-human identity, aligned with the AGNTCY standard
    (Linux Foundation).

    Agents receive unique, immutable identifiers (UUIDs), typed capabilities,
    and lifecycle status management. The registry is the authoritative source of
    truth for all registered agent identities.

    **Free Tier Limits**:
    - Max 100 registered agents per account
    - API rate limit: 100 requests/minute

servers:
  - url: http://localhost:3000/api/v1
    description: Local development server
  - url: https://api.sentryagent.ai/v1
    description: Production server

tags:
  - name: Agent Registry
    description: CRUD operations for AI agent identities

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT access token obtained via the OAuth 2.0 Client Credentials flow
        (`POST /token`). Include in the `Authorization` header as:
        `Authorization: Bearer <token>`

  schemas:
    AgentType:
      type: string
      description: The functional classification of the AI agent.
      enum:
        - screener
        - classifier
        - orchestrator
        - extractor
        - summarizer
        - router
        - monitor
        - custom
      example: screener

    DeploymentEnv:
      type: string
      description: The target deployment environment for the agent.
      enum:
        - development
        - staging
        - production
      example: production

    AgentStatus:
      type: string
      description: |
        Lifecycle status of the agent.
        - `active`: Agent is operational and can authenticate.
        - `suspended`: Agent is temporarily disabled; credentials cannot be used.
        - `decommissioned`: Agent is permanently retired; soft-deleted record remains.
      enum:
        - active
        - suspended
        - decommissioned
      example: active

    Agent:
      type: object
      description: Full representation of a registered AI agent identity.
      required:
        - agentId
        - email
        - agentType
        - version
        - capabilities
        - owner
        - deploymentEnv
        - status
        - createdAt
        - updatedAt
      properties:
        agentId:
          type: string
          format: uuid
          description: >
            Immutable, system-assigned unique identifier for the agent.
            Assigned at registration and never changes.
          readOnly: true
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        email:
          type: string
          format: email
          description: >
            Unique email-format identifier for the agent. Acts as the human-readable
            stable name for this agent identity.
          example: "screener-001@sentryagent.ai"
        agentType:
          $ref: '#/components/schemas/AgentType'
        version:
          type: string
          description: Semantic version string of the agent software.
          pattern: '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
          example: "1.4.2"
        capabilities:
          type: array
          description: >
            List of capability strings representing what this agent is permitted
            to do. Uses a `resource:action` convention.
          items:
            type: string
            pattern: '^[a-z0-9_-]+:[a-z0-9_*-]+$'
          minItems: 1
          example:
            - "resume:read"
            - "email:send"
            - "candidate:score"
        owner:
          type: string
          description: Team or organisation that owns and is responsible for this agent.
          minLength: 1
          maxLength: 128
          example: "talent-acquisition-team"
        deploymentEnv:
          $ref: '#/components/schemas/DeploymentEnv'
        status:
          $ref: '#/components/schemas/AgentStatus'
        createdAt:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the agent was first registered.
          readOnly: true
          example: "2026-03-28T09:00:00.000Z"
        updatedAt:
          type: string
          format: date-time
          description: ISO 8601 timestamp of the most recent update to this agent record.
          readOnly: true
          example: "2026-03-28T11:30:00.000Z"

    CreateAgentRequest:
      type: object
      description: Request body for registering a new AI agent identity.
      required:
        - email
        - agentType
        - version
        - capabilities
        - owner
        - deploymentEnv
      properties:
        email:
          type: string
          format: email
          description: >
            Unique email-format identifier for the agent.
            Must be unique across all registered agents in the system.
          example: "screener-001@sentryagent.ai"
        agentType:
          $ref: '#/components/schemas/AgentType'
        version:
          type: string
          description: Semantic version string of the agent software.
          pattern: '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
          example: "1.0.0"
        capabilities:
          type: array
          description: List of capability strings for this agent.
          items:
            type: string
            pattern: '^[a-z0-9_-]+:[a-z0-9_*-]+$'
          minItems: 1
          example:
            - "resume:read"
            - "email:send"
        owner:
          type: string
          description: Team or organisation that owns this agent.
          minLength: 1
          maxLength: 128
          example: "talent-acquisition-team"
        deploymentEnv:
          $ref: '#/components/schemas/DeploymentEnv'

    UpdateAgentRequest:
      type: object
      description: >
        Request body for updating agent metadata. All fields are optional;
        only provided fields are updated. `agentId`, `email`, and `createdAt`
        are immutable and cannot be changed.
      minProperties: 1
      properties:
        agentType:
          $ref: '#/components/schemas/AgentType'
        version:
          type: string
          description: Updated semantic version string.
          pattern: '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
          example: "1.5.0"
        capabilities:
          type: array
          description: Updated list of capability strings. Replaces the full list.
          items:
            type: string
            pattern: '^[a-z0-9_-]+:[a-z0-9_*-]+$'
          minItems: 1
          example:
            - "resume:read"
            - "email:send"
            - "candidate:score"
            - "report:write"
        owner:
          type: string
          description: Updated owner team or organisation.
          minLength: 1
          maxLength: 128
          example: "platform-team"
        deploymentEnv:
          $ref: '#/components/schemas/DeploymentEnv'
        status:
          $ref: '#/components/schemas/AgentStatus'

    PaginatedAgentsResponse:
      type: object
      description: Paginated list of agent identities.
      required:
        - data
        - total
        - page
        - limit
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Agent'
        total:
          type: integer
          description: Total number of agents matching the query filters.
          example: 47
        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 used across all SentryAgent.ai APIs.
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: >
            Machine-readable error code. Use this field for programmatic error handling.
          example: "AGENT_NOT_FOUND"
        message:
          type: string
          description: Human-readable description of the error.
          example: "Agent with the specified ID was not found."
        details:
          type: object
          description: >
            Optional structured details providing additional context about the error,
            such as field-level validation failures.
          additionalProperties: true
          example:
            field: "email"
            reason: "Email address is already registered to another agent."

  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 perform this action."

    NotFound:
      description: The requested resource was not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: "AGENT_NOT_FOUND"
            message: "Agent with the specified ID was not found."

    TooManyRequests:
      description: Rate limit exceeded. Retry after the reset time.
      headers:
        X-RateLimit-Limit:
          schema:
            type: integer
          description: Maximum number of requests allowed per minute.
          example: 100
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Number of requests remaining in the current window.
          example: 0
        X-RateLimit-Reset:
          schema:
            type: integer
          description: Unix timestamp (seconds) when the rate limit window resets.
          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. Contact support if the issue persists.
      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:
    post:
      operationId: registerAgent
      tags:
        - Agent Registry
      summary: Register a new AI agent
      description: |
        Creates a new AI agent identity in the SentryAgent.ai registry.

        A unique immutable `agentId` (UUID) is system-assigned on creation.
        The `email` must be unique across all registered agents.

        **Free Tier**: Maximum 100 registered agents per account. Attempting to
        register beyond this limit returns `403 Forbidden` with code `FREE_TIER_LIMIT_EXCEEDED`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentRequest'
            example:
              email: "screener-001@sentryagent.ai"
              agentType: "screener"
              version: "1.0.0"
              capabilities:
                - "resume:read"
                - "email:send"
              owner: "talent-acquisition-team"
              deploymentEnv: "production"
      responses:
        '201':
          description: Agent registered successfully.
          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/Agent'
              example:
                agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                email: "screener-001@sentryagent.ai"
                agentType: "screener"
                version: "1.0.0"
                capabilities:
                  - "resume:read"
                  - "email:send"
                owner: "talent-acquisition-team"
                deploymentEnv: "production"
                status: "active"
                createdAt: "2026-03-28T09:00:00.000Z"
                updatedAt: "2026-03-28T09:00:00.000Z"
        '400':
          description: Invalid request body. Check `details` for field-level errors.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: "VALIDATION_ERROR"
                message: "Request validation failed."
                details:
                  field: "email"
                  reason: "Must be a valid email address."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Forbidden. Either insufficient permissions or free tier limit reached.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                insufficientPermissions:
                  summary: Insufficient permissions
                  value:
                    code: "FORBIDDEN"
                    message: "You do not have permission to register agents."
                freeTierLimit:
                  summary: Free tier agent limit reached
                  value:
                    code: "FREE_TIER_LIMIT_EXCEEDED"
                    message: "Free tier limit of 100 registered agents has been reached."
                    details:
                      limit: 100
                      current: 100
        '409':
          description: An agent with the provided email address is already registered.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: "AGENT_ALREADY_EXISTS"
                message: "An agent with this email address is already registered."
                details:
                  email: "screener-001@sentryagent.ai"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

    get:
      operationId: listAgents
      tags:
        - Agent Registry
      summary: List registered agents
      description: |
        Returns a paginated list of all registered AI agent identities accessible
        to the authenticated caller.

        Results can be filtered by `owner`, `agentType`, and/or `status`.
        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: owner
          in: query
          description: Filter agents by owner name (exact match).
          required: false
          schema:
            type: string
          example: "talent-acquisition-team"
        - name: agentType
          in: query
          description: Filter agents by type.
          required: false
          schema:
            $ref: '#/components/schemas/AgentType'
        - name: status
          in: query
          description: Filter agents by lifecycle status.
          required: false
          schema:
            $ref: '#/components/schemas/AgentStatus'
      responses:
        '200':
          description: Paginated list of agents returned successfully.
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              example: 100
            X-RateLimit-Remaining:
              schema:
                type: integer
              example: 95
            X-RateLimit-Reset:
              schema:
                type: integer
              example: 1743155400
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedAgentsResponse'
              example:
                data:
                  - agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    email: "screener-001@sentryagent.ai"
                    agentType: "screener"
                    version: "1.4.2"
                    capabilities:
                      - "resume:read"
                      - "email:send"
                      - "candidate:score"
                    owner: "talent-acquisition-team"
                    deploymentEnv: "production"
                    status: "active"
                    createdAt: "2026-03-01T08:00:00.000Z"
                    updatedAt: "2026-03-28T09:00:00.000Z"
                  - agentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    email: "classifier-002@sentryagent.ai"
                    agentType: "classifier"
                    version: "2.1.0"
                    capabilities:
                      - "document:classify"
                      - "label:write"
                    owner: "talent-acquisition-team"
                    deploymentEnv: "staging"
                    status: "active"
                    createdAt: "2026-03-10T10:00:00.000Z"
                    updatedAt: "2026-03-10T10:00:00.000Z"
                total: 47
                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: "limit"
                  reason: "Must be an integer between 1 and 100."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /agents/{agentId}:
    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"

    get:
      operationId: getAgentById
      tags:
        - Agent Registry
      summary: Get agent by ID
      description: |
        Retrieves the full identity record for a single AI agent by its immutable `agentId`.
      responses:
        '200':
          description: Agent record returned successfully.
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              example: 100
            X-RateLimit-Remaining:
              schema:
                type: integer
              example: 94
            X-RateLimit-Reset:
              schema:
                type: integer
              example: 1743155400
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
              example:
                agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                email: "screener-001@sentryagent.ai"
                agentType: "screener"
                version: "1.4.2"
                capabilities:
                  - "resume:read"
                  - "email:send"
                  - "candidate:score"
                owner: "talent-acquisition-team"
                deploymentEnv: "production"
                status: "active"
                createdAt: "2026-03-01T08:00:00.000Z"
                updatedAt: "2026-03-28T09:00:00.000Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

    patch:
      operationId: updateAgent
      tags:
        - Agent Registry
      summary: Update agent metadata
      description: |
        Partially updates the metadata for an existing agent.

        Only the fields provided in the request body are updated. Omitted fields
        are left unchanged. The following fields are immutable and cannot be
        updated: `agentId`, `email`, `createdAt`.

        Setting `status` to `decommissioned` is a one-way operation — a
        decommissioned agent cannot be reactivated.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAgentRequest'
            example:
              version: "1.5.0"
              capabilities:
                - "resume:read"
                - "email:send"
                - "candidate:score"
                - "report:write"
              deploymentEnv: "production"
      responses:
        '200':
          description: Agent updated successfully. Returns the full updated agent record.
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              example: 100
            X-RateLimit-Remaining:
              schema:
                type: integer
              example: 93
            X-RateLimit-Reset:
              schema:
                type: integer
              example: 1743155400
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
              example:
                agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                email: "screener-001@sentryagent.ai"
                agentType: "screener"
                version: "1.5.0"
                capabilities:
                  - "resume:read"
                  - "email:send"
                  - "candidate:score"
                  - "report:write"
                owner: "talent-acquisition-team"
                deploymentEnv: "production"
                status: "active"
                createdAt: "2026-03-01T08:00:00.000Z"
                updatedAt: "2026-03-28T11:30:00.000Z"
        '400':
          description: Invalid request body or attempt to modify an immutable field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validationError:
                  summary: Validation failure
                  value:
                    code: "VALIDATION_ERROR"
                    message: "Request validation failed."
                    details:
                      field: "version"
                      reason: "Must be a valid semantic version string."
                immutableField:
                  summary: Attempt to modify immutable field
                  value:
                    code: "IMMUTABLE_FIELD"
                    message: "The field 'email' cannot be modified after registration."
                    details:
                      field: "email"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Forbidden. Insufficient permissions or agent is decommissioned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                forbidden:
                  summary: Insufficient permissions
                  value:
                    code: "FORBIDDEN"
                    message: "You do not have permission to update this agent."
                decommissioned:
                  summary: Agent is decommissioned
                  value:
                    code: "AGENT_DECOMMISSIONED"
                    message: "Decommissioned agents cannot be updated."
                    details:
                      agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

    delete:
      operationId: deactivateAgent
      tags:
        - Agent Registry
      summary: Deactivate (soft-delete) an agent
      description: |
        Permanently decommissions an AI agent. This is a **soft delete** — the
        agent record is retained in the database for audit purposes, but the
        agent's status is set to `decommissioned`.

        **Effects of decommissioning**:
        - All active credentials for this agent are immediately revoked.
        - The agent can no longer authenticate or obtain tokens.
        - The agent record remains visible in the registry with status `decommissioned`.
        - This operation is **irreversible**.
      responses:
        '204':
          description: Agent decommissioned successfully. No response body.
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              example: 100
            X-RateLimit-Remaining:
              schema:
                type: integer
              example: 92
            X-RateLimit-Reset:
              schema:
                type: integer
              example: 1743155400
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Agent is already decommissioned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: "AGENT_ALREADY_DECOMMISSIONED"
                message: "This agent has already been decommissioned."
                details:
                  agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
