{
  "openapi": "3.1.0",
  "info": {
    "title": "AI CodeOps public API",
    "version": "2026-10-18",
    "description": "The endpoints you can call from outside the console: the release check for CI, evidence-pack verification, and status."
  },
  "servers": [
    {
      "url": "https://console.aicodeops.dev"
    }
  ],
  "components": {
    "securitySchemes": {
      "workspaceKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A workspace API key (Configure → API Keys). It acts as the member who created it, and needs workspace:read when its scope lists permissions."
      }
    },
    "schemas": {
      "ReleaseCheckInput": {
        "type": "object",
        "description": "Give pullRequestURL or commitSHA (at least one).",
        "properties": {
          "pullRequestURL": {
            "type": "string",
            "format": "uri",
            "description": "The pull request to check, as on your Git host."
          },
          "commitSHA": {
            "type": "string",
            "minLength": 7,
            "description": "Or the commit to check."
          },
          "requiredWorkflows": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Workflow ids that must have run and completed for a pass."
          },
          "blockingSeverity": {
            "type": "string",
            "enum": [
              "critical",
              "high",
              "medium",
              "low",
              "info"
            ],
            "default": "high",
            "description": "Open findings at or above this severity block."
          },
          "blockOnTestFailure": {
            "type": "boolean",
            "default": true,
            "description": "Failing tests block."
          },
          "minCoveragePct": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Coverage floor: the latest test run that reported coverage must meet it. Omitted, coverage isn't gated."
          },
          "disallowedLicenses": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "License ids that must not appear in the project's latest SBOM (exact, case-insensitive). A project with no SBOM fails the check."
          },
          "requireDeployedTo": {
            "type": "string",
            "description": "An environment the change must already have a succeeded deployment in (for example staging)."
          },
          "lookbackMs": {
            "type": "integer",
            "minimum": 1,
            "default": 1209600000,
            "description": "How far back to look (14 days by default)."
          },
          "blockOnInFlight": {
            "type": "boolean",
            "default": false,
            "description": "Deny while a considered session is still pending or running."
          }
        }
      },
      "ReleaseCheckVerdict": {
        "type": "object",
        "required": [
          "allowed",
          "evidenceComplete",
          "summary",
          "reasons",
          "blockingFindings",
          "testFailures",
          "coverageShortfall",
          "licenseViolations",
          "licenseEvidenceMissing",
          "priorEnvironmentMissing",
          "requiredGatesPassed",
          "missingRequiredWorkflows",
          "runsConsidered",
          "matchedBy"
        ],
        "properties": {
          "allowed": {
            "type": "boolean",
            "description": "Whether the change may ship."
          },
          "evidenceComplete": {
            "type": "boolean",
            "description": "False when the evidence was cut short; then the check denies."
          },
          "summary": {
            "type": "string",
            "description": "One line, for a status check."
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Why, one reason per line."
          },
          "blockingFindings": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "testFailures": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "coverageShortfall": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "coveragePct": {
                    "type": "number"
                  },
                  "required": {
                    "type": "number"
                  }
                }
              }
            ]
          },
          "licenseViolations": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "licenseEvidenceMissing": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Projects with no SBOM to check, when disallowedLicenses was given."
          },
          "priorEnvironmentMissing": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "required": {
                    "type": "string"
                  }
                }
              }
            ]
          },
          "requiredGatesPassed": {
            "type": "boolean"
          },
          "missingRequiredWorkflows": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "runsConsidered": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "matchedBy": {
            "type": "object",
            "properties": {
              "pullRequestURL": {
                "type": "string"
              },
              "commitSHA": {
                "type": "string"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      }
    }
  },
  "paths": {
    "/api/release/check": {
      "post": {
        "operationId": "releaseCheck",
        "summary": "Check whether a pull request or commit may ship",
        "description": "Applies the release gate across every session that touched the change: tests, findings, approvals, and the optional coverage, license and promotion checks.",
        "security": [
          {
            "workspaceKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseCheckInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The verdict. allowed is true or false; a denial is still a 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseCheckVerdict"
                }
              }
            }
          },
          "400": {
            "description": "The payload didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No key or session, or the key isn't valid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key belongs to another workspace, or lacks workspace:read.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many checks for this workspace; retryAfterMs says when to try again."
          }
        }
      },
      "get": {
        "operationId": "releaseCheckGet",
        "summary": "The same check, with query parameters",
        "description": "Accepts the same names as query parameters; lists are comma-separated.",
        "security": [
          {
            "workspaceKey": []
          }
        ],
        "parameters": [
          {
            "name": "pullRequestURL",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "commitSHA",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requiredWorkflows",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated."
          },
          {
            "name": "disallowedLicenses",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated."
          }
        ],
        "responses": {
          "200": {
            "description": "The verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseCheckVerdict"
                }
              }
            }
          }
        }
      }
    },
    "/api/evidence": {
      "get": {
        "operationId": "evidenceKeys",
        "summary": "The keys that sign evidence packs",
        "description": "Public. Returns the Ed25519 public keys, so a pack can be verified offline.",
        "parameters": [
          {
            "name": "action",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "keys"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The keys, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "algorithm": {
                      "type": "string",
                      "const": "Ed25519"
                    },
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "kid": {
                            "type": "string"
                          },
                          "publicKey": {
                            "type": "string",
                            "description": "PEM."
                          },
                          "created": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "retired": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "algorithm": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "evidenceVerify",
        "summary": "Verify an evidence pack",
        "description": "Public, rate-limited (30 checks per 10 minutes per address), packs up to 8 MB.",
        "parameters": [
          {
            "name": "action",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "verify"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Valid, with the pack's key and change; or not, with the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "valid": {
                          "const": true
                        },
                        "kid": {
                          "type": "string"
                        },
                        "changeID": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "generatedAt": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "valid": {
                          "const": false
                        },
                        "reason": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "The file is too large to be an evidence pack."
          },
          "429": {
            "description": "Too many checks; wait a few minutes."
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "health",
        "summary": "Liveness and readiness",
        "description": "Public, no API key needed. Returns ok or degraded with 200, or down with 503, for uptime monitors and load balancers.",
        "responses": {
          "200": {
            "description": "Up, or degraded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded",
                        "down"
                      ]
                    },
                    "service": {
                      "type": "string"
                    },
                    "timestamp": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Down."
          }
        }
      }
    },
    "/api/status": {
      "get": {
        "operationId": "status",
        "summary": "Component status",
        "description": "Public. The overall status and each component's, as the status page shows them.",
        "responses": {
          "200": {
            "description": "The status."
          }
        }
      }
    }
  }
}