openapi: "3.0.3"

info:
  title: SentryAgent.ai — A2A Delegation (Agent-to-Agent)
  version: 1.0.0
  description: |
    Agent-to-Agent (A2A) delegation endpoints for the SentryAgent.ai AgentIdP platform.

    The delegation subsystem enables an authenticated agent (the *delegator*) to grant
    a subset of its own scopes to another agent (the *delegatee*) for a limited time.
    This creates a cryptographically-signed delegation chain, suitable for multi-agent
    orchestration patterns.

    **All endpoints require a valid Bearer JWT.**

    **Feature flag:** When `A2A_ENABLED=false` these routes are not registered (return 404).

    **Delegation rules:**
    - The delegatee must be in the same tenant as the delegator.
    - Delegated scopes must be a strict subset of the delegator's own scopes.
    - TTL minimum: 60 seconds; maximum: 86400 seconds (24 hours).
    - Each delegation chain has a unique `chainId` (UUID).
    - Revoking a chain is idempotent — revoking an already-revoked chain succeeds.

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

tags:
  - name: A2A Delegation
    description: Agent-to-Agent delegation chain management

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT access token obtained via `POST /token`.
        Include as `Authorization: Bearer <token>`.

  schemas:
    DelegationChain:
      type: object
      description: A delegation chain record as returned by the API.
      required:
        - id
        - tenantId
        - delegatorAgentId
        - delegateeAgentId
        - scopes
        - delegationToken
        - ttlSeconds
        - issuedAt
        - expiresAt
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier of the delegation chain.
          readOnly: true
          example: "chain-abcd-1234-5678-ef01"
        tenantId:
          type: string
          format: uuid
          description: Organization (tenant) that owns this delegation.
          readOnly: true
          example: "org-1234-5678-abcd-ef01"
        delegatorAgentId:
          type: string
          format: uuid
          description: UUID of the agent granting authority.
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        delegateeAgentId:
          type: string
          format: uuid
          description: UUID of the agent receiving delegated authority.
          example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
        scopes:
          type: array
          items:
            type: string
          description: OAuth 2.0 scopes granted by this delegation chain.
          example:
            - "agents:read"
        delegationToken:
          type: string
          description: |
            Opaque delegation token string that the delegatee presents to verify authority.
            This token encodes the chain metadata and is HMAC-signed.
          example: "chain-abcd-1234-5678-ef01.1743151200.1743237600"
        signature:
          type: string
          description: HMAC-SHA256 signature of the delegation token payload.
          example: "3a7f2b9c..."
        ttlSeconds:
          type: integer
          description: Delegation lifetime in seconds.
          minimum: 60
          maximum: 86400
          example: 3600
        issuedAt:
          type: string
          format: date-time
          readOnly: true
          example: "2026-04-07T09:00:00.000Z"
        expiresAt:
          type: string
          format: date-time
          readOnly: true
          example: "2026-04-07T10:00:00.000Z"
        revokedAt:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when this chain was revoked. Null if still active or expired naturally.
          readOnly: true
          example: null
        createdAt:
          type: string
          format: date-time
          readOnly: true
          example: "2026-04-07T09:00:00.000Z"

    CreateDelegationRequest:
      type: object
      description: Request body for creating a new agent-to-agent delegation chain.
      required:
        - delegateeAgentId
        - scopes
        - ttlSeconds
      properties:
        delegateeAgentId:
          type: string
          format: uuid
          description: |
            UUID of the agent to receive delegated authority.
            Must be in the same tenant as the delegator (caller).
          example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
        scopes:
          type: array
          items:
            type: string
          description: |
            Scopes to delegate. Must be a strict subset of the delegator's current token scopes.
            At least one scope must be specified.
          minItems: 1
          example:
            - "agents:read"
        ttlSeconds:
          type: integer
          description: Delegation lifetime in seconds. Minimum: 60; Maximum: 86400 (24 hours).
          minimum: 60
          maximum: 86400
          example: 3600

    VerifyDelegationRequest:
      type: object
      description: Request body for verifying a delegation token.
      required:
        - delegationToken
      properties:
        delegationToken:
          type: string
          description: The delegation token string to verify.
          example: "chain-abcd-1234-5678-ef01.1743151200.1743237600"

    DelegationVerificationResult:
      type: object
      description: |
        Result of verifying a delegation token.
        Returns `valid: false` for expired or revoked tokens without throwing.
      required:
        - valid
        - chainId
        - delegatorAgentId
        - delegateeAgentId
        - scopes
        - issuedAt
        - expiresAt
      properties:
        valid:
          type: boolean
          description: Whether the delegation token is currently valid (active, not expired, not revoked).
          example: true
        chainId:
          type: string
          format: uuid
          description: UUID of the delegation chain.
          example: "chain-abcd-1234-5678-ef01"
        delegatorAgentId:
          type: string
          format: uuid
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        delegateeAgentId:
          type: string
          format: uuid
          example: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
        scopes:
          type: array
          items:
            type: string
          example:
            - "agents:read"
        issuedAt:
          type: string
          format: date-time
          example: "2026-04-07T09:00:00.000Z"
        expiresAt:
          type: string
          format: date-time
          example: "2026-04-07T10:00:00.000Z"
        revokedAt:
          type: string
          format: date-time
          nullable: true
          example: null

    ErrorResponse:
      type: object
      description: Standard error response envelope.
      required:
        - code
        - message
      properties:
        code:
          type: string
          example: "DELEGATION_NOT_FOUND"
        message:
          type: string
          example: "Delegation chain with the specified ID was not found."
        details:
          type: object
          additionalProperties: true

  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: Delegation chain not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: "DELEGATION_NOT_FOUND"
            message: "Delegation chain with the specified ID was not found."

    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:
  /oauth2/token/delegate:
    post:
      operationId: createDelegation
      tags:
        - A2A Delegation
      summary: Create an A2A delegation chain
      description: |
        Creates a new agent-to-agent delegation chain. The authenticated agent
        (the *delegator*) grants a subset of its own scopes to the `delegateeAgentId`.

        A cryptographically-signed `delegationToken` is returned. The delegatee
        presents this token to `POST /oauth2/token/verify-delegation` to prove
        delegated authority.

        **Validation:**
        - Delegatee must be in the same organization as the delegator.
        - `scopes` must be a strict subset of the delegator's current token scopes.
        - `ttlSeconds` must be between 60 and 86400.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDelegationRequest'
            example:
              delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
              scopes:
                - "agents:read"
              ttlSeconds: 3600
      responses:
        '201':
          description: Delegation chain created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DelegationChain'
              example:
                id: "chain-abcd-1234-5678-ef01"
                tenantId: "org-1234-5678-abcd-ef01"
                delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                scopes:
                  - "agents:read"
                delegationToken: "chain-abcd-1234-5678-ef01.1743151200.1743154800"
                signature: "3a7f2b9c..."
                ttlSeconds: 3600
                issuedAt: "2026-04-07T09:00:00.000Z"
                expiresAt: "2026-04-07T10:00:00.000Z"
                revokedAt: null
                createdAt: "2026-04-07T09:00:00.000Z"
        '400':
          description: Validation error in request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                scopeExceedsOwn:
                  summary: Requested scope exceeds delegator's own scopes
                  value:
                    code: "SCOPE_EXCEEDS_DELEGATOR"
                    message: "Delegated scopes must be a subset of the delegator's own token scopes."
                    details:
                      requested: ["agents:write"]
                      available: ["agents:read"]
                invalidTtl:
                  summary: TTL out of range
                  value:
                    code: "VALIDATION_ERROR"
                    message: "ttlSeconds must be between 60 and 86400."
                crossTenant:
                  summary: Delegatee in different tenant
                  value:
                    code: "CROSS_TENANT_DELEGATION"
                    message: "Delegatee agent must be in the same organization as the delegator."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Delegatee agent not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: "AGENT_NOT_FOUND"
                message: "Delegatee agent with the specified ID was not found."
        '500':
          $ref: '#/components/responses/InternalServerError'

  /oauth2/token/verify-delegation:
    post:
      operationId: verifyDelegation
      tags:
        - A2A Delegation
      summary: Verify a delegation token
      description: |
        Verifies a delegation token and returns the chain details if valid.

        Returns `valid: true` with full chain metadata when the token is valid
        (exists, not expired, not revoked).

        Returns `valid: false` when the token is expired or revoked.
        Does not throw an error for inactive tokens — always returns `200`.

        Requires a valid Bearer JWT (any authenticated agent may verify a delegation).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyDelegationRequest'
            example:
              delegationToken: "chain-abcd-1234-5678-ef01.1743151200.1743154800"
      responses:
        '200':
          description: Delegation verification result returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DelegationVerificationResult'
              examples:
                valid:
                  summary: Valid delegation token
                  value:
                    valid: true
                    chainId: "chain-abcd-1234-5678-ef01"
                    delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    scopes:
                      - "agents:read"
                    issuedAt: "2026-04-07T09:00:00.000Z"
                    expiresAt: "2026-04-07T10:00:00.000Z"
                    revokedAt: null
                expired:
                  summary: Expired delegation token
                  value:
                    valid: false
                    chainId: "chain-abcd-1234-5678-ef01"
                    delegatorAgentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    delegateeAgentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    scopes:
                      - "agents:read"
                    issuedAt: "2026-04-06T09:00:00.000Z"
                    expiresAt: "2026-04-06T10:00:00.000Z"
                    revokedAt: null
        '400':
          description: Missing or malformed `delegationToken` field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                code: "VALIDATION_ERROR"
                message: "The 'delegationToken' field is required."
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /oauth2/token/delegate/{chainId}:
    parameters:
      - name: chainId
        in: path
        required: true
        description: UUID of the delegation chain to revoke.
        schema:
          type: string
          format: uuid
        example: "chain-abcd-1234-5678-ef01"

    delete:
      operationId: revokeDelegation
      tags:
        - A2A Delegation
      summary: Revoke a delegation chain
      description: |
        Immediately revokes a delegation chain.

        After revocation, `POST /oauth2/token/verify-delegation` will return
        `valid: false` for the revoked chain's token.

        **Idempotent** — revoking an already-revoked chain returns `204` without error.

        Only the delegator agent or an admin may revoke a chain.
        Requires a valid Bearer JWT.
      responses:
        '204':
          description: Delegation chain revoked successfully (or was already revoked). No response body.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
