Skip to content

API REST

Base URL

http://localhost:8080/api

Em 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.html

O 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:

bash
cd backend
mvn verify -Popenapi

O 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)

http
POST /auth/login
Content-Type: application/json

{
  "email": "usuario@empresa.com",
  "password": "suaSenha"
}

Resposta:

json
{
  "accessToken": "eyJ...",
  "refreshToken": "eyJ...",
  "user": { "id": "...", "email": "...", "firstName": "..." },
  "roles": ["USER"]
}

Use o accessToken no header de todas as requisições:

http
Authorization: Bearer eyJ...

Refresh de token

Quando o accessToken expirar (erro 401), use o refreshToken para obter um novo par:

http
POST /auth/refresh
Content-Type: application/json

{
  "refreshToken": "eyJ..."
}

API Key (Sistema externo)

http
X-API-Key: sua-api-key

Cada 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:

json
{ "error": "Too Many Requests", "message": "Rate limit exceeded for this API Key." }

Escopos

Existem exatamente dois escopos, definidos em ia.flow.model.ApiKeyScope:

EscopoAutoridadeMétodos que abre
readSCOPE_readGET, HEAD, OPTIONS
writeSCOPE_writePOST, 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:

json
{
  "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:

json
{
  "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:

FerramentaO que faz
flowi_form_listLista os formulários de um processo
flowi_form_getRetorna um formulário com o schema completo
flowi_form_field_typesLista os tipos de campo aceitos e como montar o schema
flowi_form_createCria um formulário (não publica)
flowi_form_updateSubstitui o schema, criando nova versão (não publica)
flowi_form_versionsHistórico de versões, com autor — é o caminho de desfazer
flowi_form_publishPublica uma versão, que é o que a faz chegar aos usuários
flowi_process_listLista as definições do tenant, com a versão publicada
flowi_process_versionsHistórico de versões de uma definição
flowi_process_get_xmlRetorna o XML BPMN/CMMN de uma versão
flowi_process_validateConfere um BPMN sem implantar: órfãos, becos sem saída, gateway sem fluxo padrão, diagrama ausente
flowi_process_deployDeposita uma nova versão da definição (não publica)
flowi_ai_model_listCatálogo de modelos de IA, com o id que flowi_agent_create exige
flowi_agent_list, flowi_agent_createAgentes de IA do processo
flowi_agent_attach_to_stepVincula um agente a uma etapa, com as tools daquela etapa
flowi_agent_suggest_knowledgeIndica, entre as bases existentes, as relevantes para uma etapa
flowi_kb_list, flowi_kb_create, flowi_kb_add_text, flowi_kb_link_processBases de conhecimento (RAG)
flowi_cms_field_typesTipos 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_queryColeções de CMS
flowi_process_doc_getLê a descrição do processo
flowi_process_doc_proposePropõe uma descrição nova — devolve o texto, não grava
flowi_run_startable_listProcessos que dá para iniciar, cada um com sua política de MCP
flowi_run_startInicia uma instância (só se a política permitir)
flowi_run_my_tasksA caixa de tarefas do chamador, como ele a vê na tela
flowi_run_task_getA tarefa e o schema do formulário dela, numa resposta só
flowi_run_task_claimAssume uma tarefa oferecida a um papel do chamador
flowi_run_task_previewMostra o que a conclusão gravaria e para onde a instância iria; não grava nada
flowi_run_task_completeConclui uma tarefa, exigindo o token do preview
flowi_run_attach_textAnexa um documento de texto a uma instância
flowi_run_instance_getEstado de uma instância
flowi_run_why_failedFalhas 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:

ValorIniciarConcluir tarefa
Nada (padrão)recusadorecusado
Iniciar e lerpermitidorecusado
Iniciar e concluirpermitidopermitido, 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:

http
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.

PrefixoEscopoAuth
/authAutenticação / sessãoSem auth em /login, /refresh, /password/forgot, /password/reset e /sso/**; o resto exige token
/admin/*Operações de plataformaJWT (SUPER_ADMIN apenas)
/a/*Operações de tenantJWT com tenant selecionado

Autenticação

MétodoEndpointDescrição
POST/auth/loginLogin com email/senha
POST/auth/refreshRenovar token
POST/auth/select-tenantTrocar o tenant da sessão (devolve um token novo)
GET/auth/meDados do usuário logado
GET/auth/me/tenantsTenants do usuário
GET/auth/me/sessionDados da sessão corrente
POST/auth/password/forgotPedir link de redefinição — responde 202 sempre, cadastrado ou não, e tem limite de tentativas por IP
POST/auth/password/resetConcluir a redefinição com o token do e-mail
POST/auth/logoutInvalidar sessão

Resposta do login:

json
{
  "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étodoEndpointDescrição
GET/a/definitionsListar 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}/variablesVariá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}/monitorCarga 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}/suspendSuspender 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}/activateReativar a definição suspensa — Só BPMN, pelo mesmo motivo do suspend
POST/a/instancesIniciar instância (BPMN ou CMMN)
GET/a/instancesListar instâncias ativas, paginado?page=&size=&type=
GET/a/instances/searchBuscar 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}/suspendSuspender instância — só BPMN; um caso responde 400
PATCH/a/instances/{id}/activateReativar instância — só BPMN; um caso responde 400
GET/a/instances/{id}/diagramSVG/XML do diagrama com atividades marcadas
GET/a/instances/failed-jobsJobs com falha do tenant
POST/a/instances/failed-jobs/{jobId}/retryRetry de job
POST/a/instances/failed-jobs/{jobId}/deadletterMove o job para a fila de dead-letter, parando as tentativas
GET/a/ai-task-failuresEtapas 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.

json
{
  "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:

MotivoSignificado
USER_TASKtarefa aguardando uma pessoa
JOB_FAILEDtentativas esgotadas, na fila de dead-letter
JOB_RETRYINGfalhou e ainda tem tentativa
TIMERaguardando uma data agendada
EXECUTINGexecuçã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étodoEndpointDescrição
GET/a/history/instancesInstâncias concluídas, paginado?tenantId=&type=&page=&size=
GET/a/history/instances/{id}Detalhe da instância concluída
GET/a/history/instances/{id}/activitiesAtividades 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étodoEndpointDescrição
GET/a/tasksListar tarefas do usuário (?page=&size=&mine=)
GET/a/tasks/completedListar tarefas já concluídas (histórico) (?page=&size=)
GET/a/tasks/{id}Detalhe + formulário
POST/a/tasks/{id}/claimAssumir tarefa
POST/a/tasks/{id}/completeConcluir tarefa
POST/a/tasks/{id}/unclaimDevolver tarefa ao grupo
POST/a/tasks/{id}/delegateDelegar
GET/a/tasks/{id}/variablesLer as variáveis visíveis da tarefa
POST/a/tasks/{id}/variablesGravar variáveis sem concluir a tarefa
POST/a/tasks/{id}/ai-suggestSugestã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}/commentsListar comentários
POST/a/tasks/{id}/commentsAdicionar 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étodoEndpointDescrição
GET/a/definitions/{processKey}/formsListar os formulários do processo
POST/a/definitions/{processKey}/formsCriar 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}/versionsListar 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}/publishPublicar 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étodoEndpointDescrição
GET/a/forms/datasourcesListar fontes de dados
POST/a/forms/datasourcesCriar 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}/executeExecutar 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:

json
{ "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étodoEndpointDescrição
GET/a/decisionsListar decisions do tenant
POST/a/decisionsDeploy 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}/evaluateTestar 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étodoEndpointPermissãoDescrição
GET/a/cms/collectionsAutenticadoListar as coleções visíveis para o chamador
POST/a/cms/collectionsGerenteCriar coleção
GET/a/cms/collections/{id}AutenticadoDetalhe da coleção
PUT/a/cms/collections/{id}GerenteAtualizar coleção
DELETE/a/cms/collections/{id}GerenteRemover coleção
GET/a/cms/collections/{id}/recordsAutenticadoListar registros, paginado (size 20 por padrão)
POST/a/cms/collections/{id}/recordsGerenteCriar um registro (201)
POST/a/cms/collections/{id}/records/bulkGerenteCarga em lote
PUT/a/cms/collections/{id}/records/{recordId}GerenteAtualizar registro
DELETE/a/cms/collections/{id}/records/{recordId}GerenteRemover registro
DELETE/a/cms/collections/{id}/recordsGerenteEsvaziar 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étodoEndpointPermissãoDescrição
GET/a/knowledge-basesGerenteListar as bases do tenant e as globais
POST/a/knowledge-basesGerenteCriar base (201); isGlobal: true exige Super Admin
GET/a/knowledge-bases/{id}GerenteDetalhe da base, com a contagem de documentos
DELETE/a/knowledge-bases/{id}GerenteRemover base — recusa enquanto houver processo vinculado
GET/a/knowledge-bases/{id}/documentsGerenteListar documentos, paginado (size 20 por padrão)
POST/a/knowledge-bases/{id}/documentsGerenteSubir PDF, TXT ou MD (202 — a indexação é assíncrona)
DELETE/a/knowledge-bases/{id}/documents/{docId}GerenteRemover documento e seus vetores
GET/a/knowledge-bases/process/{definitionKey}GerenteBases vinculadas a um processo
POST/a/knowledge-bases/process/{definitionKey}/link/{id}GerenteVincular base ao processo (201)
DELETE/a/knowledge-bases/process/{definitionKey}/unlink/{id}GerenteDesvincular

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étodoEndpointDescrição
GET/admin/usersListar usuários, paginado (?search=&page=&size=&sort=)
POST/admin/usersCriar usuário
PUT/admin/users/{id}Atualizar usuário
DELETE/admin/users/{id}Desativar usuário
PUT/admin/users/{id}/passwordTrocar a senha de um usuário
GET/admin/tenantsListar tenants, paginado
POST/admin/tenantsCriar tenant
PUT/admin/tenants/{id}Atualizar tenant
PATCH/admin/tenants/{id}/suspendSuspender tenant
PATCH/admin/tenants/{id}/reactivateReativar tenant
GET/admin/tenants/{id}/membersListar membros do tenant
POST/admin/tenants/{id}/membersAdicionar membro
DELETE/admin/tenants/{id}/members/{membershipId}Remover membro
PATCH/admin/tenants/{id}/members/{membershipId}/roleTrocar o papel de um membro
GET/admin/dmn-templatesListar templates DMN
POST/admin/dmn-templatesCriar 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étodoEndpointDescrição
POST/a/definitions/deployDeploy de arquivo BPMN/CMMN (multipart)
GET/a/definitions/{key}/versionsHistórico de versões
POST/a/definitions/{key}/versions/{definitionId}/publishPublicar versão
GET/a/definitions/{key}/versions/{definitionId}/xmlExportar XML
PATCH/a/definitions/{key}/toggleHabilitar/desabilitar o processo no catálogo do tenant
PUT/a/definitions/{key}/configBusiness 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,desc

Resposta paginada:

json
{
  "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:

json
{
  "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 error e message nos 403 de escopo e nos 429 de limite, sem status, path nem timestamp;
  • a recusa de X-Tenant-ID sai 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.

StatusSignificado
400Dados inválidos na requisição
401Não autenticado (token ausente ou expirado)
403Sem permissão
404Recurso não encontrado
409Conflito (ex.: e-mail já cadastrado; ou uma funcionalidade de IA chamada numa instalação sem modelo ativo — a mensagem diz onde cadastrar)
500Erro interno do servidor

Flowi Agentic — Plataforma de Gestão de Processos com IA