openapi: 3.1.0
info:
  title: Domain Intelligence API
  version: 1.0.0
  description: Public Domain Intelligence API contract.
servers:
  - url: https://api.domainintelligence.uk
security:
  - bearerAuth: []
paths:
  /v1/infostealers:
    get:
      summary: List infostealer C2 hosts
      operationId: listInfostealers
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - $ref: "#/components/parameters/Host"
        - $ref: "#/components/parameters/WindowDays"
        - $ref: "#/components/parameters/StartAt"
        - $ref: "#/components/parameters/EndAt"
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: Paginated infostealer rows.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InfostealerListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /v1/infostealers/{host}:
    get:
      summary: Look up one infostealer host
      operationId: getInfostealerHost
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: host
          in: path
          required: true
          schema:
            type: string
          description: Host to check. Domain names are normalized to lowercase.
        - $ref: "#/components/parameters/WindowDays"
        - $ref: "#/components/parameters/StartAt"
        - $ref: "#/components/parameters/EndAt"
      responses:
        "200":
          description: Host lookup result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InfostealerHostResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Client API key sent as a bearer token.
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: Client API key sent through the X-API-Key header.
  parameters:
    Host:
      name: host
      in: query
      required: false
      schema:
        type: string
      description: Optional host filter for list requests.
    WindowDays:
      name: window_days
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 90
        default: 14
      description: UTC day window. Use either window_days or explicit start_at and end_at.
    StartAt:
      name: start_at
      in: query
      required: false
      schema:
        type: string
      description: RFC3339, RFC3339Nano, or YYYY-MM-DD start time. Must be sent with end_at.
    EndAt:
      name: end_at
      in: query
      required: false
      schema:
        type: string
      description: RFC3339, RFC3339Nano, or YYYY-MM-DD end time. Must be after start_at.
    Page:
      name: page
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        default: 1
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 5000
        default: 100
  responses:
    BadRequest:
      description: Invalid query parameter or host.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: API key missing or invalid.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: API key lacks the required API group.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Host not found in the selected window.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    InfostealerListResponse:
      type: object
      required: [rows, pagination, filters]
      properties:
        rows:
          type: array
          items:
            $ref: "#/components/schemas/InfostealerRow"
        pagination:
          $ref: "#/components/schemas/Pagination"
        filters:
          $ref: "#/components/schemas/InfostealerListParams"
    InfostealerHostResponse:
      type: object
      required: [host, found, filters]
      properties:
        host:
          type: string
        found:
          type: boolean
        row:
          $ref: "#/components/schemas/InfostealerRow"
        filters:
          $ref: "#/components/schemas/InfostealerListParams"
    InfostealerListParams:
      type: object
      properties:
        host:
          type: string
        windowDays:
          type: integer
        startAt:
          type: string
          format: date-time
        endAt:
          type: string
          format: date-time
        page:
          type: integer
        limit:
          type: integer
    InfostealerRow:
      type: object
      required: [feedType, reviewStatus, host, families, seenAt, ports, resolvedIps, quad9Hits, circlHits]
      properties:
        feedType:
          type: string
        reviewStatus:
          type: string
        host:
          type: string
        families:
          type: array
          items:
            type: string
        seenAt:
          type: string
          format: date-time
        firstSeen:
          type: string
          format: date-time
        lastSeen:
          type: string
          format: date-time
        ports:
          type: array
          items:
            $ref: "#/components/schemas/InfostealerPort"
        dnsStatus:
          type: string
        resolvedIps:
          type: array
          items:
            type: string
        country:
          type: string
        asn:
          type: integer
        asOrg:
          type: string
        infraType:
          type: string
        quad9Hits:
          type: integer
          format: int64
        circlHits:
          type: integer
          format: int64
    InfostealerPort:
      type: object
      required: [port, families]
      properties:
        port:
          type: integer
        families:
          type: array
          items:
            $ref: "#/components/schemas/InfostealerPortFamily"
    InfostealerPortFamily:
      type: object
      required: [family, samples]
      properties:
        family:
          type: string
        samples:
          type: array
          items:
            $ref: "#/components/schemas/InfostealerSample"
    InfostealerSample:
      type: object
      required: [sha256, firstSeen, lastSeen]
      properties:
        sha256:
          type: string
        firstSeen:
          type: string
          format: date-time
        lastSeen:
          type: string
          format: date-time
    Pagination:
      type: object
      required: [page, limit, prev_page, next_page, has_prev, has_next, total_rows, total_pages, start_row, end_row]
      properties:
        page:
          type: integer
        limit:
          type: integer
        prev_page:
          type: integer
        next_page:
          type: integer
        has_prev:
          type: boolean
        has_next:
          type: boolean
        total_rows:
          type: integer
        total_pages:
          type: integer
        start_row:
          type: integer
        end_row:
          type: integer
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
            request_id:
              type: string
