openapi: 3.0.3

info:
  title: SentryAgent.ai — Audit Log Service
  version: 1.0.0
  description: |
    The Audit Log Service provides a queryable, immutable, compliance-ready
    event log of all significant actions performed by agents and administrators
    on the SentryAgent.ai AgentIdP platform.

    **Immutability**: Audit events are written internally only — there are no
    API endpoints to create, modify, or delete audit records. The log is
    append-only by design.

    **Automatic event capture**: The following actions are automatically logged:
    | Action | Description |
    |--------|-------------|
    | `agent.created` | A new agent was registered |
    | `agent.updated` | Agent metadata was modified |
    | `agent.decommissioned` | An agent was decommissioned |
    | `agent.suspended` | An agent was suspended |
    | `agent.reactivated` | A suspended agent was reactivated |
    | `token.issued` | An access token was issued |
    | `token.revoked` | An access token was revoked |
    | `token.introspected` | A token was introspected |
    | `credential.generated` | New credentials were generated |
    | `credential.rotated` | A credential was rotated |
    | `credential.revoked` | A credential was revoked |
    | `auth.failed` | An authentication attempt failed |

    **Free Tier**: Audit log retention is 90 days.
    Events older than 90 days are automatically purged on the free tier.

    **Required scope**: `audit:read`

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

tags:
  - name: Audit Log
    description: Query immutable audit events for compliance and governance

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

  schemas:
    AuditAction:
      type: string
      description: The action that triggered the audit event.
      enum:
        - agent.created
        - agent.updated
        - agent.decommissioned
        - agent.suspended
        - agent.reactivated
        - token.issued
        - token.revoked
        - token.introspected
        - credential.generated
        - credential.rotated
        - credential.revoked
        - auth.failed
      example: token.issued

    AuditOutcome:
      type: string
      description: Whether the action succeeded or failed.
      enum:
        - success
        - failure
      example: success

    AuditEvent:
      type: object
      description: |
        An immutable audit event record representing a single significant action
        that occurred within the SentryAgent.ai platform.
      required:
        - eventId
        - agentId
        - action
        - outcome
        - ipAddress
        - userAgent
        - metadata
        - timestamp
      properties:
        eventId:
          type: string
          format: uuid
          description: Immutable, system-assigned unique identifier for this audit event.
          readOnly: true
          example: "f1e2d3c4-b5a6-7890-cdef-123456789012"
        agentId:
          type: string
          format: uuid
          description: >
            The `agentId` of the agent that triggered this event. For system-generated
            events (e.g. automatic token expiry), this field refers to the affected agent.
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        action:
          $ref: '#/components/schemas/AuditAction'
        outcome:
          $ref: '#/components/schemas/AuditOutcome'
        ipAddress:
          type: string
          description: >
            IP address of the client that initiated the request.
            IPv4 or IPv6 format. May be `0.0.0.0` for system-generated events.
          example: "203.0.113.42"
        userAgent:
          type: string
          description: >
            HTTP `User-Agent` header value from the originating request.
            May be `SentryAgent-System/1.0` for internally generated events.
          example: "SentryAgent-SDK/1.0.0 Node.js/18.19.0"
        metadata:
          type: object
          description: |
            Action-specific structured data providing additional context.
            Schema varies by `action`:
            - `token.issued`: includes `scope`, `expiresAt`
            - `credential.rotated`: includes `credentialId`
            - `agent.created`: includes `agentType`, `owner`
            - `auth.failed`: includes `reason`, `clientId`
          additionalProperties: true
          example:
            scope: "agents:read agents:write"
            expiresAt: "2026-03-28T10:00:00.000Z"
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the event occurred.
          readOnly: true
          example: "2026-03-28T09:01:00.000Z"

    PaginatedAuditEventsResponse:
      type: object
      description: Paginated list of audit events.
      required:
        - data
        - total
        - page
        - limit
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/AuditEvent'
        total:
          type: integer
          description: Total number of audit events matching the query filters.
          example: 1423
        page:
          type: integer
          description: Current page number (1-based).
          example: 1
        limit:
          type: integer
          description: Number of items per page.
          example: 50

    ErrorResponse:
      type: object
      description: Standard error response envelope.
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Machine-readable error code.
          example: "AUDIT_EVENT_NOT_FOUND"
        message:
          type: string
          description: Human-readable description of the error.
          example: "Audit event 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. Requires `audit:read` scope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            code: "INSUFFICIENT_SCOPE"
            message: "The 'audit:read' scope is required to access audit logs."

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

    TooManyRequests:
      description: Rate limit exceeded.
      headers:
        X-RateLimit-Limit:
          schema:
            type: integer
          description: Maximum requests allowed per minute.
          example: 100
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Requests remaining in the current window.
          example: 0
        X-RateLimit-Reset:
          schema:
            type: integer
          description: Unix timestamp 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.
      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:
  /audit:
    get:
      operationId: queryAuditLog
      tags:
        - Audit Log
      summary: Query audit log
      description: |
        Returns a paginated, filtered list of audit events. Results are ordered
        by `timestamp` descending (most recent first).

        **Requires**: Bearer token with `audit:read` scope.

        **Retention**: On the free tier, only events from the last 90 days are
        accessible. Requests for older events will return an empty result set,
        not an error.

        **Filtering**: Multiple filters can be combined (logical AND).
        All filter parameters are optional.
      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 `50`, maximum `200`.
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
          example: 50
        - name: agentId
          in: query
          description: Filter events to those triggered by a specific agent (UUID).
          required: false
          schema:
            type: string
            format: uuid
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        - name: action
          in: query
          description: Filter events by action type.
          required: false
          schema:
            $ref: '#/components/schemas/AuditAction'
        - name: outcome
          in: query
          description: Filter events by outcome.
          required: false
          schema:
            $ref: '#/components/schemas/AuditOutcome'
        - name: fromDate
          in: query
          description: |
            Filter events at or after this ISO 8601 timestamp (inclusive).
            On free tier, cannot be older than 90 days from today.
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-03-01T00:00:00.000Z"
        - name: toDate
          in: query
          description: Filter events at or before this ISO 8601 timestamp (inclusive).
          required: false
          schema:
            type: string
            format: date-time
          example: "2026-03-28T23:59:59.999Z"
      responses:
        '200':
          description: Audit events 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/PaginatedAuditEventsResponse'
              example:
                data:
                  - eventId: "f1e2d3c4-b5a6-7890-cdef-123456789012"
                    agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    action: "token.issued"
                    outcome: "success"
                    ipAddress: "203.0.113.42"
                    userAgent: "SentryAgent-SDK/1.0.0 Node.js/18.19.0"
                    metadata:
                      scope: "agents:read agents:write"
                      expiresAt: "2026-03-28T10:01:00.000Z"
                    timestamp: "2026-03-28T09:01:00.000Z"
                  - eventId: "e2d3c4b5-a6f7-8901-bcde-f23456789013"
                    agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                    action: "credential.generated"
                    outcome: "success"
                    ipAddress: "203.0.113.42"
                    userAgent: "SentryAgent-SDK/1.0.0 Node.js/18.19.0"
                    metadata:
                      credentialId: "c9d8e7f6-a5b4-3210-fedc-ba9876543210"
                    timestamp: "2026-03-28T09:00:00.000Z"
                  - eventId: "d3c4b5a6-f7e8-9012-cdef-345678901234"
                    agentId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    action: "auth.failed"
                    outcome: "failure"
                    ipAddress: "198.51.100.17"
                    userAgent: "python-requests/2.31.0"
                    metadata:
                      reason: "invalid_client_secret"
                      clientId: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
                    timestamp: "2026-03-28T08:45:00.000Z"
                total: 1423
                page: 1
                limit: 50
        '400':
          description: Invalid query parameters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidDate:
                  summary: Invalid date format
                  value:
                    code: "VALIDATION_ERROR"
                    message: "Invalid query parameter value."
                    details:
                      field: "fromDate"
                      reason: "Must be a valid ISO 8601 date-time string."
                invalidDateRange:
                  summary: fromDate is after toDate
                  value:
                    code: "VALIDATION_ERROR"
                    message: "Invalid date range."
                    details:
                      reason: "fromDate must be before or equal to toDate."
                retentionExceeded:
                  summary: Requested date is outside retention window
                  value:
                    code: "RETENTION_WINDOW_EXCEEDED"
                    message: "Free tier audit log retention is 90 days. Requested date is outside the retention window."
                    details:
                      retentionDays: 90
                      earliestAvailable: "2025-12-28T00:00:00.000Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /audit/{eventId}:
    parameters:
      - name: eventId
        in: path
        description: The unique UUID identifier of the audit event.
        required: true
        schema:
          type: string
          format: uuid
        example: "f1e2d3c4-b5a6-7890-cdef-123456789012"

    get:
      operationId: getAuditEventById
      tags:
        - Audit Log
      summary: Get a single audit event by ID
      description: |
        Retrieves a single, immutable audit event by its unique `eventId`.

        **Requires**: Bearer token with `audit:read` scope.

        **Retention**: Free tier events older than 90 days are not accessible
        and will return `404 Not Found`.
      responses:
        '200':
          description: Audit event 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/AuditEvent'
              example:
                eventId: "f1e2d3c4-b5a6-7890-cdef-123456789012"
                agentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                action: "token.issued"
                outcome: "success"
                ipAddress: "203.0.113.42"
                userAgent: "SentryAgent-SDK/1.0.0 Node.js/18.19.0"
                metadata:
                  scope: "agents:read agents:write"
                  expiresAt: "2026-03-28T10:01:00.000Z"
                timestamp: "2026-03-28T09:01: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'
