{
  "openapi": "3.0.3",
  "info": {
    "title": "StartupOS frontend routes",
    "version": "1.0.0",
    "description": "Server routes served by the Next.js frontend on the same origin as the web app, under `/api`. The sign-in routes exchange credentials with Keycloak (realm `startupos`) on behalf of the browser and keep the refresh token in an HttpOnly cookie, so page scripts never see it. They are not part of the Quarkus services; the services are documented in their own references."
  },
  "security": [],
  "tags": [
    {
      "name": "Authentication",
      "description": "Sign in, renew and end a session. The access token is returned in the body; the refresh token lives only in the `startupos_session` HttpOnly cookie."
    },
    {
      "name": "Status",
      "description": "Public status summary used by the status page."
    },
    {
      "name": "AI",
      "description": "Server-side AI text helper."
    }
  ],
  "paths": {
    "/api/auth/token": {
      "post": {
        "operationId": "authToken",
        "tags": [
          "Authentication"
        ],
        "summary": "Sign in a demo persona and obtain an access token",
        "description": "Exchanges a persona id and that persona's password for a Keycloak access token using the password grant. Only the demo personas can be signed in here; any other `userId` is rejected with 400. The server holds no password. On success the response sets the `startupos_session` cookie (HttpOnly, `SameSite=Lax`, path `/api/auth`, `Secure` in production).",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "userId",
                  "password"
                ],
                "properties": {
                  "userId": {
                    "type": "string",
                    "description": "A demo persona id (the ids listed on the sign-in page)."
                  },
                  "password": {
                    "type": "string",
                    "format": "password",
                    "description": "The persona's password."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in.",
            "headers": {
              "Set-Cookie": {
                "description": "The `startupos_session` refresh-token cookie. Cleared instead if Keycloak issued no refresh token.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Body is not JSON, `userId` or `password` is missing, or `userId` is not an offered persona.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Keycloak did not accept the password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Keycloak could not be reached, or refused to issue a token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "This deployment is not configured to sign anyone in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/refresh": {
      "post": {
        "operationId": "authRefresh",
        "tags": [
          "Authentication"
        ],
        "summary": "Renew the access token from the session cookie",
        "description": "Redeems the refresh token held in the `startupos_session` cookie and returns a new access token. There is no request body. The cookie is rewritten with the refresh token Keycloak returns. If Keycloak refuses the token the cookie is cleared; if Keycloak is unreachable the cookie is kept.",
        "security": [],
        "parameters": [
          {
            "name": "startupos_session",
            "in": "cookie",
            "required": false,
            "description": "HttpOnly refresh-token cookie set by sign-in. Sent automatically by the browser.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Renewed.",
            "headers": {
              "Set-Cookie": {
                "description": "The rewritten `startupos_session` cookie.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "401": {
            "description": "There is no session cookie, or the session has ended. The body carries a null `token` and a `reason`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "nullable": true
                    },
                    "reason": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Keycloak could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "This deployment cannot renew a session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "operationId": "authLogout",
        "tags": [
          "Authentication"
        ],
        "summary": "End the session",
        "description": "Clears the `startupos_session` cookie and, when a refresh token is present, asks Keycloak to end it. The cookie is cleared whatever Keycloak answers, so the call always succeeds.",
        "security": [],
        "parameters": [
          {
            "name": "startupos_session",
            "in": "cookie",
            "required": false,
            "description": "HttpOnly refresh-token cookie. Sent automatically by the browser.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Signed out of this browser.",
            "headers": {
              "Set-Cookie": {
                "description": "The `startupos_session` cookie with `Max-Age=0`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "signedOut": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/status": {
      "get": {
        "operationId": "statusSummary",
        "tags": [
          "Status"
        ],
        "summary": "Get the public status summary",
        "description": "Reduced health of the user-facing components. The body carries only component names, states and timestamps. It is cached server-side and by the edge (`Cache-Control: public, max-age=10`). This replaces the public health endpoints, which are no longer reachable from the internet.",
        "security": [],
        "responses": {
          "200": {
            "description": "Status snapshot.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "`public, max-age=10, s-maxage=15, stale-while-revalidate=15`"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusSnapshot"
                }
              }
            }
          },
          "503": {
            "description": "Status is temporarily unavailable. Sent with Cache-Control no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ai/enhance": {
      "post": {
        "operationId": "aiEnhance",
        "tags": [
          "AI"
        ],
        "summary": "Generate text with the configured generative-AI model",
        "description": "Runs a single chat request against OCI Generative AI by invoking the OCI CLI on the frontend server, and returns the generated text. The route handler performs no authentication check of its own. Whether and how it is exposed in a given deployment is not verified here.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "prompt"
                ],
                "properties": {
                  "prompt": {
                    "type": "string",
                    "description": "The message to send."
                  },
                  "systemPrompt": {
                    "type": "string",
                    "description": "Optional system preamble. A forum-writing-assistant default is used when omitted."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated text.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "text": {
                      "type": "string"
                    },
                    "model": {
                      "type": "string"
                    },
                    "region": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`prompt` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Generation failed. The body also carries a `details` message.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "details": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "token",
          "expiresIn"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "Keycloak access token, sent to the services as a Bearer token in the Authorization header."
          },
          "expiresIn": {
            "type": "integer",
            "nullable": true,
            "description": "Lifetime in seconds as reported by Keycloak, or null if not reported."
          }
        }
      },
      "StatusSnapshot": {
        "type": "object",
        "required": [
          "overall",
          "components",
          "checkedAt"
        ],
        "properties": {
          "overall": {
            "type": "string",
            "enum": [
              "operational",
              "degraded",
              "partial_outage",
              "major_outage"
            ]
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time"
          },
          "components": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "description",
                "status",
                "checkedAt"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "operational",
                    "degraded",
                    "down"
                  ]
                },
                "checkedAt": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      }
    }
  }
}
