{"openapi":"3.1.0","info":{"title":"ControlHub Labs API","version":"1.1.0","description":"API da plataforma de gestão de agentes de IA ControlHub Labs: gerencie agentes, converse com eles, alimente a base de conhecimento, dispare automações e, no módulo Atendimento, responda conversas dos canais.\n\n**Autenticação.** Agentes e chat aceitam a **chave de API** (`sk-live-...`), criada no painel em Empresa → API & Devs. Conhecimento e caixa de entrada são as rotas que o próprio painel usa: pedem o **token de sessão** de uma pessoa logada e recusam a chave de API. O webhook de automação não pede credencial; se o fluxo tiver segredo de assinatura, a requisição precisa vir assinada.","contact":{"email":"dev@controlhublabs.io"}},"servers":[{"url":"https://controlhublabs.io","description":"Produção"}],"components":{"securitySchemes":{"chaveDeApi":{"type":"http","scheme":"bearer","description":"Chave de API no cabeçalho `Authorization: Bearer sk-live-...`. Crie em Empresa → API & Devs; a chave aparece uma vez só."},"sessao":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Token de acesso da sessão de uma pessoa logada no painel, em `Authorization: Bearer <token>`. A chave de API não é aceita aqui."}},"schemas":{"Erro":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","description":"Presente em alguns erros, como `VALIDATION_ERROR` e `NOT_FOUND`."}}},"ErroDoChat":{"type":"object","properties":{"success":{"type":"boolean","const":false},"error":{"type":"string"},"code":{"type":"string","description":"`NO_QUOTA` ou `QUOTA_EXCEEDED` nos erros 402."}}},"AgenteNaLista":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string","enum":["agent","expert"]},"description":{"type":["string","null"]},"llm_model":{"type":["string","null"]}}},"Agente":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string","enum":["agent","expert"]},"description":{"type":["string","null"]},"llm_model":{"type":["string","null"]},"system_prompt":{"type":["string","null"]},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"AgenteNovo":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"type":{"type":"string","enum":["agent","expert"],"default":"agent"},"llm_model":{"type":"string","maxLength":100,"default":"anthropic/claude-sonnet-5.5","description":"ID do modelo no formato provedor/modelo."},"system_prompt":{"type":"string","maxLength":10000},"description":{"type":"string","maxLength":2000},"whatsapp_instance_id":{"type":["string","null"],"format":"uuid"}}},"AgenteAlterado":{"type":"object","minProperties":1,"description":"Envie só os campos que quer mudar; pelo menos um.","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"llm_model":{"type":"string","maxLength":100},"system_prompt":{"type":["string","null"],"maxLength":10000},"description":{"type":["string","null"],"maxLength":2000},"whatsapp_instance_id":{"type":["string","null"],"format":"uuid"},"is_active":{"type":"boolean"}}},"MensagemDoChat":{"type":"object","required":["agent_id","message"],"properties":{"agent_id":{"type":"string","description":"ID do agente que responde."},"message":{"type":"string","description":"Mensagem do usuário."},"session_id":{"type":"string","description":"ID da conversa. Omita na primeira mensagem e reenvie o devolvido nas seguintes."},"image":{"type":"string","description":"Imagem opcional em base64 ou data URL (`data:image/...;base64,...`). Sem o prefixo `data:`, é tratada como PNG."}}},"RespostaDoChat":{"type":"object","properties":{"success":{"type":"boolean","const":true},"response":{"type":"string","description":"Resposta do agente."},"session_id":{"type":"string","description":"ID da conversa, para reenviar na próxima mensagem."}}}}},"paths":{"/api/v1/agents":{"get":{"summary":"Listar agentes","description":"Lista os agentes ativos do workspace de quem é dono da chave, em páginas.","tags":["Agentes"],"security":[{"chaveDeApi":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Agentes ativos.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AgenteNaLista"}}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"429":{"description":"Mais de 30 requisições por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}},"post":{"summary":"Criar agente","tags":["Agentes"],"security":[{"chaveDeApi":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgenteNovo"}}}},"responses":{"201":{"description":"Agente criado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agente"}}}},"400":{"description":"Corpo inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"429":{"description":"Mais de 30 requisições por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}},"put":{"summary":"Alterar agente","tags":["Agentes"],"security":[{"chaveDeApi":[]}],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"ID do agente."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgenteAlterado"}}}},"responses":{"200":{"description":"Agente alterado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agente"}}}},"400":{"description":"`id` ausente ou inválido, ou nenhum campo para alterar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Agente não encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"429":{"description":"Mais de 30 requisições por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}},"delete":{"summary":"Desativar ou excluir agente","description":"Por padrão desativa o agente. Com `hard=true`, exclui de vez.","tags":["Agentes"],"security":[{"chaveDeApi":[]}],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"ID do agente."},{"name":"hard","in":"query","required":false,"schema":{"type":"string","enum":["true"]},"description":"`true` exclui de vez; qualquer outro valor desativa."}],"responses":{"200":{"description":"Agente desativado ou excluído.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"action":{"type":"string","enum":["deactivated","deleted"]}}}}}},"400":{"description":"`id` ausente ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Agente não encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"429":{"description":"Mais de 30 requisições por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}}},"/api/v1/chat":{"post":{"summary":"Enviar mensagem a um agente","description":"Envia uma mensagem e devolve a resposta inteira (sem streaming). A conversa é criada na primeira mensagem.","tags":["Chat"],"security":[{"chaveDeApi":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MensagemDoChat"}}}},"responses":{"200":{"description":"Resposta do agente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RespostaDoChat"}}}},"400":{"description":"Corpo inválido, ou `agent_id`/`message` ausentes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErroDoChat"}}}},"401":{"description":"Sem chave, ou chave inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErroDoChat"}}}},"402":{"description":"Sem cota de tokens no plano.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErroDoChat"}}}},"403":{"description":"Modelo do agente não permitido no plano.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErroDoChat"}}}},"404":{"description":"Agente não encontrado, inativo ou pausado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErroDoChat"}}}},"429":{"description":"Mais de 20 requisições por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErroDoChat"}}}},"500":{"description":"Erro interno ou falha do provedor de IA.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErroDoChat"}}}},"502":{"description":"O modelo não devolveu resposta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErroDoChat"}}}}}}},"/api/v1/knowledge/upload":{"post":{"summary":"Enviar documento para a base de conhecimento","tags":["Conhecimento"],"security":[{"sessao":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","agentId"],"properties":{"file":{"type":"string","format":"binary","description":"PDF, DOCX, TXT, CSV ou MD, até 10 MB."},"agentId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Documento processado.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"file_name":{"type":"string"},"chunks":{"type":"integer","description":"Trechos indexados."}}}}}},"400":{"description":"Arquivo ausente, grande demais, de formato não aceito ou sem texto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Agente não encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"429":{"description":"Mais de 10 envios por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"503":{"description":"Indexação indisponível no momento.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}}},"/api/v1/knowledge/url":{"post":{"summary":"Importar uma página da web para a base de conhecimento","tags":["Conhecimento"],"security":[{"sessao":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","agentId"],"properties":{"url":{"type":"string","format":"uri","description":"Endereço http(s) público."},"agentId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Página importada.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"url":{"type":"string"},"chunks":{"type":"integer"}}}}}},"400":{"description":"Endereço inválido, bloqueado ou sem texto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Agente não encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"503":{"description":"Indexação indisponível no momento.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}}},"/api/v1/knowledge/notion":{"post":{"summary":"Importar uma página do Notion para a base de conhecimento","tags":["Conhecimento"],"security":[{"sessao":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["agentId","notionPageId","notionToken"],"properties":{"agentId":{"type":"string","format":"uuid"},"notionPageId":{"type":"string","description":"ID da página no Notion."},"notionToken":{"type":"string","description":"Token de integração do Notion."}}}}}},"responses":{"200":{"description":"Página importada.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"chunks":{"type":"integer"},"characters":{"type":"integer"},"title":{"type":"string"}}}}}},"400":{"description":"Corpo inválido, ou página vazia.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Agente não encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno ou do Notion.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"503":{"description":"Indexação indisponível no momento.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}}},"/api/v1/knowledge/gdrive":{"post":{"summary":"Importar um arquivo do Google Drive para a base de conhecimento","tags":["Conhecimento"],"security":[{"sessao":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["agentId","fileId","accessToken"],"properties":{"agentId":{"type":"string","format":"uuid"},"fileId":{"type":"string","description":"ID do arquivo no Google Drive."},"accessToken":{"type":"string","description":"Access token OAuth 2.0 do Google."}}}}}},"responses":{"200":{"description":"Arquivo importado.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"chunks":{"type":"integer"},"characters":{"type":"integer"},"fileName":{"type":"string"}}}}}},"400":{"description":"Campos ausentes, ou tipo de arquivo não aceito.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Agente não encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"429":{"description":"Mais de 10 requisições por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno ou do Google.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}}},"/api/v1/inbox/reply":{"post":{"summary":"Responder uma conversa da caixa de entrada","description":"Módulo Atendimento. A resposta é gravada e enviada ao canal da conversa.","tags":["Caixa de entrada"],"security":[{"sessao":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["conversation_id","content"],"properties":{"conversation_id":{"type":"string","format":"uuid"},"content":{"type":"string"}}}}}},"responses":{"200":{"description":"Mensagem gravada.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"conversation_id":{"type":"string","format":"uuid"},"sender_type":{"type":"string","const":"human"},"content":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}},"400":{"description":"`conversation_id` ou `content` ausentes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Conversa não encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"429":{"description":"Mais de 30 requisições por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}}},"/api/v1/inbox/resolve":{"post":{"summary":"Encerrar uma conversa, ou devolvê-la ao agente","description":"Módulo Atendimento.","tags":["Caixa de entrada"],"security":[{"sessao":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["conversation_id"],"properties":{"conversation_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["resolved","bot_active"],"default":"resolved","description":"`bot_active` devolve a conversa ao agente; qualquer outro valor encerra."},"reason":{"type":"string","description":"Motivo do encerramento."},"closingMessage":{"type":"string","description":"Mensagem de despedida enviada ao contato."}}}}}},"responses":{"200":{"description":"Conversa atualizada.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"status":{"type":"string","enum":["resolved","bot_active"]}}}}}},"400":{"description":"`conversation_id` ausente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Sem credencial, ou credencial inválida.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Sem permissão para esta ação, ou limite do plano atingido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Conversa não encontrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"429":{"description":"Mais de 20 requisições por minuto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}}},"/api/automations/hook/{hookId}":{"post":{"summary":"Disparar o webhook de um fluxo","description":"Executa as ações do fluxo com o corpo recebido. As chaves do corpo preenchem as variáveis `{{chave}}` do prompt de cada ação. Se o fluxo tiver segredo de assinatura, envie `x-webhook-signature` = `sha256=` + HMAC-SHA256 hexadecimal de `<timestamp>.<corpo>` e `x-webhook-timestamp` em segundos Unix, com até 5 minutos de diferença.","tags":["Automações"],"security":[],"parameters":[{"name":"hookId","in":"path","required":true,"schema":{"type":"string"},"description":"ID do webhook, gerado na tela do fluxo."},{"name":"x-webhook-signature","in":"header","required":false,"schema":{"type":"string"},"description":"Obrigatório se o fluxo tiver segredo de assinatura."},{"name":"x-webhook-timestamp","in":"header","required":false,"schema":{"type":"string"},"description":"Obrigatório se o fluxo tiver segredo de assinatura."}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","description":"Objeto JSON livre, até 100 KB."}}}},"responses":{"200":{"description":"Fluxo executado. Falha de uma ação aparece no texto da resposta dela.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"flow_id":{"type":"string"},"results":{"type":"array","items":{"type":"object","properties":{"action_id":{"type":"string"},"agent":{"type":"string"},"response":{"type":"string"}}}}}}}}},"400":{"description":"Corpo não é um objeto JSON, ou o fluxo não tem ações.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"401":{"description":"Assinatura inválida ou fora do prazo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"403":{"description":"Fluxo pausado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"404":{"description":"Webhook não encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"413":{"description":"Corpo maior que 100 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}},"500":{"description":"Erro interno.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erro"}}}}}}}},"tags":[{"name":"Agentes","description":"Criar, listar, alterar e desativar agentes. Aceita chave de API."},{"name":"Chat","description":"Conversar com um agente. Aceita chave de API."},{"name":"Conhecimento","description":"Alimentar a base de conhecimento de um agente. Pede token de sessão."},{"name":"Caixa de entrada","description":"Módulo Atendimento: responder e encerrar conversas dos canais. Pede token de sessão."},{"name":"Automações","description":"Disparar fluxos por webhook."}]}