Saltar para o conteúdo principal

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": {}
}
]
}
}
ChaveObrigatóriaO que é
trigger_config.typesimA key do gatilho, tal como aparece em Gatilhos. Vazia ou desconhecida é erro (trigger_missing, trigger_unknown_key).
trigger_config.confignãoA config do gatilho. Cada gatilho valida a sua (trigger_config_invalid).
graph_config.stepssim na práticaA cadeia, por ordem de execução. Lista vazia passa com aviso (graph_empty).
steps[].keysimA key do passo, de Passos. Desconhecida é erro (unknown_step_key).
steps[].idnãoIdentidade 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[].configdepende do passoA config do passo.
steps[].namenãoAlias 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, de GET /v1/features.
  • Colunas por nome técnico: targetColumn, columnName, filters[].column, watchedColumns, orderBy.column. É o name de GET /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:

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

PassoRESTMCP
Listar as automações de um workspaceGET /v1/workflows-v2?workspace_id=…list_workflows_v2
Ler a que está em vigorGET /v1/workflows-v2/{workflow_id}get_workflow_v2
Validar sem gravarPOST /v1/workflows-v2/validatevalidate_workflow_v2
Criar como rascunhoPOST /v1/workflows-v2create_workflow_v2
Publicar o rascunhoPOST /v1/workflows-v2/{workflow_id}/applysem equivalente
Acrescentar uma versão ativaPOST /v1/workflows-v2/{workflow_id}/versionssem equivalente
Ativarna aplicaçãona aplicação
  • Cada item da listagem é uma versão, e o workflow_id agrupa-as: é por ele que se endereça a automação. active=true filtra as que disparam; false não filtra nada de útil, porque uma versão superada também é inativa.
  • POST /v1/workflows-v2 nasce rascunho, e um rascunho nunca dispara. Com is_draft: false cria e publica de uma vez, e o resultado fica inativo.
  • apply preserva 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.
  • versions exige 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: validate chega com read; create, versions e apply precisam de write mais structure. Um Idempotency-Key em qualquer POST torna a repetição segura, e no MCP o create_workflow_v2 aceita idempotency_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:

  • watchedColumns são nomes de coluna, e o gatilho só dispara quando uma delas muda de facto.
  • mode: "updateTrigger" escreve na linha que disparou, por isso o targetFeatureId tem 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-se estado, escreve-se notas.
  • O ramo else pode 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 cron arranca com o envelope vazio, por isso alguma coisa tem de trazer registos antes do email. Nenhum passo está preso à primeira posição: com mergeMode: "append", o feature.read junta uma segunda tabela a meio.
  • Os operadores do filterGroup sã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 com and chegam à 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). Sem connectionName, o email sai pelo transporte da organização.