Automações (WorkflowV2)
Uma automação do OutDo é uma definição versionada: um gatilho e uma cadeia
de passos, em duas colunas JSON. Esta página diz o que cabe nessas colunas, que
rotas e ferramentas as escrevem, e em que ordem. O vocabulário está gerado do
código e não se repete aqui: Passos e
Gatilhos, com os campos de cada config.
As duas colunas
trigger_config é o que dispara. graph_config é o que acontece a seguir.
{
"trigger_config": {
"type": "record.event",
"config": {}
},
"graph_config": {
"steps": [
{
"id": "stp_exemplo0001",
"key": "feature.write",
"config": {}
}
]
}
}
| Chave | Obrigatória | O que é |
|---|---|---|
trigger_config.type | sim | A key do gatilho, tal como aparece em Gatilhos. Vazia ou desconhecida é erro (trigger_missing, trigger_unknown_key). |
trigger_config.config | não | A config do gatilho. Cada gatilho valida a sua (trigger_config_invalid). |
graph_config.steps | sim na prática | A cadeia, por ordem de execução. Lista vazia passa com aviso (graph_empty). |
steps[].key | sim | A key do passo, de Passos. Desconhecida é erro (unknown_step_key). |
steps[].id | não | Identidade estável do passo, stp_ mais 12 alfanuméricos. Omitida, a app atribui uma ao abrir; repetida é erro (duplicate_step_id). É a chave de {{vars.steps.<id>.<saída>}}. |
steps[].config | depende do passo | A config do passo. |
steps[].name | não | Alias humano do cartão. Não substitui o id. |
Não há nós, arestas nem coordenadas. A ordem da lista é o fluxo, e quem
ramifica guarda os ramos dentro da config do passo de controlo, como listas
de passos com a mesma forma: then e else no ifElse, as rotas no switch e
no agent.route. Daí o caminho pontuado dos problemas, onde 0.then.0 é o
primeiro passo do ramo verdadeiro do primeiro passo da raiz.
Ids, nomes e referências
A API não resolve nomes de tabelas nem símbolos:
- Tabelas por uuid:
featureId,targetFeatureId, deGET /v1/features. - Colunas por nome técnico:
targetColumn,columnName,filters[].column,watchedColumns,orderBy.column. É onamedeGET /v1/features/{id}/columns. - Ligações por nome:
connectionName, que esta rota não cruza com as ligações da organização (um nome errado só falha em execução).
As referências simbólicas $f_… e $c_… são a gramática dos pacotes de
template (ver Formato do pacote) e nada em
/v1/workflows-v2 as resolve.
A gramática dos valores
Os campos de expressão aceitam tokens {{…}}, resolvidos em execução contra o
envelope que viaja entre passos:
| Token | O que devolve |
|---|---|
{{vars.x}} | Uma variável do envelope. |
{{vars.trigger.<coluna>}} | Uma coluna da linha que disparou (gatilhos record.event e disparo manual sobre uma linha). |
{{vars.steps.<id>.<saída>}} | Uma saída anunciada por um passo anterior. |
{{record.<coluna>}} | A coluna do primeiro registo do conjunto corrente. |
{{records.2.<coluna>}} | A coluna do registo no índice indicado. |
{{sys.now}}, {{sys.today}} | Data e hora, ou só a data, do relógio da execução. |
Um token sozinho preserva o tipo do valor; embebido em texto, interpola. Um
{{vars.x}} que nenhum passo anterior produza é erro (unknown_var), tal como
um id inexistente (unknown_step_ref) ou uma saída não anunciada
(unknown_step_output).
Ciclo de vida
Validar, criar como rascunho, publicar, e só então alguém ativa na aplicação. A ativação não tem rota nem ferramenta: é sempre uma decisão de uma pessoa.
| Passo | REST | MCP |
|---|---|---|
| Listar as automações de um workspace | GET /v1/workflows-v2?workspace_id=… | list_workflows_v2 |
| Ler a que está em vigor | GET /v1/workflows-v2/{workflow_id} | get_workflow_v2 |
| Validar sem gravar | POST /v1/workflows-v2/validate | validate_workflow_v2 |
| Criar como rascunho | POST /v1/workflows-v2 | create_workflow_v2 |
| Publicar o rascunho | POST /v1/workflows-v2/{workflow_id}/apply | sem equivalente |
| Acrescentar uma versão ativa | POST /v1/workflows-v2/{workflow_id}/versions | sem equivalente |
| Ativar | na aplicação | na aplicação |
- Cada item da listagem é uma versão, e o
workflow_idagrupa-as: é por ele que se endereça a automação.active=truefiltra as que disparam;falsenão filtra nada de útil, porque uma versão superada também é inativa. POST /v1/workflows-v2nasce rascunho, e um rascunho nunca dispara. Comis_draft: falsecria e publica de uma vez, e o resultado fica inativo.applypreserva o estado de ativação: uma pausada continua pausada, uma nova nasce inativa. Um 409 quer dizer que o rascunho ou a base dele se mexeram: recarregar e reconciliar, nunca repetir a chamada.versionsexige uma versão ativa; sem ela responde 409 a dizer para publicar primeiro. O que se omite no corpo mantém o valor da versão ativa.- Scopes como no resto da API REST:
validatechega comread;create,versionseapplyprecisam dewritemaisstructure. UmIdempotency-Keyem qualquer POST torna a repetição segura, e no MCP ocreate_workflow_v2aceitaidempotency_key.
O que o validador verifica
POST /v1/workflows-v2/validate não grava nada e responde sempre 200 com
{ok, issues}. O ok é a ausência de erros: um aviso nunca bloqueia. Cada
problema traz um code estável, a severity, o path pontuado e um target
que diz onde se resolve, trigger ou graph. Cobre gatilho em falta,
desconhecido ou que afinal é um passo; ids repetidos; keys desconhecidas; as
precondições de cada passo e de cada gatilho; o encadeamento das formas do
envelope; tokens para variáveis, passos ou saídas inexistentes; ciclos reativos,
quando o grafo escreve na tabela que o gatilho observa; e auto-recursão, quando
se passa workflow_id e uma sub-automação aponta ao próprio workflow. As rotas
de escrita correm o mesmo validador antes de gravar, e um erro aí é um 422 com
os códigos na mensagem.
Exemplo A: reagir a uma mudança de estado
Quando o estado de um documento de compra passa a Validado, escrever uma
nota na própria linha. O corpo abaixo é o de POST /v1/workflows-v2/validate;
para criar, acrescenta-se name e envia-se o mesmo para POST /v1/workflows-v2.
{
"workspace_id": "a5a50556-ef07-449d-a556-077a2a310461",
"trigger_config": {
"type": "record.event",
"config": {
"modes": ["updated"],
"featureId": "d174bd26-c7f0-46ed-a2e3-7bad133b28ce",
"watchedColumns": ["estado"]
}
},
"graph_config": {
"steps": [
{
"id": "stp_ifestado0001",
"key": "ifElse",
"config": {
"condition": {
"subject": "var",
"var": "{{vars.trigger.estado}}",
"op": "eq",
"value": "Validado"
},
"then": [
{
"id": "stp_escrever0001",
"key": "feature.write",
"config": {
"mode": "updateTrigger",
"targetFeatureId": "d174bd26-c7f0-46ed-a2e3-7bad133b28ce",
"bindings": [
{
"targetColumn": "notas",
"value": "Validado em {{sys.today}}",
"type": "text"
}
]
}
}
],
"else": []
}
}
]
}
}
O que aqui é regra:
watchedColumnssão nomes de coluna, e o gatilho só dispara quando uma delas muda de facto.mode: "updateTrigger"escreve na linha que disparou, por isso otargetFeatureIdtem de ser a mesma tabela do gatilho (trigger_row_write_target_mismatch).- Escrever numa coluna observada fecha um ciclo e é recusado
(
reactive_cycle_column_overlap): observa-seestado, escreve-senotas. - O ramo
elsepode ficar vazio; com os dois vazios o passo publica e não faz nada, e o validador avisa (branches_all_empty).
Exemplo B: um resumo diário por email
Todos os dias às 08:00 de Lisboa, ler as faturas por rever e enviar um email.
{
"workspace_id": "a5a50556-ef07-449d-a556-077a2a310461",
"trigger_config": {
"type": "cron",
"config": {
"mode": "daily",
"atTime": "08:00",
"timezone": "Europe/Lisbon"
}
},
"graph_config": {
"steps": [
{
"id": "stp_lerfaturas01",
"key": "feature.read",
"config": {
"featureId": "d174bd26-c7f0-46ed-a2e3-7bad133b28ce",
"filterGroup": {
"combinator": "and",
"rules": [
{
"id": "r1",
"columnName": "estado",
"operator": "equals",
"value": "Aguarda revisão"
}
]
},
"limit": 100,
"orderBy": { "column": "vencimento", "desc": false },
"mergeMode": "replace"
}
},
{
"id": "stp_enviaremail1",
"key": "email.send",
"config": {
"to": "compras@exemplo.pt",
"subject": "Faturas por rever em {{sys.today}}",
"body": "<p>A mais antiga é a {{record.numero}}, de {{record.fornecedor_nome}}, no valor de {{record.total}}.</p>",
"attachFiles": false
}
}
]
}
}
O que aqui é regra:
- O
cronarranca com o envelope vazio, por isso alguma coisa tem de trazer registos antes do email. Nenhum passo está preso à primeira posição: commergeMode: "append", ofeature.readjunta uma segunda tabela a meio. - Os operadores do
filterGroupsão os do editor de filtros da aplicação (equals,notEquals,contains,greaterThan,greaterOrEqual,lessThan,lessOrEqual,isEmpty,isNotEmpty,between), e só as regras de topo comandchegam à leitura. - A cadeia termina num passo que entrega; sem isso o validador avisa que a
automação processa e não faz nada (
chain_has_no_delivery). SemconnectionName, o email sai pelo transporte da organização.