{
    "openapi": "3.1.0",
    "info": {
      "title": "RiserLabs Public API",
      "version": "1.0.0",
      "description": "Starter OpenAPI spec for v1 routes (widget/public + project B2B)."
    },
    "servers": [
      {
        "url": "https://riserlabs.io"
      }
    ],
    "tags": [
      { "name": "Public" },
      { "name": "Chat" },
      { "name": "End Users" },
      { "name": "Personas" },
      { "name": "Debug" }
    ],
    "paths": {
      "/api/v1/public/end-user-token": {
        "post": {
          "tags": ["Public"],
          "operationId": "createPublicEndUserToken",
          "summary": "Issue end-user token for widget (public flow)",
          "description": "Requires projectKey + hostToken in body. Enforces rate limits, domain allowlist, captcha/billing gates.",
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PublicEndUserTokenRequest" }
              }
            }
          },
          "responses": {
            "200": {
              "description": "Token issued",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/PublicEndUserTokenResponse" }
                }
              }
            },
            "400": { "$ref": "#/components/responses/JsonError" },
            "401": { "$ref": "#/components/responses/JsonError" },
            "403": { "$ref": "#/components/responses/JsonError" },
            "429": {
              "description": "Rate limited or daily/widget limits",
              "headers": {
                "Retry-After": {
                  "description": "Seconds to wait before retry",
                  "schema": { "type": "string" }
                }
              },
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ErrorEnvelope" }
                }
              }
            },
            "500": { "$ref": "#/components/responses/JsonError" }
          }
        },
        "options": {
          "tags": ["Public"],
          "operationId": "optionsPublicEndUserToken",
          "summary": "CORS preflight",
          "responses": {
            "204": { "description": "No Content" }
          }
        }
      },
      "/api/v1/end-user-token": {
        "post": {
          "tags": ["End Users"],
          "operationId": "createEndUserTokenProjectAuth",
          "summary": "Issue end-user token (project-auth flow)",
          "security": [{ "BearerProject": [] }],
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProjectEndUserTokenRequest" }
              }
            }
          },
          "responses": {
            "200": {
              "description": "Token issued",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ProjectEndUserTokenResponse" }
                }
              }
            },
            "400": { "$ref": "#/components/responses/LegacyError" },
            "401": { "$ref": "#/components/responses/LegacyError" }
          }
        }
      },
      "/api/v1/end-users/persona": {
        "put": {
          "tags": ["End Users"],
          "operationId": "setEndUserPersona",
          "summary": "Set persona for an end-user and bump contextVersion",
          "security": [{ "BearerProject": [] }],
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SetPersonaRequest" }
              }
            }
          },
          "responses": {
            "200": {
              "description": "Persona updated",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/SetPersonaResponse" }
                }
              }
            },
            "400": { "$ref": "#/components/responses/LegacyErrorWithDetails" },
            "401": { "$ref": "#/components/responses/LegacyErrorWithDetails" },
            "404": { "$ref": "#/components/responses/LegacyErrorWithDetails" }
          }
        }
      },
      "/api/v1/personas/scenarios": {
        "get": {
          "tags": ["Personas"],
          "operationId": "getPersonaScenarios",
          "summary": "Get initial scenarios for current end-user persona",
          "security": [{ "BearerEndUser": [] }],
          "parameters": [
            {
              "name": "locale",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "enum": ["en", "ru", "es"],
                "default": "en"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "Scenarios list",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/PersonasScenariosResponse" }
                }
              }
            },
            "401": { "$ref": "#/components/responses/JsonError" },
            "404": { "$ref": "#/components/responses/JsonError" },
            "500": { "$ref": "#/components/responses/JsonError" }
          }
        }
      },
      "/api/v1/chat/active-conversation": {
        "get": {
          "tags": ["Chat"],
          "operationId": "getActiveConversation",
          "summary": "Get latest active conversation for current end-user",
          "security": [{ "BearerEndUser": [] }],
          "parameters": [
            {
              "name": "locale",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "enum": ["en", "ru"],
                "default": "en"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "Conversation or empty result",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ActiveConversationResponse" }
                }
              }
            },
            "403": { "$ref": "#/components/responses/JsonError" },
            "404": { "$ref": "#/components/responses/JsonError" },
            "500": { "$ref": "#/components/responses/JsonError" }
          }
        }
      },
      "/api/v1/chat/new-conversation": {
        "post": {
          "tags": ["Chat"],
          "operationId": "createNewConversation",
          "summary": "Create a new conversation for current end-user",
          "security": [{ "BearerEndUser": [] }],
          "requestBody": {
            "required": false,
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NewConversationRequest" }
              }
            }
          },
          "responses": {
            "200": {
              "description": "Created",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/NewConversationResponse" }
                }
              }
            },
            "403": { "$ref": "#/components/responses/JsonError" },
            "500": { "$ref": "#/components/responses/JsonError" }
          }
        }
      },
      "/api/v1/chat/messages": {
        "get": {
          "tags": ["Chat"],
          "operationId": "getChatMessages",
          "summary": "Get messages for selected/latest conversation",
          "security": [{ "BearerEndUser": [] }],
          "parameters": [
            {
              "name": "limit",
              "in": "query",
              "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 30 }
            },
            {
              "name": "before",
              "in": "query",
              "description": "Filter: createdAt < before",
              "schema": { "type": "string", "format": "date-time" }
            },
            {
              "name": "after",
              "in": "query",
              "description": "Filter: createdAt > after",
              "schema": { "type": "string", "format": "date-time" }
            },
            {
              "name": "types",
              "in": "query",
              "description": "CSV of message types, e.g. question,answer",
              "schema": { "type": "string", "example": "question,answer" }
            },
            {
              "name": "drafts",
              "in": "query",
              "schema": { "type": "string", "enum": ["0", "1"], "default": "0" }
            },
            {
              "name": "media",
              "in": "query",
              "schema": { "type": "string", "enum": ["0", "1"], "default": "1" }
            },
            {
              "name": "status",
              "in": "query",
              "description": "Strapi status mode",
              "schema": { "type": "string", "enum": ["draft", "published"], "default": "draft" }
            },
            {
              "name": "conversationDocumentId",
              "in": "query",
              "required": false,
              "schema": { "type": "string" }
            }
          ],
          "responses": {
            "200": {
              "description": "Messages list",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ChatMessagesResponse" }
                }
              }
            },
            "403": { "$ref": "#/components/responses/JsonError" },
            "404": { "$ref": "#/components/responses/JsonError" },
            "500": { "$ref": "#/components/responses/JsonError" }
          }
        }
      },
      "/api/v1/chat/stream": {
        "post": {
          "tags": ["Chat"],
          "operationId": "streamChatAnswer",
          "summary": "Stream chat answer via SSE",
          "description": "Returns text/event-stream with events: ready, chunk, done, error.",
          "security": [{ "BearerEndUser": [] }],
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChatStreamRequest" }
              }
            }
          },
          "responses": {
            "200": {
              "description": "SSE stream",
              "content": {
                "text/event-stream": {
                  "schema": { "type": "string" },
                  "example": "event: ready\ndata: {\"ok\":true,\"conversationId\":123,\"conversationDocumentId\":\"conv_xxx\",\"personaId\":\"persona_xxx\"}\n\nevent: chunk\ndata: {\"text\":\"Hello\"}\n\nevent: done\ndata: {\"ok\":true,\"answer\":\"Hello\",\"model\":\"gpt-4o-mini\"}\n\n"
                }
              },
              "headers": {
                "Cache-Control": {
                  "schema": { "type": "string", "example": "no-cache, no-transform" }
                },
                "Connection": {
                  "schema": { "type": "string", "example": "keep-alive" }
                },
                "X-Accel-Buffering": {
                  "schema": { "type": "string", "example": "no" }
                }
              }
            },
            "400": { "$ref": "#/components/responses/JsonError" },
            "401": { "$ref": "#/components/responses/JsonError" },
            "403": { "$ref": "#/components/responses/JsonError" },
            "404": { "$ref": "#/components/responses/JsonError" },
            "429": {
              "description": "Rate/daily limit reached",
              "headers": {
                "Retry-After": {
                  "schema": { "type": "string" }
                }
              },
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ErrorEnvelope" }
                }
              }
            },
            "500": { "$ref": "#/components/responses/JsonError" }
          },
          "x-sse-events": [
            {
              "name": "ready",
              "payloadSchema": {
                "type": "object",
                "properties": {
                  "ok": { "type": "boolean" },
                  "conversationId": { "type": ["integer", "null"] },
                  "conversationDocumentId": { "type": "string" },
                  "personaId": { "type": ["string", "null"] }
                }
              }
            },
            {
              "name": "chunk",
              "payloadSchema": {
                "type": "object",
                "properties": {
                  "text": { "type": "string" }
                }
              }
            },
            {
              "name": "done",
              "payloadSchema": {
                "type": "object",
                "properties": {
                  "ok": { "type": "boolean" },
                  "answer": { "type": "string" },
                  "assistantMessageDocumentId": { "type": ["string", "null"] },
                  "messageId": { "type": ["integer", "null"] },
                  "model": { "type": "string" },
                  "tokens": {
                    "type": "object",
                    "properties": {
                      "promptTokens": { "type": "integer" },
                      "completionTokens": { "type": "integer" },
                      "contextTokens": { "type": "integer" }
                    }
                  },
                  "cost": { "type": "number" },
                  "remaining": { "type": ["integer", "null"] },
                  "limits": {
                    "type": "object",
                    "properties": {
                      "ownerEventsRemaining": { "type": ["integer", "null"] },
                      "projectEventsRemaining": { "type": ["integer", "null"] }
                    }
                  }
                }
              }
            },
            {
              "name": "error",
              "payloadSchema": {
                "type": "object",
                "properties": {
                  "ok": { "type": "boolean", "const": false },
                  "error": { "type": "string" }
                }
              }
            }
          ]
        }
      },
      "/api/v1/whoami": {
        "get": {
          "tags": ["Debug"],
          "operationId": "whoAmI",
          "summary": "Debug token decode/verify",
          "description": "Internal/debug endpoint; do not expose in production docs if not needed.",
          "security": [{ "BearerEndUser": [] }],
          "responses": {
            "200": {
              "description": "Token info",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/WhoAmIResponseOk" }
                }
              }
            },
            "401": {
              "description": "Unauthorized",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/WhoAmIResponseError" }
                }
              }
            }
          },
          "x-internal": true
        }
      }
    },
    "components": {
      "securitySchemes": {
        "BearerEndUser": {
          "type": "http",
          "scheme": "bearer",
          "bearerFormat": "JWT",
          "description": "End-user JWT from /api/v1/public/end-user-token or /api/v1/end-user-token"
        },
        "BearerProject": {
          "type": "http",
          "scheme": "bearer",
          "bearerFormat": "JWT",
          "description": "Project-level bearer for backend-to-backend routes"
        }
      },
      "responses": {
        "JsonError": {
          "description": "Error envelope",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ErrorEnvelope" }
            }
          }
        },
        "LegacyError": {
          "description": "Legacy error envelope",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LegacyError" }
            }
          }
        },
        "LegacyErrorWithDetails": {
          "description": "Legacy error envelope with details",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LegacyErrorWithDetails" }
            }
          }
        }
      },
      "schemas": {
        "ErrorEnvelope": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean", "const": false },
            "error": { "type": "string" },
            "details": {}
          },
          "required": ["ok", "error"]
        },
        "LegacyError": {
          "type": "object",
          "properties": {
            "error": { "type": "string" }
          },
          "required": ["error"]
        },
        "LegacyErrorWithDetails": {
          "type": "object",
          "properties": {
            "error": { "type": "string" },
            "details": {}
          },
          "required": ["error"]
        },
        "PublicEndUserTokenRequest": {
          "type": "object",
          "properties": {
            "projectKey": { "type": "string" },
            "hostToken": { "type": "string" },
            "externalUserId": { "type": "string" },
            "metadata": {
              "type": "object",
              "additionalProperties": true
            },
            "captchaToken": { "type": "string" }
          },
          "required": ["projectKey", "hostToken"]
        },
        "PublicEndUserTokenResponse": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean", "const": true },
            "projectDocumentId": { "type": "string" },
            "endUserDocumentId": { "type": "string" },
            "endUserToken": { "type": "string" },
            "hostname": { "type": ["string", "null"] },
            "enabled": { "type": "boolean" },
            "messagesPerUser": { "type": "integer" }
          },
          "required": [
            "ok",
            "projectDocumentId",
            "endUserDocumentId",
            "endUserToken",
            "enabled",
            "messagesPerUser"
          ]
        },
        "ProjectEndUserTokenRequest": {
          "type": "object",
          "properties": {
            "externalUserId": { "type": "string" },
            "metadata": { "type": "object", "additionalProperties": true }
          },
          "required": ["externalUserId"]
        },
        "ProjectEndUserTokenResponse": {
          "type": "object",
          "properties": {
            "projectDocumentId": { "type": "string" },
            "endUserDocumentId": { "type": "string" },
            "endUserToken": { "type": "string" }
          },
          "required": ["projectDocumentId", "endUserDocumentId", "endUserToken"]
        },
        "SetPersonaRequest": {
          "type": "object",
          "properties": {
            "externalUserId": { "type": "string" },
            "personaId": { "type": "string", "description": "Persona documentId" }
          },
          "required": ["externalUserId", "personaId"]
        },
        "SetPersonaResponse": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean" },
            "endUserDocumentId": { "type": "string" },
            "personaId": { "type": "string" },
            "contextVersion": { "type": "integer" },
            "updatedEndUser": {}
          },
          "required": ["ok", "endUserDocumentId", "personaId", "contextVersion"]
        },
        "Scenario": {
          "type": "object",
          "properties": {
            "id": { "type": ["integer", "string"] },
            "title": { "type": "string" },
            "initialMessage": { "type": "string" },
            "promptInjection": { "type": "string" }
          },
          "required": ["initialMessage"]
        },
        "PersonasScenariosResponse": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean" },
            "source": { "type": "string", "enum": ["project_override", "persona"] },
            "overrideDocumentId": { "type": ["string", "null"] },
            "localeRequested": { "type": "string", "enum": ["en", "ru", "es"] },
            "localeUsed": { "type": "string", "enum": ["en", "ru", "es"] },
            "projectDocumentId": { "type": ["string", "null"] },
            "personaId": { "type": "string" },
            "personaName": { "type": "string" },
            "scenarios": {
              "type": "array",
              "items": { "$ref": "#/components/schemas/Scenario" }
            }
          },
          "required": ["ok", "source", "localeRequested", "localeUsed", "personaId", "scenarios"]
        },
        "ActiveConversationResponse": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean" },
            "conversationId": { "type": ["integer", "null"] },
            "conversationDocumentId": { "type": "string" },
            "title": { "type": ["string", "null"] },
            "language": { "type": ["string", "null"] },
            "lastMessageAt": { "type": ["string", "null"], "format": "date-time" }
          },
          "required": ["ok", "conversationId", "conversationDocumentId"]
        },
        "NewConversationRequest": {
          "type": "object",
          "properties": {
            "locale": { "type": "string", "enum": ["en", "ru"], "default": "en" }
          }
        },
        "NewConversationResponse": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean" },
            "conversationId": { "type": "integer" },
            "conversationDocumentId": { "type": "string" },
            "title": { "type": "string" },
            "language": { "type": "string" }
          },
          "required": ["ok", "conversationId", "conversationDocumentId", "title", "language"]
        },
        "ChatMessage": {
          "type": "object",
          "properties": {
            "id": { "type": "integer" },
            "documentId": { "type": "string" },
            "createdAt": { "type": "string", "format": "date-time" },
            "messageType": { "type": "string", "enum": ["question", "answer", "loading"] },
            "author": { "type": "string" },
            "content": { "type": "string" },
            "mcontentType": { "type": ["string", "null"] },
            "media": {
              "type": "array",
              "items": {}
            }
          },
          "required": ["id", "documentId", "createdAt", "messageType", "author", "content"]
        },
        "ChatMessagesResponse": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean" },
            "conversationDocumentId": { "type": ["string", "null"] },
            "data": {
              "type": "array",
              "items": { "$ref": "#/components/schemas/ChatMessage" }
            },
            "cursor": {
              "type": "object",
              "properties": {
                "before": { "type": ["string", "null"], "format": "date-time" },
                "after": { "type": ["string", "null"], "format": "date-time" }
              },
              "required": ["before", "after"]
            },
            "meta": {}
          },
          "required": ["ok", "conversationDocumentId", "data", "cursor"]
        },
        "ChatStreamRequest": {
          "type": "object",
          "properties": {
            "conversationDocumentId": { "type": "string" },
            "message": { "type": "string" },
            "promptInjection": { "type": "string" },
            "locale": { "type": "string", "enum": ["en", "ru", "es"], "default": "en" },
            "webSearchEnabled": { "type": "boolean", "default": false }
          },
          "required": ["message"]
        },
        "WhoAmIResponseOk": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean", "const": true },
            "tokenLength": { "type": "integer" },
            "decoded": {},
            "claims": {},
            "secretLen": { "type": "integer" }
          },
          "required": ["ok", "tokenLength", "decoded", "claims", "secretLen"]
        },
        "WhoAmIResponseError": {
          "type": "object",
          "properties": {
            "ok": { "type": "boolean", "const": false },
            "error": { "type": "string" },
            "name": { "type": ["string", "null"] },
            "secretLen": { "type": "integer" }
          },
          "required": ["ok", "error", "secretLen"]
        }
      }
    }
  }