openapi: 3.1.0
info:
  title: ProjectMind Project Reports API
  version: 1.0.0
  description: |
    Versioned HTTPS JSON API for ingesting and querying project status reports.

    This is the **canonical** data-ingestion interface for project reports.
    Future MCP tools must read from the same internal application services —
    MCP is not the primary write path.
  contact:
    name: ProjectMind
    url: https://imemory.dev
  license:
    name: Proprietary

servers:
  - url: https://{host}
    variables:
      host:
        default: your-imemory-host
  - url: http://localhost:8080

tags:
  - name: Reports
  - name: Projects
  - name: Schema
  - name: Spec

security:
  - ProjectReportBearer: []
  - ProjectReportApiKey: []

paths:
  /api/v1/project-reports:
    post:
      tags: [Reports]
      operationId: ingestProjectReport
      summary: Ingest a project report
      description: |
        Authenticate with a project report credential that has the
        `project-reports:write` scope and is authorized for `project.id`.

        Validation uses the published JSON Schema. Duplicates are detected via
        `reportId` and/or the `Idempotency-Key` header. Historical reports are
        never deleted; the latest-report pointer is updated atomically.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProjectReport"
            examples:
              minimal:
                $ref: "#/components/examples/MinimalReport"
      responses:
        "202":
          description: Report accepted (or idempotent replay of a prior acceptance)
          headers:
            X-RateLimit-Limit:
              schema: { type: integer }
            X-RateLimit-Remaining:
              schema: { type: integer }
            X-RateLimit-Reset:
              schema: { type: integer }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportAccepted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: Conflict (e.g. same reportId with different payload)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          description: Payload too large
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: Schema or schemaVersion validation failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/projects:
    get:
      tags: [Projects]
      operationId: listProjects
      summary: List projects visible to the credential
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - name: environment
          in: query
          schema:
            $ref: "#/components/schemas/Environment"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/ProjectStatus"
      responses:
        "200":
          description: Paginated project list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/projects/{projectId}:
    get:
      tags: [Projects]
      operationId: getProject
      summary: Get a project summary with latest report pointer
      parameters:
        - $ref: "#/components/parameters/ProjectId"
      responses:
        "200":
          description: Project detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectDetail"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/projects/{projectId}/reports/latest:
    get:
      tags: [Reports]
      operationId: getLatestProjectReport
      summary: Get the latest report for a project
      parameters:
        - $ref: "#/components/parameters/ProjectId"
        - name: environment
          in: query
          description: Environment for the latest pointer (defaults to production)
          schema:
            $ref: "#/components/schemas/Environment"
      responses:
        "200":
          description: Latest stored report (raw payload)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoredReport"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/projects/{projectId}/reports:
    get:
      tags: [Reports]
      operationId: listProjectReports
      summary: List historical reports for a project
      parameters:
        - $ref: "#/components/parameters/ProjectId"
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - name: environment
          in: query
          schema:
            $ref: "#/components/schemas/Environment"
        - name: from
          in: query
          description: Inclusive lower bound on generatedAt (ISO-8601)
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          description: Inclusive upper bound on generatedAt (ISO-8601)
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Paginated report list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportList"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/project-report-schema:
    get:
      tags: [Schema]
      operationId: getProjectReportSchema
      summary: Download the Project Report JSON Schema
      security: []
      responses:
        "200":
          description: JSON Schema document
          content:
            application/schema+json:
              schema:
                type: object
            application/json:
              schema:
                type: object

  /openapi.json:
    get:
      tags: [Spec]
      operationId: getOpenApiJson
      summary: OpenAPI document (JSON)
      security: []
      responses:
        "200":
          description: OpenAPI 3.1 document
          content:
            application/json:
              schema:
                type: object

  /openapi.yaml:
    get:
      tags: [Spec]
      operationId: getOpenApiYaml
      summary: OpenAPI document (YAML)
      security: []
      responses:
        "200":
          description: OpenAPI 3.1 document
          content:
            application/yaml:
              schema:
                type: string

components:
  securitySchemes:
    ProjectReportBearer:
      type: http
      scheme: bearer
      description: |
        Project report credential token (`impr_…`). Requires scope
        `project-reports:write` for ingestion. Read endpoints accept the same
        credential when it is authorized for the project.
    ProjectReportApiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: Alternative to Authorization Bearer for the same credential token.

  parameters:
    ProjectId:
      name: projectId
      in: path
      required: true
      schema:
        type: string
    Page:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
    PageSize:
      name: pageSize
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Optional dedupe key. When provided, combined with `reportId` for
        idempotent acceptance. Prefer setting `reportId` in the body; the header
        is used when clients cannot control the body id.
      schema:
        type: string
        maxLength: 128

  schemas:
    Environment:
      type: string
      enum: [production, staging, development, test, preview]
    ProjectStatus:
      type: string
      enum: [healthy, degraded, unhealthy, unknown, maintenance]
    IssueSeverity:
      type: string
      enum: [critical, high, medium, low, info]
    TaskStatus:
      type: string
      enum: [todo, in_progress, blocked, done, cancelled]
    DeploymentStatus:
      type: string
      enum: [pending, building, deploying, success, failed, rolled_back, cancelled]

    ProjectReport:
      description: |
        Canonical Project Report body. See `/api/v1/project-report-schema` for
        the authoritative JSON Schema (unknown root fields rejected; unknown
        fields inside `extensions` accepted).
      type: object
      additionalProperties: false
      required: [schemaVersion, reportId, generatedAt, project]
      properties:
        schemaVersion:
          type: string
          enum: ["1.0"]
        reportId:
          type: string
        generatedAt:
          type: string
          format: date-time
        project:
          type: object
          required: [id, name, environment, status]
          properties:
            id: { type: string }
            name: { type: string }
            environment: { $ref: "#/components/schemas/Environment" }
            status: { $ref: "#/components/schemas/ProjectStatus" }
            slug: { type: string }
            description: { type: string }
            version: { type: string }
            url: { type: string }
        summary: { type: object, additionalProperties: true }
        health: { type: object, additionalProperties: true }
        metrics: { type: array, items: { type: object } }
        deployments: { type: array, items: { type: object } }
        issues: { type: array, items: { type: object } }
        tasks: { type: array, items: { type: object } }
        agent: { type: object, additionalProperties: true }
        links: { type: object, additionalProperties: true }
        extensions: { type: object, additionalProperties: true }

    ReportAccepted:
      type: object
      required: [accepted, reportId, projectId, schemaVersion, receivedAt]
      properties:
        accepted: { type: boolean, const: true }
        reportId: { type: string }
        projectId: { type: string }
        schemaVersion: { type: string }
        receivedAt: { type: string, format: date-time }
        duplicate: { type: boolean, description: "True when this was an idempotent replay" }

    StoredReport:
      type: object
      required: [reportId, projectId, schemaVersion, generatedAt, receivedAt, report]
      properties:
        reportId: { type: string }
        projectId: { type: string }
        schemaVersion: { type: string }
        environment: { $ref: "#/components/schemas/Environment" }
        generatedAt: { type: string, format: date-time }
        receivedAt: { type: string, format: date-time }
        report:
          $ref: "#/components/schemas/ProjectReport"

    ProjectSummary:
      type: object
      required: [id, name]
      properties:
        id: { type: string }
        name: { type: string }
        slug: { type: string, nullable: true }
        latestEnvironment: { $ref: "#/components/schemas/Environment" }
        latestStatus: { $ref: "#/components/schemas/ProjectStatus" }
        latestReportId: { type: string, nullable: true }
        latestGeneratedAt: { type: string, format: date-time, nullable: true }

    ProjectDetail:
      allOf:
        - $ref: "#/components/schemas/ProjectSummary"
        - type: object
          properties:
            description: { type: string, nullable: true }
            latestByEnvironment:
              type: object
              additionalProperties:
                type: object
                properties:
                  reportId: { type: string }
                  generatedAt: { type: string, format: date-time }
                  status: { $ref: "#/components/schemas/ProjectStatus" }

    ProjectList:
      type: object
      required: [items, page, pageSize, total]
      properties:
        items:
          type: array
          items: { $ref: "#/components/schemas/ProjectSummary" }
        page: { type: integer }
        pageSize: { type: integer }
        total: { type: integer }

    ReportList:
      type: object
      required: [items, page, pageSize, total]
      properties:
        items:
          type: array
          items: { $ref: "#/components/schemas/StoredReport" }
        page: { type: integer }
        pageSize: { type: integer }
        total: { type: integer }

    Error:
      type: object
      required: [error, message]
      properties:
        error: { type: string }
        message: { type: string }
        details: { type: object, additionalProperties: true }

    ValidationError:
      allOf:
        - $ref: "#/components/schemas/Error"
        - type: object
          properties:
            issues:
              type: array
              items:
                type: object
                properties:
                  path: { type: string }
                  message: { type: string }

  responses:
    BadRequest:
      description: Malformed request
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Unauthorized:
      description: Missing or invalid credential
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Forbidden:
      description: Credential lacks scope or project authorization
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: Rate limit exceeded (default 60 requests / minute per credential)
      headers:
        Retry-After:
          schema: { type: integer }
        X-RateLimit-Limit:
          schema: { type: integer }
        X-RateLimit-Remaining:
          schema: { type: integer }
        X-RateLimit-Reset:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

  examples:
    MinimalReport:
      summary: Minimal valid report
      value:
        schemaVersion: "1.0"
        reportId: "550e8400-e29b-41d4-a716-446655440000"
        generatedAt: "2026-07-18T12:00:00.000Z"
        project:
          id: "11111111-1111-1111-1111-111111111111"
          name: "Example App"
          environment: production
          status: healthy
