API REST
Base URL
http://localhost:8080/apiEm produção, substitua pelo domínio da sua instância.
Documentação interativa (Swagger)
Acesse a documentação OpenAPI 3.1 gerada automaticamente:
http://localhost:8080/swagger-ui.htmlO documento por trás dela é servido em /v3/api-docs, pela própria aplicação — é essa a fonte de verdade da API, e é ela que o nginx expõe em produção.
Arquivo estático openapi.json
Se você precisar do documento como arquivo (para importar no Postman, versionar um contrato, gerar cliente), peça o profile openapi:
cd backend
mvn verify -PopenapiO resultado sai em backend/core/target/openapi.json.
Por que é um profile, e não parte do mvn verify
O gerador lê /v3/api-docs de uma aplicação rodando. O profile sobe a aplicação antes e a derruba depois, então precisa de um banco alcançável — as mesmas variáveis do roteiro de execução local.
Fora do profile, mvn verify não gera o arquivo e não tenta subir nada, para que o build continue verde numa máquina que só quer rodar os testes.
Autenticação
JWT (Usuário)
POST /auth/login
Content-Type: application/json
{
"email": "usuario@empresa.com",
"password": "suaSenha"
}Resposta:
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"user": { "id": "...", "email": "...", "firstName": "..." },
"roles": ["USER"]
}Use o accessToken no header de todas as requisições:
Authorization: Bearer eyJ...Refresh de token
Quando o accessToken expirar (erro 401), use o refreshToken para obter um novo par:
POST /auth/refresh
Content-Type: application/json
{
"refreshToken": "eyJ..."
}API Key (Sistema externo)
X-API-Key: sua-api-keyCada API Key tem um limite de requisições por minuto configurável (padrão 100, 0 = ilimitado), definido na criação da chave ou depois em Integração → API Keys. Ao ultrapassar o limite a API responde 429 Too Many Requests:
{ "error": "Too Many Requests", "message": "Rate limit exceeded for this API Key." }Escopos
Existem exatamente dois escopos, definidos em ia.flow.model.ApiKeyScope:
| Escopo | Autoridade | Métodos que abre |
|---|---|---|
read | SCOPE_read | GET, HEAD, OPTIONS |
write | SCOPE_write | POST, PUT, PATCH, DELETE, e qualquer outro |
A regra é uma só e mora num lugar: ApiKeyScope.requiredFor(httpMethod) decide o escopo que a requisição exige, e ApiKeyAuthFilter consulta essa decisão antes de deixar a cadeia seguir. Nenhuma rota nomeia escopo e nenhum controller repete a lista — um endpoint novo sob /api/a/** já nasce coberto.
Os dois escopos não se implicam: uma chave só de write é recusada num GET. A chave que precisa dos dois lados carrega os dois.
Uma requisição sem o escopo necessário é interrompida no filtro — nunca chega ao controller — e responde 403 Forbidden:
{
"error": "Forbidden",
"message": "Esta chave de API tem os escopos [read] e o método POST exige o escopo 'write'. Marque esse escopo na chave em Integração → API Keys, ou use uma chave que já o tenha."
}A leitura da coluna scopes falha fechada em toda ambiguidade: escopo desconhecido é ignorado e nunca promove a chave; lista vazia, null ou JSON malformado não concedem nada, e a chave é recusada em todo método. Antes de 2026-09-03 a lista vazia significava "sem restrição" — hoje significa "sem alcance".
Na criação (POST /api/a/api-keys) a lista de escopos é obrigatória e não pode ser vazia, e cada entrada precisa ser um escopo conhecido: um valor inventado é recusado com 400 e a mensagem lista os que existem. O PATCH aceita scopes opcionalmente — ausente, a chave mantém os que tem; presente, substitui.
A migração V0103__api_key_scopes.sql deu ["read","write"] a toda chave que já existia, inclusive as vazias e as com escopo inventado, pela razão registrada na própria migração (issue #57): o que já roda em produção mantém o alcance que tinha, e o que é criado a partir daqui nasce mínimo.
Servidor MCP (/api/a/mcp)
O Flow.IA expõe um servidor MCP (Model Context Protocol) para que um agente de IA externo — Claude Code, um script de provisionamento, um assistente de IDE — configure o processo no tenant: a definição em si, formulários, bases de conhecimento, coleções de CMS, agentes de IA e a documentação do processo.
Autenticação é HTTP Basic, com um usuário da plataforma:
{
"mcpServers": {
"flowi": {
"url": "https://<host>/api/a/mcp",
"headers": {
"Authorization": "Basic <base64 de email:senha>",
"X-Tenant-ID": "<slug do tenant>"
}
}
}
}Esse formato vale para os clientes que aceitam cabeçalho fixo: VS Code e Claude Code (CLI). Claude Desktop, claude.ai e ChatGPT adicionam servidor remoto por URL e negociam OAuth — não têm campo para Basic nem para cabeçalho próprio, então não conseguem falar com este endpoint diretamente. O 401 do endpoint responde WWW-Authenticate: Basic realm="flowi-mcp", que é como um cliente descobre o esquema; OAuth 2.1 ainda não está implementado.
Basic em vez de JWT porque a configuração do cliente MCP é estática e o token expira em 1 hora. Tentativas malsucedidas contam no mesmo limitador do endpoint de login.
O X-Tenant-ID diz qual tenant o cliente quer; quem decide se ele pode é a verificação de associação, feita na autenticação. Um slug em que o usuário não é membro não autentica — o cabeçalho nomeia o candidato, nunca seleciona o tenant. Sem o cabeçalho, o cliente opera sem tenant, que é o caso de um usuário SUPER_ADMIN.
Use um usuário dedicado
Crie um usuário só para isso (ex.: mcp-bot@...) e dê ADMIN apenas nos tenants que ele deve alcançar — a associação do usuário ao tenant é o que concede acesso. Não use a conta de uma pessoa: clientes MCP guardam credenciais em texto plano, e uma senha pessoal vazada dá login na interface, troca de senha e acesso a todos os tenants daquela pessoa. Um usuário dedicado se desativa sem afetar ninguém.
Ferramentas disponíveis:
| Ferramenta | O que faz |
|---|---|
flowi_form_list | Lista os formulários de um processo |
flowi_form_get | Retorna um formulário com o schema completo |
flowi_form_field_types | Lista os tipos de campo aceitos e como montar o schema |
flowi_form_create | Cria um formulário (não publica) |
flowi_form_update | Substitui o schema, criando nova versão (não publica) |
flowi_form_versions | Histórico de versões, com autor — é o caminho de desfazer |
flowi_form_publish | Publica uma versão, que é o que a faz chegar aos usuários |
flowi_process_list | Lista as definições do tenant, com a versão publicada |
flowi_process_versions | Histórico de versões de uma definição |
flowi_process_get_xml | Retorna o XML BPMN/CMMN de uma versão |
flowi_process_validate | Confere um BPMN sem implantar: órfãos, becos sem saída, gateway sem fluxo padrão, diagrama ausente |
flowi_process_deploy | Deposita uma nova versão da definição (não publica) |
flowi_ai_model_list | Catálogo de modelos de IA, com o id que flowi_agent_create exige |
flowi_agent_list, flowi_agent_create | Agentes de IA do processo |
flowi_agent_attach_to_step | Vincula um agente a uma etapa, com as tools daquela etapa |
flowi_agent_suggest_knowledge | Indica, entre as bases existentes, as relevantes para uma etapa |
flowi_kb_list, flowi_kb_create, flowi_kb_add_text, flowi_kb_link_process | Bases de conhecimento (RAG) |
flowi_cms_field_types | Tipos de campo e níveis de classificação de uma coleção |
flowi_cms_collection_list, flowi_cms_collection_create, flowi_cms_records_load, flowi_cms_records_query | Coleções de CMS |
flowi_process_doc_get | Lê a descrição do processo |
flowi_process_doc_propose | Propõe uma descrição nova — devolve o texto, não grava |
flowi_run_startable_list | Processos que dá para iniciar, cada um com sua política de MCP |
flowi_run_start | Inicia uma instância (só se a política permitir) |
flowi_run_my_tasks | A caixa de tarefas do chamador, como ele a vê na tela |
flowi_run_task_get | A tarefa e o schema do formulário dela, numa resposta só |
flowi_run_task_claim | Assume uma tarefa oferecida a um papel do chamador |
flowi_run_task_preview | Mostra o que a conclusão gravaria e para onde a instância iria; não grava nada |
flowi_run_task_complete | Conclui uma tarefa, exigindo o token do preview |
flowi_run_attach_text | Anexa um documento de texto a uma instância |
flowi_run_instance_get | Estado de uma instância |
flowi_run_why_failed | Falhas de agente de IA de uma instância, por business key |
Todas as ferramentas exigem papel de administrador do tenant (ou SUPER_ADMIN). Um usuário comum autentica e não alcança ferramenta nenhuma — a verificação está na classe de cada grupo de ferramentas, não no controller, porque o caminho MCP não passa por controller nenhum.
Cada ferramenta se anuncia ao cliente como leitura ou escrita (readOnlyHint, destructiveHint), para que uma listagem não peça a mesma aprovação que uma publicação. Escrita passa pela mesma validação de bean que a rota REST equivalente: um slug com espaço é recusado pela ferramenta como seria pela tela.
Há limites por chamada: 5000 registros em flowi_cms_records_load e 2 milhões de caracteres em flowi_kb_add_text. Divida cargas maiores.
Não há ferramenta de exclusão, nem de fonte de dados. Excluir não tem desfazer, e fonte de dados guarda URL e credencial de saída. Publicar é sempre uma chamada explícita — update_form nunca publica sozinho, e tarefas já abertas mantêm a versão que travaram na criação.
Depositar uma versão não é publicá-la
flowi_process_deploy cria uma versão nova e não existe ferramenta que publique uma definição. Enquanto houver uma versão publicada, a versão depositada não inicia instância nenhuma — ela fica lá para ser conferida e publicada por uma pessoa. A exceção é uma definição que nunca foi publicada: aí o início de instância cai na última versão, e a resposta da ferramenta avisa disso.
Antes de implantar um BPMN escrito por um modelo, chame flowi_process_validate: implantar cria uma versão e versão não se retira. A validação também avisa quando o XML veio sem a seção de diagrama — o processo executa, mas abre em branco no modelador.
Executar o processo é fechado por padrão
As ferramentas flowi_run_* iniciam instâncias e concluem tarefas. Concluir faz o processo andar e nenhuma ferramenta desfaz, então cada processo decide por si, no campo Assistentes de IA (MCP) da aba Configuração:
| Valor | Iniciar | Concluir tarefa |
|---|---|---|
| Nada (padrão) | recusado | recusado |
| Iniciar e ler | permitido | recusado |
| Iniciar e concluir | permitido | permitido, sob as regras abaixo |
O padrão é fechado, e nenhuma ferramenta muda isso — quem muda é a pessoa dona do processo, na tela.
Concluir exige três coisas ao mesmo tempo: a política do processo em Iniciar e concluir, a tarefa já assumida pelo próprio chamador, e um confirmationToken vindo de flowi_run_task_preview com exatamente aquelas variáveis. O token vale dez minutos e deixa de valer se qualquer campo mudar depois do preview — é assim que a pessoa confirma o payload final, e não um rascunho anterior dele. Nenhuma ferramenta assume tarefa a caminho de concluir, e conclui-se uma tarefa por chamada. A conclusão registra no histórico da tarefa que veio por MCP, para que uma leitura seis meses depois distinga o que a pessoa digitou do que um assistente enviou em nome dela.
O preview lê o diagrama e diz para quais atividades a tarefa pode ir, com a condição de cada caminho quando existe. Ele não avalia as condições — isso seria executar o processo —, então um caminho condicional aparece como condicional em vez de resolvido.
Para desligar o servidor, defina MCP_SERVER_ENABLED=false. Atrás de proxy, o endpoint precisa de proxy_buffering off e leitura longa: o canal de escuta é text/event-stream, e o timeout de 60s das demais rotas de API cortaria a sessão a cada minuto.
O tenant vem do token, não de um header
O tenant em que a sessão opera é um claim assinado dentro do access token. Não há header de tenant a enviar: X-Tenant-ID nunca seleciona o tenant. Enviá-lo com o mesmo slug que o token já carrega é aceito e ignorado; enviá-lo com outro slug, ou sem tenant no token, responde 400 com a mensagem dizendo para trocar de contexto em /api/auth/select-tenant. A única credencial que não é token — o HTTP Basic do endpoint MCP — nomeia seu tenant nesse header, e lá a verificação de associação acontece na autenticação: um slug do qual o usuário não é membro não autentica.
Depois do login, escolha o tenant:
POST /api/auth/select-tenant
Content-Type: application/json
{ "tenantSlug": "slug-do-tenant" }A resposta tem o mesmo formato do login, com um accessToken novo carregando o tenant e apenas os papéis daquele tenant. Trocar de tenant é repetir esta chamada e substituir o token guardado — não existe troca em memória.
Selecionar um tenant do qual o usuário não é membro responde 403.
Por que o tenant vive no token
Um header é escolhido pelo cliente, e conferi-lo contra a lista de tenants ativos não impede que um usuário associado a um tenant peça dados de outro trocando o valor. Com o claim assinado, o tenant deixa de ser palpite do cliente: alterá-lo invalida o token inteiro.
Principais endpoints
Os endpoints seguem o padrão de prefixo abaixo. Todos os caminhos são relativos à base /api.
| Prefixo | Escopo | Auth |
|---|---|---|
/auth | Autenticação / sessão | Sem auth em /login, /refresh, /password/forgot, /password/reset e /sso/**; o resto exige token |
/admin/* | Operações de plataforma | JWT (SUPER_ADMIN apenas) |
/a/* | Operações de tenant | JWT com tenant selecionado |
Autenticação
| Método | Endpoint | Descrição |
|---|---|---|
| POST | /auth/login | Login com email/senha |
| POST | /auth/refresh | Renovar token |
| POST | /auth/select-tenant | Trocar o tenant da sessão (devolve um token novo) |
| GET | /auth/me | Dados do usuário logado |
| GET | /auth/me/tenants | Tenants do usuário |
| GET | /auth/me/session | Dados da sessão corrente |
| POST | /auth/password/forgot | Pedir link de redefinição — responde 202 sempre, cadastrado ou não, e tem limite de tentativas por IP |
| POST | /auth/password/reset | Concluir a redefinição com o token do e-mail |
| POST | /auth/logout | Invalidar sessão |
Resposta do login:
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"user": { "id": "...", "email": "...", "firstName": "..." },
"roles": ["USER"],
"tenants": [
{ "id": "uuid", "slug": "empresa-abc", "name": "Empresa ABC" }
]
}O campo tenants lista os tenants aos quais o usuário pertence. Use o slug em POST /api/auth/select-tenant para obter o token daquele tenant.
O que POST /auth/logout encerra. Os dois tokens de um mesmo login pertencem à mesma sessão e carregam o mesmo identificador (sid). Sair apaga a sessão inteira — o access token e o refresh token —, então o refresh token daquele login deixa de renovar. Outras sessões da mesma pessoa, em outro aparelho ou outro navegador, continuam ativas: sair de uma aba não é sair de todos os aparelhos.
O access token em si continua válido até expirar (uma hora): ele não é conferido no banco a cada requisição, e essa é uma decisão de custo registrada em specs/core/tenant-in-token.md. O que o logout fecha é a janela: sem o refresh token daquela sessão, ela não é reaberta.
Processos e instâncias
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /a/definitions | Listar definições do tenant — resposta é a lista inteira, sem paginação |
| GET | /a/definitions/{key} | Detalhe + startFormKey — do StartEvent no BPMN, do flowable:formKey do casePlanModel no CMMN |
| GET | /a/definitions/{key}/variables | Variáveis de configuração do processo, não de instância — exige Admin do tenant. GET /a/definitions/{key}/variables/public devolve as não secretas a qualquer autenticado |
| GET | /a/definitions/{key}/monitor | Carga parada por etapa, de uma versão do processo — ?version= |
| GET | /a/definitions/{key}/monitor/{activityId} | O que está parado naquela etapa — ?version= |
| PUT | /a/definitions/{key}/suspend | Suspender a definição — nenhuma instância nova começa. Só BPMN; um caso responde 400, porque o motor de CMMN não suspende definição |
| PUT | /a/definitions/{key}/activate | Reativar a definição suspensa — Só BPMN, pelo mesmo motivo do suspend |
| POST | /a/instances | Iniciar instância (BPMN ou CMMN) |
| GET | /a/instances | Listar instâncias ativas, paginado — ?page=&size=&type= |
| GET | /a/instances/search | Buscar instâncias por variável de processo, paginado — ?varName=&varValue=&page=&size= |
| GET | /a/instances/{id} | Detalhe + variáveis |
| DELETE | /a/instances/{id} | Cancelar instância |
| PATCH | /a/instances/{id}/suspend | Suspender instância — só BPMN; um caso responde 400 |
| PATCH | /a/instances/{id}/activate | Reativar instância — só BPMN; um caso responde 400 |
| GET | /a/instances/{id}/diagram | SVG/XML do diagrama com atividades marcadas |
| GET | /a/instances/failed-jobs | Jobs com falha do tenant |
| POST | /a/instances/failed-jobs/{jobId}/retry | Retry de job |
| POST | /a/instances/failed-jobs/{jobId}/deadletter | Move o job para a fila de dead-letter, parando as tentativas |
| GET | /a/ai-task-failures | Etapas de agente de IA que falharam, paginado (size 20 por padrão) |
Papéis desta tabela, já que a coluna não os traz: iniciar, listar, detalhar e ver o diagrama de uma instância bastam estar autenticado. Cancelar, suspender e reativar uma instância, e as rotas de failed-jobs e de monitor, exigem Gerente do tenant ou superior. Suspender e reativar a definição exigem Admin do tenant. Abaixo disso a resposta é 403.
Quando um agente de IA falha durante POST /a/instances, a instância não chega a existir: o processo roda de forma síncrona dentro da transação da requisição, então o rollback leva junto a instância, as tarefas e o vínculo do anexo. GET /a/ai-task-failures é onde a tentativa fica registrada — processo, business key, atividade, tipo da falha e motivo, da mais recente para a mais antiga. Aceita ?businessKey= para filtrar por documento.
Numa etapa de IA dentro de um caso a leitura muda: o caso existe e continua aberto, só a etapa falhou, e ela pode ser executada de novo. O campo engine diz qual dos dois é o caso — BPMN ou CMMN — e nas linhas CMMN o campo processKey carrega a chave do caso.
attemptCount conta as retentativas. É uma linha por etapa, não uma por tentativa: quando o motor repete a mesma etapa, a linha existente é atualizada e o contador sobe. Nove tentativas da mesma etapa aparecem como uma linha com attemptCount: 9, o que é uma informação; nove linhas iguais seriam ruído.
Quem enviou o documento recebe uma notificação AI_TASK_FAILED na hora, pelo mesmo canal das demais notificações — uma vez, na primeira falha. As retentativas não notificam de novo: repetir o mesmo aviso nove vezes não acrescenta nada e esconde o primeiro. Início por API key não tem usuário por trás, então a linha é gravada e nenhuma notificação é enviada.
Não há retry nem dead-letter pela API: o registro é um fato, não um item de trabalho. Reprocessar significa enviar o documento de novo.
Acompanhamento por etapa
GET /a/definitions/{key}/monitor devolve o diagrama de uma versão e quanto trabalho está parado em cada etapa dela. Exige papel de Gerente ou superior no tenant.
{
"definitionKey": "fiscal-nfe",
"definitionName": "Auditoria fiscal",
"version": 3,
"bpmnXml": "<definitions …>",
"runningInstances": 12,
"runningInstancesOtherVersions": 7,
"versions": [ { "version": 3, "runningInstances": 12 }, { "version": 2, "runningInstances": 7 } ],
"activities": [
{ "activityId": "parecer", "activityName": "Parecer final", "activityType": "UserTask",
"count": 11, "byReason": { "USER_TASK": 11 } }
]
}version é opcional. Sem ele, a resposta traz a versão com mais instâncias correndo (empate: a maior) e, se nenhuma instância existir, a última publicada. Um diagrama pertence a uma versão e as instâncias não: contar instâncias de todas as versões sobre um só desenho produz número errado, então runningInstancesOtherVersions diz quanto ficou de fora em vez de omitir.
Etapa sem nada parado não aparece em activities — o diagrama já desenha todas.
byReason classifica o que está ali:
| Motivo | Significado |
|---|---|
USER_TASK | tarefa aguardando uma pessoa |
JOB_FAILED | tentativas esgotadas, na fila de dead-letter |
JOB_RETRYING | falhou e ainda tem tentativa |
TIMER | aguardando uma data agendada |
EXECUTING | execução na etapa, sem job e sem tarefa |
GET /a/definitions/{key}/monitor/{activityId} lista os itens daquela etapa — instanceId, businessKey, startTime, reason e, conforme o motivo, taskId, assignee, dueDate ou errorMessage. Passe o mesmo version que o resumo devolveu, senão o detalhe pode descrever um diagrama diferente do que está na tela.
Histórico de instâncias
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /a/history/instances | Instâncias concluídas, paginado — ?tenantId=&type=&page=&size= |
| GET | /a/history/instances/{id} | Detalhe da instância concluída |
| GET | /a/history/instances/{id}/activities | Atividades da instância |
A listagem e a busca por variável respondem com o mesmo envelope de página de /a/instances (content, number, size, totalElements, totalPages). size vale 20 por padrão e é limitado a 100 — pedir mais devolve 100, não um erro. Duas tabelas que só crescem estão por trás delas: ACT_HI_PROCINST e ACT_CMMN_HI_CASE_INST. Sem página, um tenant com dois anos de operação recebia dezenas de milhares de linhas numa resposta só.
Tarefas
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /a/tasks | Listar tarefas do usuário (?page=&size=&mine=) |
| GET | /a/tasks/completed | Listar tarefas já concluídas (histórico) (?page=&size=) |
| GET | /a/tasks/{id} | Detalhe + formulário |
| POST | /a/tasks/{id}/claim | Assumir tarefa |
| POST | /a/tasks/{id}/complete | Concluir tarefa |
| POST | /a/tasks/{id}/unclaim | Devolver tarefa ao grupo |
| POST | /a/tasks/{id}/delegate | Delegar |
| GET | /a/tasks/{id}/variables | Ler as variáveis visíveis da tarefa |
| POST | /a/tasks/{id}/variables | Gravar variáveis sem concluir a tarefa |
| POST | /a/tasks/{id}/ai-suggest | Sugestão de preenchimento para os campos ainda vazios. O corpo {"draft": {"campo": "valor"}} leva o formulário como está na tela — sem ele o servidor só enxerga as variáveis já salvas. Corpo ausente ou draft vazio é válido. Nada é gravado: as sugestões voltam para o formulário, e cada uma traz conflictsWithCurrent (true quando o campo já tem outro valor, e aí o cliente nunca aplica sozinho) |
| GET | /a/tasks/{id}/comments | Listar comentários |
| POST | /a/tasks/{id}/comments | Adicionar comentário |
As respostas de listagem e detalhe de tarefas incluem formVersionId para buscar a versão exata do formulário via GET /a/definitions/{processKey}/forms/{key}/versions/by-id/{versionId}.
POST /a/tasks/{id}/complete valida o formulário no servidor
Se a tarefa tem formKey e o formulário existe na plataforma, os campos obrigatórios são conferidos antes de a tarefa ser concluída. Faltando algum, a resposta é 400 com a mensagem Campos obrigatórios não preenchidos: <campos> e nada é gravado — nem as variáveis, nem o avanço do processo. A conferência usa a mesma versão do formulário travada na criação da tarefa (a de formVersionId), e respeita as regras condicionais: um campo escondido por regra não é cobrado.
Um cliente de API que só enviava o que a tela enviava não muda. Um que enviava menos passa a receber 400 — busque o schema por formVersionId antes de montar o corpo.
Não é validado: POST /a/instances (variáveis do formulário de início) e as validações de formato, tamanho e faixa de cada campo, que continuam só no navegador.
GET /a/tasks e GET /a/tasks/completed devolvem uma janela de página, não a fila inteira. Nas duas, page começa em zero e size vale 100 por padrão, com teto de 200 — pedir mais que isso devolve 200. A resposta continua sendo um array simples, com os mesmos campos; só o número de linhas por chamada é que tem limite. Quem precisa de mais que a primeira página pede ?page=1, e assim por diante.
As duas listas também seguem a mesma regra de visibilidade: Gerente, Administrador e Super Admin veem as tarefas de todo o tenant, abertas e concluídas; os demais veem as suas, aquelas em que são candidatos e as oferecidas a um grupo dos seus papéis de processo.
GET /a/tasks?mine=true estreita a lista às tarefas atribuídas ao chamador, ignorando grupo e papel de processo — inclusive para quem vê o tenant inteiro. O padrão é mine=false, que mantém a lista completa que a visibilidade do chamador permite. Não existe mine em /a/tasks/completed.
Formulários
Um formulário sempre pertence a um processo, e o caminho reflete isso: a chave do processo vem antes da chave do formulário. Não existe rota /a/forms/{key} — chamá-la devolve 404.
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /a/definitions/{processKey}/forms | Listar os formulários do processo |
| POST | /a/definitions/{processKey}/forms | Criar formulário |
| GET | /a/definitions/{processKey}/forms/{key} | Detalhe do formulário |
| PUT | /a/definitions/{processKey}/forms/{key} | Salvar formulário (cria nova versão) |
| DELETE | /a/definitions/{processKey}/forms/{key} | Remover formulário |
| GET | /a/definitions/{processKey}/forms/{key}/versions | Listar versões (traz createdBy, o UUID de quem salvou) |
| GET | /a/definitions/{processKey}/forms/{key}/versions/{num} | Versão por número |
| GET | /a/definitions/{processKey}/forms/{key}/versions/by-id/{versionId} | Versão por UUID |
| POST | /a/definitions/{processKey}/forms/{key}/publish | Publicar uma versão |
As fontes de dados são a exceção: elas não pertencem a um processo e ficam direto sob /a/forms.
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /a/forms/datasources | Listar fontes de dados |
| POST | /a/forms/datasources | Criar fonte de dados |
| GET | /a/forms/datasources/{key} | Detalhe da fonte de dados |
| PUT | /a/forms/datasources/{key} | Atualizar fonte de dados |
| DELETE | /a/forms/datasources/{key} | Remover fonte de dados |
| POST | /a/forms/datasources/{key}/execute | Executar fonte de dados |
Validação do schema em POST e PUT
O schema enviado precisa ser JSON válido e, quando trouxer um array fields, todo campo tem de declarar um type reconhecido. Um tipo inexistente é rejeitado com 400 e a mensagem nomeia o culpado e lista os tipos aceitos:
{ "message": "Tipo de campo desconhecido: 'markdown'. Tipos aceitos: text, textarea, ..." }Sem essa checagem, um type inventado seria gravado e só quebraria na hora de renderizar. Quem monta o formulário pela tela nunca esbarra nisso, porque a paleta só oferece tipos reais; quem escreve o JSON direto pela API, sim.
Schemas no formato Form.io (com components em vez de fields) são aceitos sem essa validação.
Decision Tables
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /a/decisions | Listar decisions do tenant |
| POST | /a/decisions | Deploy de arquivo .dmn |
| POST | /a/decisions/from-template/{key} | Clonar de template global |
| GET | /a/decisions/{key} | Detalhes |
| PUT | /a/decisions/{key} | Atualizar regras (nova versão) |
| POST | /a/decisions/{key}/evaluate | Testar com inputs |
| DELETE | /a/decisions/{key} | Remover |
Todas as rotas acima exigem Admin do tenant ou Super Admin, com uma exceção: POST /a/decisions/{key}/evaluate está aberta a qualquer papel autenticado, porque é o que um formulário chama para testar a regra.
CMS
Coleções de conteúdo estruturado e seus registros. Ler basta estar autenticado no tenant, e a resposta já vem cortada pelo nível de sigilo da coleção: o que está acima da sua liberação não aparece. Escrever exige Gerente do tenant ou superior; uma chave de API alcança a escrita com o escopo write.
| Método | Endpoint | Permissão | Descrição |
|---|---|---|---|
| GET | /a/cms/collections | Autenticado | Listar as coleções visíveis para o chamador |
| POST | /a/cms/collections | Gerente | Criar coleção |
| GET | /a/cms/collections/{id} | Autenticado | Detalhe da coleção |
| PUT | /a/cms/collections/{id} | Gerente | Atualizar coleção |
| DELETE | /a/cms/collections/{id} | Gerente | Remover coleção |
| GET | /a/cms/collections/{id}/records | Autenticado | Listar registros, paginado (size 20 por padrão) |
| POST | /a/cms/collections/{id}/records | Gerente | Criar um registro (201) |
| POST | /a/cms/collections/{id}/records/bulk | Gerente | Carga em lote |
| PUT | /a/cms/collections/{id}/records/{recordId} | Gerente | Atualizar registro |
| DELETE | /a/cms/collections/{id}/records/{recordId} | Gerente | Remover registro |
| DELETE | /a/cms/collections/{id}/records | Gerente | Esvaziar a coleção |
A carga em lote recebe { "records": [...], "keyField": "codigo" } e responde { "inserted": n, "updated": n, "total": n }. São no máximo 20 000 registros por requisição — uma lista maior é recusada com 400 —, e keyField tem no máximo 100 caracteres. Com keyField, a carga é idempotente: um registro cujo valor daquele campo já existe é atualizado em vez de duplicado. Sem keyField, tudo é inserido, então uma recarga limpa passa antes por DELETE /a/cms/collections/{id}/records.
A exclusão de registro e de coleção é definitiva: não há lixeira nem desfazer.
Bases de conhecimento (RAG)
Os documentos que alimentam a resposta do agente. Todas as rotas abaixo exigem Gerente do tenant ou superior — leitura inclusive, porque a listagem revela o que a IA consulta. Quem chama sem esse papel recebe 403. Criar ou alterar uma base marcada como global continua sendo exclusivo do Super Admin, e a recusa vem do próprio serviço.
| Método | Endpoint | Permissão | Descrição |
|---|---|---|---|
| GET | /a/knowledge-bases | Gerente | Listar as bases do tenant e as globais |
| POST | /a/knowledge-bases | Gerente | Criar base (201); isGlobal: true exige Super Admin |
| GET | /a/knowledge-bases/{id} | Gerente | Detalhe da base, com a contagem de documentos |
| DELETE | /a/knowledge-bases/{id} | Gerente | Remover base — recusa enquanto houver processo vinculado |
| GET | /a/knowledge-bases/{id}/documents | Gerente | Listar documentos, paginado (size 20 por padrão) |
| POST | /a/knowledge-bases/{id}/documents | Gerente | Subir PDF, TXT ou MD (202 — a indexação é assíncrona) |
| DELETE | /a/knowledge-bases/{id}/documents/{docId} | Gerente | Remover documento e seus vetores |
| GET | /a/knowledge-bases/process/{definitionKey} | Gerente | Bases vinculadas a um processo |
| POST | /a/knowledge-bases/process/{definitionKey}/link/{id} | Gerente | Vincular base ao processo (201) |
| DELETE | /a/knowledge-bases/process/{definitionKey}/unlink/{id} | Gerente | Desvincular |
Administração
/admin/* inteiro é SUPER_ADMIN
A regra é do filtro de segurança, não de cada controller: todo caminho sob /api/admin/** exige o papel de plataforma SUPER_ADMIN. Um Admin de tenant recebe 403 em qualquer linha da tabela abaixo, inclusive nas de membros — não existe bloco de /admin/* que ele alcance. O que um Admin de tenant administra vive sob /a/*.
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /admin/users | Listar usuários, paginado (?search=&page=&size=&sort=) |
| POST | /admin/users | Criar usuário |
| PUT | /admin/users/{id} | Atualizar usuário |
| DELETE | /admin/users/{id} | Desativar usuário |
| PUT | /admin/users/{id}/password | Trocar a senha de um usuário |
| GET | /admin/tenants | Listar tenants, paginado |
| POST | /admin/tenants | Criar tenant |
| PUT | /admin/tenants/{id} | Atualizar tenant |
| PATCH | /admin/tenants/{id}/suspend | Suspender tenant |
| PATCH | /admin/tenants/{id}/reactivate | Reativar tenant |
| GET | /admin/tenants/{id}/members | Listar membros do tenant |
| POST | /admin/tenants/{id}/members | Adicionar membro |
| DELETE | /admin/tenants/{id}/members/{membershipId} | Remover membro |
| PATCH | /admin/tenants/{id}/members/{membershipId}/role | Trocar o papel de um membro |
| GET | /admin/dmn-templates | Listar templates DMN |
| POST | /admin/dmn-templates | Criar template DMN |
| PUT | /admin/dmn-templates/{key} | Atualizar template DMN |
| DELETE | /admin/dmn-templates/{key} | Remover template DMN |
Para ler os membros do tenant em que a sessão está, sem ser Super Admin, existe GET /a/members, que exige Admin do tenant.
Definições de processo (BPMN/CMMN)
Não existe /admin/definitions. Deploy, versionamento e publicação de definição vivem sob /a/definitions, com escopo de tenant vindo do token. Todas as rotas desta tabela exigem Admin do tenant ou Super Admin — listar (GET /a/definitions) e detalhar (GET /a/definitions/{key}) bastam estar autenticado.
| Método | Endpoint | Descrição |
|---|---|---|
| POST | /a/definitions/deploy | Deploy de arquivo BPMN/CMMN (multipart) |
| GET | /a/definitions/{key}/versions | Histórico de versões |
| POST | /a/definitions/{key}/versions/{definitionId}/publish | Publicar versão |
| GET | /a/definitions/{key}/versions/{definitionId}/xml | Exportar XML |
| PATCH | /a/definitions/{key}/toggle | Habilitar/desabilitar o processo no catálogo do tenant |
| PUT | /a/definitions/{key}/config | Business Key template e política de anexos |
Deploy não é publicação
POST /a/definitions/deploy cria uma versão nova, mas não muda a versão que executa. Se o tenant já publicou alguma versão, as instâncias continuam começando naquela — o deploy responde 200, o número da versão sobe, e nada do que você acabou de subir entra em execução.
Para ativar: GET /a/definitions/{key}/versions mostra qual linha está com published: true, e POST /a/definitions/{key}/versions/{definitionId}/publish move a publicação. O deploy também registra um WARN no log quando a versão implantada não é a publicada.
Paginação
Endpoints de listagem aceitam parâmetros de paginação:
GET /admin/users?page=0&size=20&sort=createdAt,descResposta paginada:
{
"content": [...],
"totalElements": 45,
"totalPages": 3,
"number": 0,
"size": 20
}number é o índice da página devolvida, contado a partir de zero — o mesmo valor que você mandou em page. O nome vem do formato de página do Spring Data, e todas as rotas paginadas desta API usam o mesmo formato, inclusive /a/instances.
size é limitado a 100 em /a/instances; pedir mais devolve 100 sem erro em vez de varrer o tenant inteiro.
Erros
Todo erro tratado pela aplicação segue o formato:
{
"status": 400,
"error": "Bad Request",
"message": "Descrição do problema",
"path": "/api/a/instances",
"timestamp": "2026-04-15T10:30:00"
}Quando o erro é de validação de campo, o mesmo corpo ganha fieldErrors, um array de { "field": "slug", "message": "Slug é obrigatório" }.
Duas famílias de resposta não passam por aí, porque são escritas antes do controller:
- o filtro de chave de API devolve apenas
erroremessagenos403de escopo e nos429de limite, semstatus,pathnemtimestamp; - a recusa de
X-Tenant-IDsai pelo tratador de erro do servidor de aplicação, no formato padrão dele.
Não trate a presença de status ou path como garantida ao ler um erro; leia sempre o código HTTP da resposta.
| Status | Significado |
|---|---|
| 400 | Dados inválidos na requisição |
| 401 | Não autenticado (token ausente ou expirado) |
| 403 | Sem permissão |
| 404 | Recurso não encontrado |
| 409 | Conflito (ex.: e-mail já cadastrado; ou uma funcionalidade de IA chamada numa instalação sem modelo ativo — a mensagem diz onde cadastrar) |
| 500 | Erro interno do servidor |