{
  "openapi": "3.0.0",
  "info": {
    "title": "Dulyx API (superfície pública)",
    "version": "1.0",
    "description": "Recorte público da API da Dulyx: o que se chama com API Token (rotas v3), o servidor MCP, o fluxo OAuth 2.0 que o protege e o widget de chave pública. Gerado a partir do spec completo em https://api.dulyx.com.br/api/docs-json, sem edição manual. Documentação: https://dulyx.com.br/desenvolvedores",
    "contact": {
      "name": "Dulyx",
      "email": "contato@dulyx.com.br",
      "url": "https://dulyx.com.br/contato"
    },
    "termsOfService": "https://dulyx.com.br/termos-de-uso"
  },
  "externalDocs": {
    "description": "Dulyx para desenvolvedores",
    "url": "https://dulyx.com.br/desenvolvedores"
  },
  "servers": [
    {
      "url": "https://api.dulyx.com.br",
      "description": "Produção"
    }
  ],
  "tags": [
    {
      "name": "Agent V3 (API Token)"
    },
    {
      "name": "Agents V3 (API Token)"
    },
    {
      "name": "MCP Server (API Token)"
    },
    {
      "name": "MCP OAuth"
    },
    {
      "name": "Widget (chave pública)"
    }
  ],
  "paths": {
    "/api/v3/agent/chat": {
      "post": {
        "description": "Mesmo motor do chat v2. Autentique com \"Authorization: Bearer <api_token>\" — o cliente é resolvido automaticamente a partir do token. Com \"stream\": true a resposta sai em SSE, igual a POST /chat/stream.",
        "operationId": "AgentV3Controller_chat",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RagChatDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta gerada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Chat com o agente autenticado via API Token",
        "tags": [
          "Agent V3 (API Token)"
        ]
      }
    },
    "/api/v3/agent/chat/stream": {
      "post": {
        "description": "Mesma rota de streaming do chat da tela: eventos `meta`, `token`, `activity_start`, `activity_end`, `done` e `error`.",
        "operationId": "AgentV3Controller_chatStreamEndpoint",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RagChatDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Chat com streaming (SSE) autenticado via API Token",
        "tags": [
          "Agent V3 (API Token)"
        ]
      }
    },
    "/api/v3/widget/config": {
      "get": {
        "description": "Chamada pelo SDK na carga da página. Não consome crédito nem franquia. É o que permite ao dono do agente mudar cor, saudação e sugestões sem redeploy no site do integrador.",
        "operationId": "WidgetController_config",
        "parameters": [
          {
            "name": "key",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Configuração do widget"
          }
        },
        "summary": "Bootstrap do widget — agente, aparência e textos",
        "tags": [
          "Widget (chave pública)"
        ]
      }
    },
    "/api/v3/widget/chat": {
      "post": {
        "description": "Com \"stream\": true a resposta sai em SSE, igual a POST /chat/stream.",
        "operationId": "WidgetController_chat",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WidgetChatDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta gerada"
          }
        },
        "summary": "Turno do chat embarcado",
        "tags": [
          "Widget (chave pública)"
        ]
      }
    },
    "/api/v3/widget/chat/stream": {
      "post": {
        "description": "Eventos `meta`, `token`, `activity_start`, `activity_end`, `done` e `error`.",
        "operationId": "WidgetController_chatStream",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WidgetChatDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Turno do chat embarcado com streaming (SSE)",
        "tags": [
          "Widget (chave pública)"
        ]
      }
    },
    "/api/v3/widget/conversation": {
      "get": {
        "description": "Recuperação depois de um stream que caiu, e histórico ao recarregar a página. NÃO custa crédito — é o que impede o SDK de reexecutar (e cobrar) um turno que o servidor já terminou.",
        "operationId": "WidgetController_conversation",
        "parameters": [
          {
            "name": "sessionId",
            "required": true,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Mensagens de uma conversa do widget",
        "tags": [
          "Widget (chave pública)"
        ]
      }
    },
    "/api/v3/widget/feedback": {
      "post": {
        "operationId": "WidgetController_feedback",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WidgetFeedbackDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Avaliar a resposta de um turno (👍/👎)",
        "tags": [
          "Widget (chave pública)"
        ],
        "description": "Avaliar a resposta de um turno (👍/👎)"
      }
    },
    "/api/v3/agents": {
      "get": {
        "operationId": "AgentsV3Controller_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Lista de agentes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AgentV3Dto"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Listar os agentes disponíveis para este API Token",
        "tags": [
          "Agents V3 (API Token)"
        ],
        "description": "Listar os agentes disponíveis para este API Token"
      }
    },
    "/api/v3/mcp": {
      "post": {
        "description": "Métodos: initialize, tools/list, tools/call, ping. Cada tools/call executa direto na origem e devolve dado estruturado — sem LLM do lado do Dulyx. Cobrada por chamada (Parâmetros → mcpCreditosPorChamada).",
        "operationId": "McpController_rpc",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "oauth2": [
              "mcp"
            ]
          }
        ],
        "summary": "Endpoint JSON-RPC do servidor MCP",
        "tags": [
          "MCP Server (API Token)"
        ]
      }
    },
    "/api/v3/mcp/icone.svg": {
      "get": {
        "operationId": "McpController_iconeSvg",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "oauth2": [
              "mcp"
            ]
          }
        ],
        "tags": [
          "MCP Server (API Token)"
        ],
        "description": "GET /api/v3/mcp/icone.svg"
      }
    },
    "/api/v3/mcp/icone.png": {
      "get": {
        "operationId": "McpController_iconePng",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "bearer": []
          },
          {
            "oauth2": [
              "mcp"
            ]
          }
        ],
        "tags": [
          "MCP Server (API Token)"
        ],
        "description": "GET /api/v3/mcp/icone.png"
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "operationId": "OAuthController_recursoProtegido",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Metadata do recurso protegido (RFC 9728)",
        "tags": [
          "MCP OAuth"
        ],
        "description": "Metadata do recurso protegido (RFC 9728)"
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "operationId": "OAuthController_authorizationServer",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Metadata do authorization server (RFC 8414)",
        "tags": [
          "MCP OAuth"
        ],
        "description": "Metadata do authorization server (RFC 8414)"
      }
    },
    "/oauth/register": {
      "post": {
        "operationId": "OAuthController_registrar",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "summary": "Registro dinâmico de cliente OAuth (RFC 7591)",
        "tags": [
          "MCP OAuth"
        ],
        "description": "Registro dinâmico de cliente OAuth (RFC 7591)"
      }
    },
    "/oauth/authorize": {
      "get": {
        "operationId": "OAuthController_authorize",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Início do fluxo de autorização (redireciona para a tela)",
        "tags": [
          "MCP OAuth"
        ],
        "description": "Início do fluxo de autorização (redireciona para a tela)"
      }
    },
    "/api/v1/oauth/aprovar": {
      "post": {
        "operationId": "OAuthController_aprovar",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Aprova a autorização e devolve a URL de redirect",
        "tags": [
          "MCP OAuth"
        ],
        "description": "Aprova a autorização e devolve a URL de redirect"
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "OAuthController_token",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Troca o código de autorização pelo access token",
        "tags": [
          "MCP OAuth"
        ],
        "description": "Troca o código de autorização pelo access token"
      }
    }
  },
  "components": {
    "schemas": {
      "ActionChangeDto": {
        "type": "object",
        "properties": {
          "campo": {
            "type": "string",
            "example": "preco"
          },
          "label": {
            "type": "string",
            "example": "Preço do produto"
          },
          "de": {
            "type": "string",
            "example": "35.9"
          },
          "para": {
            "type": "string",
            "example": "39.49"
          },
          "nomeRef": {
            "type": "string",
            "example": "Ketchup Tradicional 400g",
            "description": "Nome do registro apontado, quando o campo é uma referência a outro recurso."
          }
        },
        "required": [
          "campo",
          "label",
          "para"
        ]
      },
      "ActionConfirmationDto": {
        "type": "object",
        "properties": {
          "actionId": {
            "type": "number",
            "example": 3
          },
          "actionNome": {
            "type": "string",
            "example": "Altera Produto"
          },
          "metodo": {
            "type": "string",
            "example": "PUT",
            "enum": [
              "GET",
              "POST",
              "PUT",
              "DELETE"
            ]
          },
          "alvo": {
            "$ref": "#/components/schemas/ActionTargetDto"
          },
          "mudancas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionChangeDto"
            }
          },
          "referencias": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionReferenceDto"
            }
          }
        },
        "required": [
          "actionId",
          "actionNome",
          "metodo",
          "mudancas"
        ]
      },
      "ActionReferenceDto": {
        "type": "object",
        "properties": {
          "campo": {
            "type": "string",
            "example": "produto_id"
          },
          "valor": {
            "type": "string",
            "example": "46"
          },
          "nome": {
            "type": "string",
            "example": "Ketchup Tradicional 400g"
          },
          "fonte": {
            "type": "string",
            "example": "produtos",
            "description": "Recurso de onde o nome veio, ou \"llm\" quando a própria LLM o informou."
          }
        },
        "required": [
          "campo",
          "valor",
          "nome",
          "fonte"
        ]
      },
      "ActionTargetDto": {
        "type": "object",
        "properties": {
          "nome": {
            "type": "string",
            "example": "Fralda Descartável Tamanho M (30 un)"
          },
          "identificadores": {
            "type": "object",
            "example": {
              "id": "42"
            },
            "description": "Chaves que identificam o registro alterado (ex.: id)."
          }
        },
        "required": [
          "identificadores"
        ]
      },
      "ActivityStepDto": {
        "type": "object",
        "properties": {
          "tool": {
            "type": "string",
            "example": "Consultar Pedidos"
          },
          "args": {
            "type": "object",
            "example": {
              "cliente_id": "42"
            }
          },
          "status": {
            "type": "string",
            "example": "success",
            "enum": [
              "success",
              "error"
            ]
          },
          "summary": {
            "type": "string",
            "example": "Consultou 3 pedidos do cliente 42"
          },
          "timestamp": {
            "type": "number",
            "example": 1735689600000
          }
        },
        "required": [
          "tool",
          "status",
          "summary",
          "timestamp"
        ]
      },
      "AgentV3Dto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "example": 1,
            "description": "Valor de `agentId` no chat."
          },
          "nome": {
            "type": "string",
            "example": "Ana"
          },
          "cargo": {
            "type": "string",
            "example": "Atendimento de pedidos"
          },
          "descricao": {
            "type": "string",
            "example": "Responde dúvidas sobre status de entrega"
          },
          "avatar": {
            "type": "object",
            "description": "Receita do mascote Duly (camadas do avatar)."
          },
          "padrao": {
            "type": "boolean",
            "example": false,
            "description": "É o agente padrão do cliente (o pré-selecionado no chat)."
          },
          "respondePorPadrao": {
            "type": "boolean",
            "example": true,
            "description": "É quem responde quando a chamada NÃO informa `agentId`. Leva em conta o agente padrão deste token e a whitelist dele — pode ser diferente de `padrao`."
          }
        },
        "required": [
          "id",
          "nome",
          "avatar",
          "padrao",
          "respondePorPadrao"
        ]
      },
      "ChatFonteDto": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "example": "https://centraldeatendimento.totvs.com/hc/pt-br/articles/360027266772"
          },
          "titulo": {
            "type": "string",
            "example": "Central de Atendimento TOTVS"
          }
        },
        "required": [
          "url",
          "titulo"
        ]
      },
      "ChatOptionDto": {
        "type": "object",
        "properties": {
          "rotulo": {
            "type": "string",
            "example": "Fulano da Silva (cliente 1)",
            "description": "Identifica a alternativa sozinho — é o texto que o front reenvia como a próxima mensagem quando o usuário clica."
          },
          "descricao": {
            "type": "string",
            "example": "Pedido 1 · 20/07/2026 · R$ 40,90",
            "description": "O que distingue esta alternativa das outras."
          }
        },
        "required": [
          "rotulo"
        ]
      },
      "ChatResponseDto": {
        "type": "object",
        "properties": {
          "answer": {
            "type": "string",
            "example": "A resposta para a sua pergunta é..."
          },
          "usage": {
            "nullable": true,
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/LlmUsageDto"
              }
            ]
          },
          "missingParams": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sessionId": {
            "type": "string",
            "example": "session-abc123"
          },
          "costDetails": {
            "description": "Custo em créditos do turno — só presente para usuários admin.",
            "allOf": [
              {
                "$ref": "#/components/schemas/CostDetailsDto"
              }
            ]
          },
          "contextUsage": {
            "description": "Ocupação da janela do modelo no turno. `percent` vai para todo mundo; tokens, janela, modelo e breakdown só para admin. Ausente quando o turno não chamou LLM (BRAIN, cache, confirmação).",
            "allOf": [
              {
                "$ref": "#/components/schemas/ContextUsageDto"
              }
            ]
          },
          "pendingConfirmation": {
            "type": "boolean",
            "example": true,
            "description": "true quando `answer` é um pedido de confirmação de Ação mutante, aguardando sim/não do usuário."
          },
          "confirmation": {
            "description": "Presente junto com `pendingConfirmation`: mesma confirmação em formato estruturado.",
            "allOf": [
              {
                "$ref": "#/components/schemas/ActionConfirmationDto"
              }
            ]
          },
          "opcoes": {
            "description": "Alternativas da pergunta em `answer`, quando o turno terminou pedindo ao usuário que escolha uma. O texto continua completo — isto é a mesma escolha em formato clicável.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatOptionDto"
            }
          },
          "activity": {
            "description": "Rastro de tool calls do turno — só presente quando `includeActivity` é enviado no request.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActivityStepDto"
            }
          },
          "fontes": {
            "description": "Fontes citáveis dos documentos que sustentaram a resposta. Derivado do conteúdo — só entra quando o documento traz URL junto de um marcador de fonte.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatFonteDto"
            }
          }
        },
        "required": [
          "answer"
        ]
      },
      "ContextBreakdownDto": {
        "type": "object",
        "properties": {
          "system": {
            "type": "number",
            "example": 1800,
            "description": "Instruções (system prompt)."
          },
          "tools": {
            "type": "number",
            "example": 520,
            "description": "Schemas das ferramentas."
          },
          "conversation": {
            "type": "number",
            "example": 900,
            "description": "Pergunta, histórico e fatos embutidos neles."
          },
          "toolResults": {
            "type": "number",
            "example": 6400,
            "description": "Retorno das ferramentas — a fatia que cresce no turno."
          }
        },
        "required": [
          "system",
          "tools",
          "conversation",
          "toolResults"
        ]
      },
      "ContextUsageDto": {
        "type": "object",
        "properties": {
          "percent": {
            "type": "object",
            "example": 12,
            "nullable": true,
            "description": "Quanto da janela do modelo o turno ocupou, em %. null quando a janela do modelo não está cadastrada."
          },
          "persistentPercent": {
            "type": "object",
            "example": 3,
            "nullable": true,
            "description": "Ocupação da CONVERSA: só o que segue para o próximo prompt (instruções + ferramentas + histórico). Sobe de forma monótona; `percent` oscila com o dado que cada turno consultou."
          },
          "calibrated": {
            "type": "boolean",
            "example": true,
            "description": "false enquanto a régua chars/token daquele modelo ainda é o chute inicial. É sempre estimativa."
          },
          "estimatedTokens": {
            "type": "number",
            "example": 9620,
            "description": "Só para admin Dulyx."
          },
          "persistentTokens": {
            "type": "number",
            "example": 3220,
            "description": "Só para admin Dulyx."
          },
          "windowTokens": {
            "type": "object",
            "example": 128000,
            "nullable": true
          },
          "breakdown": {
            "$ref": "#/components/schemas/ContextBreakdownDto"
          },
          "model": {
            "type": "string",
            "example": "openai/gpt-5-nano"
          }
        },
        "required": [
          "percent",
          "persistentPercent",
          "calibrated"
        ]
      },
      "CostBreakdownEntryDto": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "example": "rag_search"
          },
          "credits": {
            "type": "number",
            "example": 1
          },
          "formula": {
            "type": "string",
            "example": "fixo configurado (dulyx_billing_config) = 1"
          },
          "inputTokens": {
            "type": "number",
            "example": 350
          },
          "outputTokens": {
            "type": "number",
            "example": 120
          },
          "cost": {
            "type": "number",
            "example": 0.000842
          }
        },
        "required": [
          "type",
          "credits",
          "formula"
        ]
      },
      "CostDetailsDto": {
        "type": "object",
        "properties": {
          "totalCredits": {
            "type": "number",
            "example": 3
          },
          "breakdown": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CostBreakdownEntryDto"
            }
          }
        },
        "required": [
          "totalCredits",
          "breakdown"
        ]
      },
      "LlmUsageDto": {
        "type": "object",
        "properties": {
          "promptTokens": {
            "type": "number",
            "example": 120
          },
          "completionTokens": {
            "type": "number",
            "example": 80
          },
          "totalTokens": {
            "type": "number",
            "example": 200
          },
          "cachedPromptTokens": {
            "type": "number",
            "example": 1024,
            "description": "Parte de promptTokens servida pelo cache de prefixo do provedor (já incluída em promptTokens). 0 quando o provedor não informa."
          }
        },
        "required": [
          "promptTokens",
          "completionTokens",
          "totalTokens"
        ]
      },
      "MentionDto": {
        "type": "object",
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "agente",
              "pessoa"
            ],
            "example": "agente"
          },
          "id": {
            "type": "string",
            "example": "3",
            "description": "dulyx_agents.id (agente) ou dulyx_users.user_id (pessoa)."
          }
        },
        "required": [
          "tipo",
          "id"
        ]
      },
      "RagChatDto": {
        "type": "object",
        "properties": {
          "question": {
            "type": "string",
            "example": "Como funciona o sistema?"
          },
          "contexts": {
            "example": [
              "regras de negocio",
              "pedidos"
            ],
            "description": "Filtro de contexto. Omitir para buscar em todos.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "agentId": {
            "type": "number",
            "example": 1,
            "description": "Agente que responde. Omitir usa o agente da conversa (quando ela já existe) ou o agente padrão do cliente. Pedir um agente diferente do da conversa retorna 400 — troca de agente é conversa nova."
          },
          "sessionId": {
            "type": "string",
            "example": "session-abc123",
            "description": "ID da sessão para manter contexto."
          },
          "params": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "campo": "valor"
            },
            "description": "Parâmetros fornecidos em resposta a uma pergunta anterior."
          },
          "data": {
            "type": "object",
            "example": [
              {
                "id": 1,
                "name": "Produto A"
              }
            ],
            "description": "Dados JSON (qualquer estrutura válida) que será fornecido ao motor de IA para análise direta. Pode ser um array, objeto aninhado, etc."
          },
          "stream": {
            "type": "boolean",
            "example": false,
            "default": false,
            "description": "Habilita streaming (SSE) da resposta. Desligado por padrão; só transmite em chunks quando explicitamente true."
          },
          "includeActivity": {
            "type": "boolean",
            "example": false,
            "default": false,
            "description": "Quando true, a resposta inclui `activity`: o rastro de tool calls (consultas/ações) executadas neste turno."
          },
          "mentions": {
            "description": "Menções `@` resolvidas no cliente. Agente: consulta obrigatória neste turno, limitada aos delegados do agente atual — fora disso a menção é ignorada (nunca 400: menção inválida não invalida a pergunta). Pessoa: molda a FORMA da resposta; não envia nada a ela.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MentionDto"
            }
          }
        },
        "required": [
          "question"
        ]
      },
      "WidgetChatDto": {
        "type": "object",
        "properties": {
          "question": {
            "type": "string",
            "example": "cadê meu pedido 4471?"
          },
          "sessionId": {
            "type": "string",
            "example": "a1b2c3d4e5f6..."
          },
          "visitorId": {
            "type": "string",
            "description": "Id do browser, gerado e persistido pelo SDK. É o dono da conversa: sem ele o histórico não é gravado e a recuperação não acha nada."
          },
          "publicKey": {
            "type": "string",
            "description": "A chave pública, quando não vier em `x-dulyx-key`."
          },
          "stream": {
            "type": "boolean",
            "example": false,
            "default": false
          },
          "params": {
            "type": "object",
            "example": {
              "cliente": "ACME"
            },
            "description": "Resposta a um `missingParams` do turno anterior."
          }
        },
        "required": [
          "question"
        ]
      },
      "WidgetFeedbackDto": {
        "type": "object",
        "properties": {
          "requestId": {
            "type": "string",
            "example": "req_abc123"
          },
          "rating": {
            "type": "number",
            "example": 1,
            "enum": [
              1,
              -1
            ]
          },
          "comment": {
            "type": "string",
            "example": "respondeu o que eu precisava"
          },
          "visitorId": {
            "type": "string"
          },
          "publicKey": {
            "type": "string"
          }
        },
        "required": [
          "requestId",
          "rating"
        ]
      }
    },
    "securitySchemes": {
      "bearer": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "type": "http"
      },
      "oauth2": {
        "type": "oauth2",
        "description": "Autorização de apps MCP (Claude, ChatGPT, Cursor…). Registro dinâmico de cliente (RFC 7591) e metadata em https://api.dulyx.com.br/.well-known/oauth-authorization-server",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.dulyx.com.br/oauth/authorize",
            "tokenUrl": "https://api.dulyx.com.br/oauth/token",
            "scopes": {
              "mcp": "Chamar o servidor MCP em nome do usuário que autorizou; alcança só os contextos liberados para ele."
            }
          }
        }
      }
    }
  }
}
