API da Gaio

Montar automações pela API

Como escrever o corpo de uma automação (o canal, o gatilho e o grafo de blocos), com o catálogo dos blocos, as variáveis, exemplos prontos e os erros da validação.

Baixar o catálogo de blocos (JSON Schema)
Nesta página

A definição de uma automação

Uma automação é um JSON com seis campos:

CampoObrigatórioO que é
titlesimO nome da automação, de 1 a 128 caracteres.
descriptionnãoUma descrição, de até 512 caracteres.
channelsimO canal em que a automação roda (veja Canal).
triggersimO que inicia a automação (veja Gatilho).
graphnãoOs blocos e as ligações entre eles (veja O grafo). Sem ele, a automação nasce só com o bloco inicial.
folder_idnãoA pasta em que a automação fica (veja Pastas). Sem ela, a automação fica fora de pastas.

A automação mais simples responde uma mensagem direta do Instagram que contém "oi" ou "olá":

{
  "title": "Boas-vindas",
  "channel": { "type": "instagram" },
  "trigger": { "events": ["message"], "match": "contains", "keywords": ["oi", "olá"] },
  "graph": {
    "nodes": {
      "root": { "type": "root", "data": { "next": ["saudacao"] } },
      "saudacao": {
        "type": "message",
        "data": {
          "text": "Oi, {contact.first_name}! Que bom ter você por aqui. Em que podemos ajudar?",
          "next": [null]
        }
      }
    }
  }
}

O root é o início, e o next dele aponta para o bloco saudacao, que envia o texto. O null no next da saudacao encerra a execução. O {contact.first_name} é uma variável, trocada pelo primeiro nome do contato no envio.

Este e os outros exemplos desta página passam na validação, são criados e são publicados pelos testes da API, então você pode partir deles.

Canal

channel.type é instagram ou whatsapp, e channel.connection_id é o id de uma conexão da conta, de GET /v1/connections.

  • WhatsApp: connection_id é obrigatório e precisa ser um número de WhatsApp da conta. A API aceita e publica um número desconectado, mas a automação não responde ninguém enquanto ele estiver assim: confira o status em GET /v1/connections.
  • Instagram: connection_id é opcional. Sem ele, a automação roda na conta do Instagram conectada.

Uma automação publicada não muda de canal: a resposta é 409, com workflow_published. Despublique antes.

Gatilho

O trigger diz que eventos iniciam a automação e como o texto deles é comparado com as palavras-chave.

CampoO que é
eventsOs eventos que iniciam a automação (tabela abaixo). Com [], nenhum evento inicia a automação: ela começa pela API (POST /v1/workflows/{id}/runs), pelo console (num envio para um contato, num envio em massa ou num iniciador de conversa do Instagram) ou por um bloco start de outra automação.
matchany (qualquer texto; as palavras-chave são ignoradas), contains, equals ou starts_with.
keywordsOpcional, padrão []. Até 200 palavras-chave, de 1 a 200 caracteres. A mesma lista vale para todos os eventos, e basta uma palavra-chave bater.
postsOpcional. null ou omitido (qualquer post), ou de 1 a 200 itens { "type": "feed", "id": "…" } ou { "type": "story", "id": "…" }, com o id da mídia no Instagram. Os posts do feed limitam o comment, e os stories limitam o story_message.
next_postOpcional, padrão false. true limita o comment ao próximo post publicado depois de criar a automação. Não combina com posts do feed em posts.
replies_list_idOpcional. A lista de respostas públicas aos comentários que iniciam a automação (veja Listas de respostas). Só no Instagram e só com o evento comment: com outros eventos, ela é aceita, mas nunca é usada.

A comparação com as palavras-chave não diferencia maiúsculas e minúsculas, mas diferencia acentos: horario não bate com "horário", então liste as duas grafias. Em contains, a palavra-chave vale em qualquer parte do texto, inclusive dentro de outra palavra: oi bate com "boa noite". equals e starts_with comparam o texto como chegou, sem tirar espaços nem pontuação: "oi!" não é igual a oi.

EventoQuando aconteceCanal
commentUm comentário num post ou reel.Instagram
live_commentUm comentário numa live.Instagram
story_messageUma resposta a um story.Instagram
story_mentionUma menção à conta no story de alguém. Não usa palavras-chave.Instagram
messageUma mensagem direta do contato.Instagram e WhatsApp
message_sentUma mensagem direta que a própria conta enviou.Instagram

No WhatsApp, só message inicia uma automação. Os outros eventos são aceitos, mas nunca disparam, e a validação avisa com trigger_event_not_supported.

Numa automação iniciada por comment ou live_comment, a primeira mensagem sai como resposta privada ao comentário, que não depende da janela de 24 horas. O Instagram aceita uma só resposta privada por comentário: uma segunda mensagem antes de o contato responder é recusada, e a execução termina ali. Por isso, depois da primeira mensagem, use botões ou uma pergunta, que esperam o contato responder. Daí em diante, as mensagens saem como mensagens diretas.

Um match diferente de any sem palavras-chave também é aceito, mas os eventos que comparam texto nunca disparam (aviso trigger_has_no_keywords). O story_mention não usa palavras-chave e dispara mesmo assim.

O grafo

graph.nodes é um mapa de blocos por id. Cada bloco tem type, o tipo do bloco, data, os campos daquele tipo, e position, opcional:

"pedir_email": {
  "type": "question",
  "data": {
    "text": "Qual é o seu e-mail?",
    "answer_type": "any",
    "answer_field": "email",
    "next": ["confirmar"]
  },
  "position": { "x": 510, "y": 0 }
}

As regras:

  • O início: o grafo precisa de um bloco com o id root e o tipo root. A execução começa nele.
  • Os ids: letras minúsculas e números, com _ entre as partes, até 64 caracteres. pedir_email e passo2 valem; pedirEmail, pedir-email, _passo e pedir__email não.
  • As saídas: data.next tem exatamente uma entrada por saída do bloco, na ordem das saídas, com o id do próximo bloco. null numa saída encerra a execução por ali. Um bloco final tem "next": [null], nunca []. A quantidade de saídas de cada tipo está na tabela abaixo.
  • Os caminhos: um next pode voltar a um bloco que já rodou, mas cada bloco roda no máximo 6 vezes na mesma execução. Na 7ª vez, a execução para ali, com o status stopped. Um bloco que não se alcança a partir do root é conferido, mas nunca roda.
  • As posições: position ({ "x": …, "y": … }) só serve para o editor do console desenhar o bloco. Sem ela, a API calcula uma. Ao reenviar um grafo que você leu, mantenha as posições, para não desarrumar o editor. Os campos group_id e media_kind, que aparecem em alguns blocos, também só servem para o editor.
  • O tamanho: até 500 blocos e 256 KiB, contando as posições que a API preenche.
  • Os recursos: as etiquetas, os campos personalizados e os uploads citados nos blocos precisam existir na conta. O grafo não cria nenhum deles. Crie as etiquetas com POST /v1/tags antes de salvar; os campos personalizados e os uploads são criados no console.
  • Os ids internos: ao reescrever um bloco, mantenha o id dos botões de buttons e de cards, porque as métricas de clique são contadas por ele. O id das opções de um randomizer e dos canais de um multichannel só serve para o editor, mas o randomizer conta as escolhas pela posição de cada opção: não reordene as opções de um randomizer que já rodou.
  • O que você não conhece: um tipo de bloco ou um campo que o seu código não conhece deve voltar como veio.
BlocoSaídas em next
buttons4: uma por botão, na ordem dos botões, e a 4ª, que segue logo depois do envio.
cards31: três por card, uma por botão, e a 31ª, que segue logo depois do envio.
conditional2: condição verdadeira e condição falsa.
invoke2: sucesso e falha.
randomizerUma por opção.
multichannelUma por canal, e mais uma para quando nenhum canal pode receber.
blank1, ou nenhuma, sem o campo next.
Os outros1.

Catálogo de blocos

O catálogo completo, com todos os campos, os valores aceitos e os tamanhos máximos, está em JSON Schema na cópia do catálogo e em GET /v1/workflow-graph-schema (veja O catálogo em JSON Schema). As regras que olham mais de um campo não estão no schema e são conferidas pela validação (veja Erros de validação). Abaixo, o que cada bloco faz. Os campos marcados como opcionais podem ficar de fora; os outros são obrigatórios.

Fora dos headers e dos fields do invoke, os mínimos de 1 caractere desta seção não estão no catálogo e não são conferidos pela validação: um text, um subtitle ou um title de botão vazio é aceito, e o envio falha quando a automação roda. Se o plano da conta inclui a assinatura da Gaio, um text ou um subtitle vazio sai só com a assinatura. No Instagram, o title de cada card também precisa de pelo menos 1 caractere.

root

O início da automação. Não faz nada: segue para a sua única saída.

  • next: 1 saída.

message

Envia um texto.

  • text: de 1 a 4096 caracteres, com variáveis. No Instagram, só os primeiros 1000 caracteres são entregues. Quando o plano da conta inclui a assinatura da Gaio, o texto é cortado para caber com ela, nos dois canais.
  • next: 1 saída, depois do envio.

question

Envia uma pergunta e espera a próxima mensagem do contato, por até 90 dias.

  • text: a pergunta, de 1 a 4096 caracteres, com variáveis. A validação não confere esse máximo: um texto mais longo é aceito, e o envio falha quando a automação roda. Quando o plano da conta inclui a assinatura da Gaio, o texto é cortado em vez de falhar. Como no message, o Instagram entrega só os primeiros 1000 caracteres, e menos com a assinatura.
  • answer_type: sempre any: a pergunta aceita qualquer resposta. number está obsoleto: é aceito e roda como any, sem conferir se a resposta é um número, e a validação avisa com answer_type_deprecated.
  • answer_field: a chave de um campo personalizado que já existe na conta, em que a resposta é salva, ou "" para não salvar. A API não cria campos: crie o campo no console e confira a chave em GET /v1/custom-fields. Nos blocos seguintes, a resposta está na variável com essa chave, como {email}.
  • next: 1 saída, depois da resposta.

A próxima mensagem de texto do contato responde a pergunta, mesmo que tenha a palavra-chave de outra automação, que então não começa. Uma mensagem sem texto, como um áudio, uma figurinha ou uma imagem sem legenda, não responde: a pergunta continua esperando. No WhatsApp, a legenda de uma imagem ou de um vídeo conta como texto. No Instagram, uma resposta a um story também não responde a pergunta.

media

Envia uma imagem, um vídeo, um áudio ou um documento.

  • upload_id: opcional, o id de um arquivo enviado ao console da conta.
  • media_url: opcional, o endereço do arquivo, com variáveis. Só no Instagram.
  • next: 1 saída.

Envie exatamente um dos dois, upload_id ou media_url. No WhatsApp, só upload_id funciona.

buttons

Envia um texto com até três botões.

  • subtitle: o texto, de 1 a 4096 caracteres, com variáveis. O Instagram entrega até 1000 caracteres e o WhatsApp, até 1024, e menos quando o plano da conta inclui a assinatura da Gaio.
  • buttons: de 0 a 3 botões, cada um com:
    • id: opcional, um texto que identifica o botão nas métricas. Sem ele, as métricas usam a posição e o título do botão, e mudar um dos dois separa as contagens;
    • title: o texto do botão, de 1 a 20 caracteres;
    • url: opcional, um endereço http ou https, que transforma o botão num link. No Instagram, o clique abre o link e segue a saída do botão, mas não abre a janela de 24 horas: um bloco que envia mensagem nessa saída só é entregue se a janela do contato já estiver aberta, e, se não estiver, a execução para nele. No WhatsApp, o clique abre o link, mas a saída do botão ainda não funciona: deixe-a null;
    • whatsapp_connection_id e whatsapp_message: opcionais, transformam o botão num link para conversar com um número de WhatsApp conectado da conta, com a mensagem já escrita (até 300 caracteres; a Gaio põe um código Ref: antes dela). Com whatsapp_connection_id, a url do botão é ignorada. O clique não segue a saída do botão: ela segue quando o contato envia a mensagem para esse número, e a execução continua no WhatsApp. Por isso, os blocos dessa saída precisam funcionar no WhatsApp. Só em automações do Instagram; numa automação do WhatsApp, o botão vira um botão de resposta comum.
  • next: 4 saídas. As três primeiras seguem o toque em cada botão, na ordem dos botões. A 4ª segue logo depois do envio; com null, nada roda ali. Nos dois casos, a execução fica esperando um toque, por até 90 dias.

Enquanto a execução espera um toque, uma mensagem do contato não responde os botões: ela é tratada como uma mensagem nova e pode iniciar outra automação pela palavra-chave.

No WhatsApp, a mensagem tem de 1 a 3 botões de resposta ou exatamente um botão de link, sem misturar os dois.

cards

Envia um carrossel de cards, cada um com imagem e até três botões.

  • caption: opcional, o texto que acompanha o carrossel, de até 4096 caracteres, com variáveis. O WhatsApp entrega até 1024 caracteres. Obrigatório no WhatsApp; o Instagram ignora.
  • cards: de 1 a 10 cards, cada um com:
    • title e subtitle: até 80 caracteres cada, com variáveis;
    • upload_id: opcional (obrigatório no WhatsApp), a imagem, um arquivo enviado ao console;
    • buttons: de 0 a 3 botões, com os mesmos campos dos botões do bloco buttons.
  • next: 31 saídas. O botão b do card c (os dois contados a partir de 0) segue a saída c × 3 + b. A 31ª segue logo depois do envio.

No WhatsApp, o carrossel tem de 2 a 10 cards, todos com imagem e com de 1 a 3 botões de resposta ou exatamente um botão de link, do mesmo tipo em todos os cards.

tags

Põe ou tira etiquetas do contato.

  • operation: add ou remove.
  • tags: os nomes das etiquetas, exatamente como estão na conta (o name de GET /v1/tags), com as mesmas maiúsculas e acentos. A validação aceita outra grafia do mesmo nome, como cliente vip para "Cliente VIP". Mas, com ela, o add renomeia a etiqueta da conta, e o remove não tira a etiqueta do contato.
  • next: 1 saída.

data

Grava um valor num campo personalizado do contato.

  • name: a chave do campo, de até 50 caracteres, que precisa existir na conta.
  • value: opcional, de 1 a 100 caracteres, com variáveis. Também aceita $input e $timestamp (veja Variáveis). Sem value, o bloco apaga o valor do campo no contato.
  • next: 1 saída.

math

Faz uma conta e grava o resultado num campo personalizado.

  • result_field: a chave do campo que recebe o resultado, de até 50 caracteres, que precisa existir na conta.
  • operand_a e operand_b: { "type": "field", "value": "<chave do campo>" }, com um campo que existe na conta, ou { "type": "constant", "value": "10.5" }. O value é sempre um texto, inclusive na constante: 10, sem aspas, é recusado com schema_violation. Na constante, use ponto como separador decimal: "1.234" é lido como 1,234.
  • operator:
    • add, sub, mul e div: as quatro operações;
    • min e max: o menor e o maior dos dois;
    • mod: o resto da divisão;
    • pct_of: a por cento de b (a × b ÷ 100);
    • pct_add: a acrescido de b por cento (a × (1 + b ÷ 100)).
  • next: 1 saída.

Uma divisão ou um mod por zero dá 0. Um campo vazio, ou um campo de texto com um valor que não é só um número com ponto decimal (como 1,5 ou R$ 10), conta como 0. O resultado é gravado como o número escrito com ponto e sem arredondar, como 20, 10.5 ou 0.3333333333333333, e é esse texto que a variável do campo mostra nos blocos seguintes.

conditional

Segue uma saída se as condições forem verdadeiras e a outra se não forem.

  • conditions: a lista de condições, cada uma com field_type, field_name, operator e value, conforme a tabela abaixo.
  • combinator: opcional, and (todas as condições, o padrão) ou or (pelo menos uma).
  • next: 2 saídas, a verdadeira e a falsa.
field_typefield_nameoperatorvalue
tagO nome da etiqueta, exatamente como está na conta, com as mesmas maiúsculas e acentos. Com outra grafia, a validação passa e has_tag é sempre falso.has_tag, not_has_tag—
custom_fieldA chave do campo.has_field, not_has_field, field_equals, field_not_equalsO valor comparado, nos dois últimos.
score—field_equals, field_not_equals, field_greater_than, field_greater_or_equal, field_less_than, field_less_or_equal, field_betweenO número, ou min,max em field_between.
variableO nome da variável, sem chaves, como system.current_time ou a chave de um campo.has_field, not_has_field, field_equals, field_not_equals, field_contains, field_not_contains, field_greater_than, field_greater_or_equal, field_less_than, field_less_or_equal, field_between, date_before, date_after, date_on, time_before, time_after, time_betweenUm texto, um número, min,max em field_between (com ponto como separador decimal, como 1.5,3), uma data (dd/MM/aaaa ou aaaa-MM-dd), uma hora (HH:mm) ou início,fim em time_between, que pode atravessar a meia-noite (22:00,06:00).
follower_status—is_follower, not_follower—
messaging_window—window_open, window_closed—

score é a pontuação do contato. follower_status diz se o contato segue a conta, e só funciona no Instagram. messaging_window diz se a janela de 24 horas do contato está aberta no canal da execução.

Na condição variable, igualdade e "contém" não diferenciam maiúsculas e minúsculas, e o value é comparado como está, sem variáveis. As horas são comparadas por minuto: em time_between, os dois extremos entram (09:00,18:00 vale das 09:00 às 18:00:59), e time_before e time_after não incluem o próprio minuto. Um value fora do formato, ou um operador de comparação sem value, deixa a condição falsa, sem erro.

Os campos field_type, field_name, operator e value direto em data, fora de conditions, são a forma antiga de uma condição só. Use conditions.

randomizer

Divide as execuções entre as saídas na proporção de cada opção. Não é um sorteio: a primeira execução segue a primeira opção, e cada execução seguinte segue a opção que está mais abaixo do seu percentual. Com 50 e 50, as execuções se alternam. Serve para testes A/B.

  • options: de 2 a 20 opções { "id": "…", "percentage": 50 }, com percentuais que somam 100, nenhum negativo. O id é opcional.
  • next: uma saída por opção, na ordem das opções.

sleep

Espera antes de seguir.

  • seconds: de 1 a 82800 (23 horas).
  • next: 1 saída, depois da espera.

Uma mensagem depois da espera só é entregue se a janela de 24 horas do contato ainda estiver aberta (veja Enviar uma mensagem). A exceção é a primeira mensagem de uma execução iniciada por um comentário, que sai como resposta privada e não depende da janela (veja Gatilho).

start

Inicia outra automação para o mesmo contato, no mesmo canal.

  • workflow_id: o id da outra automação, publicada no mesmo canal (a mesma conexão) em que a execução está. Quando ela não está publicada nesse canal (está pausada, na lixeira ou em outro canal), a execução termina neste bloco, com parallel true ou false. A validação avisa desses casos (veja os avisos em Erros de validação).
  • parallel: com true, a outra automação começa e esta segue em seguida. Com false, esta espera a outra terminar.
  • delay_seconds: opcional, de 0 a 82800, uma espera antes de iniciar.
  • next: 1 saída.

invoke

Faz uma requisição HTTP para o seu sistema, como um webhook.

  • method: o método HTTP, como POST.
  • endpoint: o endereço, http ou https, com variáveis.
  • headers: opcional, uma lista de { "name": "…", "value": "…" } (o nome de 1 a 100 caracteres, o valor de 1 a 1000), com variáveis e segredos no value.
  • data: opcional, o corpo da requisição, um texto com variáveis e segredos. Sem cabeçalho Content-Type, o corpo vai como application/json.
  • fields: opcional, uma lista de { "path": "$.cliente.id", "name": "<chave do campo>" } (o path de 1 a 500 caracteres, o name de 1 a 50), que copia valores de uma resposta JSON de sucesso, por JSONPath, para campos personalizados do contato. Só o primeiro resultado de cada path é copiado. Um resultado 0, false ou vazio não é copiado, e o campo fica como estava.
  • next: 2 saídas. A primeira segue quando a resposta é 2xx, e a segunda segue em qualquer outro caso.

Cada tentativa espera a resposta por até 60 segundos e segue até 5 redirecionamentos. Uma resposta 429 ou 5xx é repetida, até 3 tentativas no total, então o seu sistema deve aceitar a mesma requisição mais de uma vez. Endereços de rede privada são recusados e seguem a saída de falha.

Num corpo JSON, as variáveis de texto entram já escapadas, mas sem aspas: escreva as aspas no próprio corpo, como em "{\"instagram\": \"{contact.alias}\"}". O exemplo Lead para o CRM mostra um corpo completo.

google-sheets

Escreve, atualiza ou apaga uma linha numa planilha do Google conectada à conta.

  • google_external_id, spreadsheet_id e tab_title: a conta do Google, a planilha e a aba. spreadsheet_name, opcional, é o nome da planilha que o editor mostra. A API não lista contas nem planilhas do Google: monte o bloco no editor do console e leia os valores com GET /v1/workflows/{workflowId}.
  • operation:
    • append: acrescenta uma linha;
    • update: reescreve a primeira linha encontrada inteira. As colunas que não estão em values e os valores que saem vazios ficam vazios na planilha;
    • upsert: atualiza a primeira linha encontrada, sem apagar uma célula quando o valor sai vazio, ou acrescenta uma linha se nenhuma for encontrada;
    • delete: apaga a primeira linha encontrada.
  • match: opcional, uma lista de { "column": "…", "value": "…" }, com variáveis no value, que encontra a primeira linha em que todas as colunas têm exatamente esses valores. É o que update, upsert e delete usam. Sem match, ou com uma lista vazia, eles agem na primeira linha de dados da aba: envie sempre um match nessas operações.
  • values: opcional, as colunas e os valores escritos, como { "Nome": "{contact.name}", "Data": "{system.current_date}" }. As colunas são os títulos da linha 1 da aba, e uma coluna que a aba não tem é ignorada. Cada valor é um texto: escreva "30", não 30, ou o bloco falha na execução.
  • next: 1 saída.

Os valores aceitam variáveis, $input e $timestamp, e também {contact.tag.<etiqueta>}, que vira true ou false conforme o contato tenha a etiqueta. A etiqueta aparece em minúsculas, sem acentos e com um - no lugar de cada trecho que não é letra nem número, até 50 caracteres: "Cliente VIP" vira {contact.tag.cliente-vip}, e "CPF/CNPJ" vira {contact.tag.cpf-cnpj}. Se a conta já teve outra etiqueta com o mesmo nome, o identificador pode ter um sufixo, como cliente-vip-1. Um valor que começa com =, +, - ou @ é escrito como texto, nunca como fórmula.

multichannel

Continua a execução com a mesma pessoa em outro canal conectado.

  • mode: first segue pelo primeiro canal da lista em que o contato pode receber mensagens. all inicia uma execução em cada canal em que ele pode receber.
  • channels: de 1 a 10 canais { "id": "…", "provider": "whatsapp", "connection_id": "…" }, sem repetir conexão. provider é instagram ou whatsapp; o id é opcional.
  • next: uma saída por canal, na ordem dos canais, e mais uma, seguida quando nenhum canal pode receber.

pause-automations

Pausa as automações para o contato e encerra a execução.

  • minutes: por quanto tempo as automações ficam pausadas para o contato, de 1 a 52034400 (cerca de 99 anos).
  • next: 1 saída, que nunca é seguida. Envie [null].

comment

Uma anotação no editor. Não faz nada.

  • text: opcional, a anotação.
  • next: 1 saída.

blank

Um bloco vazio. Não faz nada.

  • next: opcional, 1 saída.

unsupported

Um passo importado de outra ferramenta que a Gaio não converteu. Não faz nada. Não crie esse bloco; se ele vier numa automação que você leu, devolva como veio.

  • data: o conteúdo original do passo.
  • next: 1 saída.

Variáveis

Os textos dos blocos aceitam variáveis entre chaves, como {contact.first_name}, trocadas pelo valor na hora em que o bloco roda. Os campos que aceitam variáveis estão no catálogo de blocos.

  • O nome tem só letras sem acento, números, _, . e -, sem espaços. { contact.name } e {ação} não são variáveis e ficam no texto como estão.
  • Maiúsculas e minúsculas contam: {Contact.Name} não é {contact.name}.
  • Uma variável que não existe vira texto vazio, sem erro. Confira o nome: a validação não confere as variáveis.
  • Uma variável que existe, mas está sem valor, como {contact.picture_url} no WhatsApp, vira o texto null. Antes de usar uma variável que pode faltar, confira com um bloco conditional (has_field numa condição variable).
  • Não há como escrever uma palavra entre chaves num campo com variáveis: ela vira o valor ou some.

Do contato

VariávelValor
{contact.id}O id do contato, o mesmo de GET /v1/contacts/{contactId}.
{contact.name}O nome do contato no canal. Sem nome, o contact.alias.
{contact.first_name}O primeiro nome. Sem nome, o contact.alias.
{contact.alias}No Instagram, o nome de usuário, sem @. No WhatsApp, o telefone só com dígitos, com o código do país e sem +: o whatsapp.phone do contato sem o +, como 5511999998888. Um celular brasileiro pode vir sem o nono dígito, quando é assim que o WhatsApp o identifica.
{contact.score}A pontuação do contato.
{contact.is_verified}true quando a conta do contato no Instagram tem o selo de verificada e false em qualquer outro caso, inclusive quando o Instagram ainda não mostrou o perfil e no WhatsApp.
{contact.follower_count}Quantos seguidores a conta do contato tem no Instagram, segundo a última leitura do perfil, que a Gaio refaz quando o contato interage e a leitura guardada tem mais de um dia. Vira o texto null quando o Instagram ainda não mostrou o perfil e no WhatsApp; numa condição, ele não casa com field_greater_than, field_greater_or_equal, field_less_than, field_less_or_equal nem field_between, e not_has_field separa esse contato.
{contact.picture_url}O endereço da foto de perfil. No WhatsApp, e no Instagram quando o perfil não tem foto, vira o texto null.
{contact.picture_url_base64}O endereço da foto de perfil codificado em Base64.
{contact.tags}Os nomes das etiquetas do contato, separados por vírgula e espaço, como cliente, vip. A ordem é a do código dos caracteres: maiúsculas antes de minúsculas e letras acentuadas no fim. Já inclui as etiquetas que a própria execução pôs ou tirou.

O Instagram mostra o perfil de um contato, com o selo e os seguidores, a partir da primeira mensagem que ele manda para a conta, ou do primeiro toque num ice-breaker. Quem só comentou ainda não tem perfil.

Da mensagem

VariávelValor
{message.trigger}O texto da mensagem ou do comentário que iniciou a automação. Vazio quando a execução começou pela API, por um envio em massa ou por uma menção. Numa automação iniciada por um bloco start, é o texto do gatilho da automação que a iniciou.
{message.last_contact}O texto da última mensagem do contato. Quando essa mensagem não tem texto (um áudio, uma imagem) ou o contato nunca escreveu, vira o texto null. Numa automação iniciada por message_sent, até a execução parar para esperar (uma pergunta, um botão ou um sleep de mais de 5 segundos), é o texto que a própria conta enviou.
{message.last_sender}Quem mandou a última mensagem da conversa: contact, o contato, ou user, a conta (pela equipe ou pela automação).
{message.interaction_type}O tipo da interação que trouxe a execução até o bloco: dm, dm_sent, comment, story, live ou mention. No começo, é a que iniciou a automação. Depois da resposta a um question, é dm (ou story), mesmo numa automação iniciada por comentário. Depois de um botão, e numa execução iniciada por um envio em massa, é o tipo da última mensagem do contato. Vazio quando a execução começou pela API.
{message.post_id}O id do post comentado, quando a automação começou por um comentário.
{message.post_url}O link desse post, quando a Gaio o conhece.
{message.collaborator_email}Numa automação iniciada por message_sent, o e-mail de quem da equipe digitou a mensagem no chat ou na caixa de entrada do console: o e-mail do perfil do dono da conta ou de um colaborador. Vazio quando a mensagem foi enviada pelo app do Instagram e em qualquer outro gatilho. Numa automação iniciada por um bloco start, é o da automação que a iniciou.

Da conversa

{conversation.last_30m}, {conversation.last_1h} e {conversation.last_24h} trazem a conversa da janela, da mensagem mais antiga para a mais recente, uma por linha: [2026-10-05 14:30:00] Contact: … ou [2026-10-05 14:31:12] Agent: …. São até 50, 75 e 100 mensagens, e as mensagens sem texto ficam de fora. Os horários estão em UTC, e não no fuso da conta. Agent é qualquer mensagem que não veio do contato, seja da equipe ou da automação.

Do sistema

{system.current_date} (dd/MM/aaaa), {system.current_time} (HH:mm:ss) e {system.current_datetime} (dd/MM/aaaa HH:mm:ss), no fuso horário da conta, definido no console em Configurações › Conta › Geral (o padrão é o de São Paulo). A API não informa o fuso.

Campos personalizados

Cada campo personalizado é uma variável pela chave dele, o name de GET /v1/custom-fields, e não pelo rótulo: o campo "Maior dificuldade" é {maior-dificuldade}. A chave nasce do rótulo, quando o campo é criado no console, em minúsculas, sem acentos e com um - no lugar de cada trecho que não é letra nem número, e não muda quando o rótulo muda. Se a conta já teve um campo com a mesma chave, a nova ganha um sufixo, como cidade-1: confira sempre em GET /v1/custom-fields.

O valor que a própria execução grava (por data, pela resposta de um question, por math ou pelos fields de um invoke) já vale nos blocos seguintes. Use a chave do campo nesses blocos, para que a variável e o campo salvo tenham o mesmo nome.

Da conta

{account.<nome>} é uma variável de texto que a conta define no console.

Segredos

{secret.<nome>} é um segredo da conta, como um token do seu sistema, cadastrado no console com o domínio em que pode ser usado. Ele só funciona nos headers e no data de um invoke, e só quando o endereço da requisição é exatamente desse domínio. Em qualquer outro lugar, o segredo vira texto vazio, e a requisição sai sem a credencial.

$input e $timestamp

No value de um data e, num google-sheets, nos values e no value de cada match, o valor inteiro do campo pode ser:

  • $input: o texto que chegou com a mensagem atual, como a resposta a um question ou o texto do gatilho;
  • $timestamp: o momento atual, em milissegundos desde 1º de janeiro de 1970 (UTC).

Só funciona quando o campo tem exatamente $input ou $timestamp. Por isso, qualquer valor desses campos que comece com $ é lido como variável: "$50" vira vazio.

O catálogo em JSON Schema

GET /v1/workflow-graph-schema (workflows:read) responde o catálogo de blocos em JSON Schema (2020-12) e a versão dele:

{
  "schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "…": "…" },
  "catalog_version": "…"
}

O schema descreve graph.nodes: cada tipo de bloco, os campos de data, os limites e o formato dos ids. Com ele, você confere um grafo antes de enviá-lo ou gera tipos para o seu código. A cópia do catálogo traz o mesmo schema, com a versão no $comment, para consultar sem chave; a resposta da API é a referência.

O JSON Schema não expressa tudo. A validação confere: o tipo do root, a quantidade de saídas, os blocos citados em next, o limite de 256 KiB e os recursos da conta. Confere também as regras do compilador: de 2 a 20 opções num randomizer, com percentuais que somam 100 e nenhum negativo; mode e de 1 a 10 canais num multichannel; exatamente uma origem num media; condições completas num conditional. Por último, as regras de publicação. O limite de 500 blocos está no maxProperties do schema.

O catálogo evolui fora da versão da API e cresce por adição: um tipo de bloco, um campo opcional, um valor. Guarde o catalog_version para notar quando ele muda. Uma mudança que recusaria um grafo aceito hoje é anunciada com 90 dias de antecedência, e, nesse prazo, a validação avisa sobre os grafos afetados.

Exemplos

Os exemplos do Instagram, a boas-vindas inclusive, precisam de uma conta do Instagram conectada na sua conta da Gaio. Os exemplos também usam recursos que a sua conta precisa ter: as etiquetas lead-instagram, orcamento e cliente, o campo personalizado email, um número de WhatsApp conectado no lugar de <id-da-conexao-whatsapp> e, no último, um segredo crm_token para o domínio api.exemplo.com.br. Cada exemplo tem o arquivo para baixar.

Cupom por comentário

Quem comenta "cupom" num post recebe o cupom com dois botões. "Ver a loja" abre o link e etiqueta o contato como lead; "Falar com a gente" avisa que alguém do time vai responder. cupom-por-comentario.json

{
  "title": "Cupom por comentário",
  "channel": { "type": "instagram" },
  "trigger": { "events": ["comment"], "match": "contains", "keywords": ["cupom"] },
  "graph": {
    "nodes": {
      "root": { "type": "root", "data": { "next": ["oferta"] } },
      "oferta": {
        "type": "buttons",
        "data": {
          "subtitle": "Seu cupom é BEMVINDO10. Quer ver a loja ou falar com a gente?",
          "buttons": [
            { "id": "ver_loja", "title": "Ver a loja", "url": "https://exemplo.com.br/loja" },
            { "id": "falar", "title": "Falar com a gente" }
          ],
          "next": ["marcar_lead", "atendimento", null, null]
        }
      },
      "marcar_lead": {
        "type": "tags",
        "data": { "operation": "add", "tags": ["lead-instagram"], "next": [null] }
      },
      "atendimento": {
        "type": "message",
        "data": { "text": "Já chamamos alguém do time para falar com você.", "next": [null] }
      }
    }
  }
}

O terceiro null é a saída do terceiro botão, que não existe, e o quarto é a saída depois do envio.

Pedido de orçamento no WhatsApp

Quem manda "orçamento" no WhatsApp recebe uma pergunta, e a resposta vai para o campo email. Depois, a automação confirma e etiqueta o contato. orcamento-whatsapp.json

{
  "title": "Pedido de orçamento",
  "channel": { "type": "whatsapp", "connection_id": "<id-da-conexao-whatsapp>" },
  "trigger": { "events": ["message"], "match": "contains", "keywords": ["orçamento"] },
  "graph": {
    "nodes": {
      "root": { "type": "root", "data": { "next": ["pedir_email"] } },
      "pedir_email": {
        "type": "question",
        "data": {
          "text": "Para qual e-mail mandamos o orçamento?",
          "answer_type": "any",
          "answer_field": "email",
          "next": ["confirmar"]
        }
      },
      "confirmar": {
        "type": "message",
        "data": { "text": "Obrigado! Você recebe o orçamento em até 1 dia útil.", "next": ["etiquetar"] }
      },
      "etiquetar": {
        "type": "tags",
        "data": { "operation": "add", "tags": ["orcamento"], "next": [null] }
      }
    }
  }
}

Preço para cliente e para novo contato

Quem pergunta "preço" recebe um valor se já tem a etiqueta cliente e outro se não tem. O novo contato recebe um lembrete uma hora depois. cliente-ou-novo.json

{
  "title": "Preço para cliente e para novo contato",
  "channel": { "type": "instagram" },
  "trigger": { "events": ["message"], "match": "contains", "keywords": ["preço"] },
  "graph": {
    "nodes": {
      "root": { "type": "root", "data": { "next": ["ja_e_cliente"] } },
      "ja_e_cliente": {
        "type": "conditional",
        "data": {
          "conditions": [{ "field_type": "tag", "field_name": "cliente", "operator": "has_tag" }],
          "combinator": "and",
          "next": ["preco_cliente", "preco_novo"]
        }
      },
      "preco_cliente": {
        "type": "message",
        "data": { "text": "Como você já é cliente, o plano sai por R$ 89 por mês.", "next": [null] }
      },
      "preco_novo": {
        "type": "message",
        "data": { "text": "O plano custa R$ 99 por mês, com 7 dias grátis.", "next": ["esperar"] }
      },
      "esperar": { "type": "sleep", "data": { "seconds": 3600, "next": ["lembrete"] } },
      "lembrete": {
        "type": "message",
        "data": { "text": "Ficou alguma dúvida sobre o plano? É só responder aqui.", "next": [null] }
      }
    }
  }
}

Teste A/B da mensagem de abertura

Quem responde um story recebe uma de duas mensagens, meio a meio: as execuções se alternam entre as duas. teste-a-b.json

{
  "title": "Teste A/B da mensagem de abertura",
  "channel": { "type": "instagram" },
  "trigger": { "events": ["story_message"], "match": "any" },
  "graph": {
    "nodes": {
      "root": { "type": "root", "data": { "next": ["sortear"] } },
      "sortear": {
        "type": "randomizer",
        "data": {
          "options": [
            { "id": "versao_a", "percentage": 50 },
            { "id": "versao_b", "percentage": 50 }
          ],
          "next": ["mensagem_a", "mensagem_b"]
        }
      },
      "mensagem_a": {
        "type": "message",
        "data": { "text": "Obrigado por responder o story! Quer conhecer a novidade?", "next": [null] }
      },
      "mensagem_b": {
        "type": "message",
        "data": { "text": "Vimos sua resposta. Temos uma novidade para você!", "next": [null] }
      }
    }
  }
}

Lead para o CRM

Quem manda "quero" informa o e-mail, e a automação envia o contato para o seu CRM, com o token num segredo da conta. Se o CRM responder com erro, o contato é avisado. lead-para-o-crm.json

{
  "title": "Lead para o CRM",
  "channel": { "type": "instagram" },
  "trigger": { "events": ["message"], "match": "contains", "keywords": ["quero"] },
  "graph": {
    "nodes": {
      "root": { "type": "root", "data": { "next": ["pedir_email"] } },
      "pedir_email": {
        "type": "question",
        "data": {
          "text": "Qual é o seu e-mail? Um especialista vai falar com você.",
          "answer_type": "any",
          "answer_field": "email",
          "next": ["enviar_crm"]
        }
      },
      "enviar_crm": {
        "type": "invoke",
        "data": {
          "method": "POST",
          "endpoint": "https://api.exemplo.com.br/leads",
          "headers": [{ "name": "Authorization", "value": "Bearer {secret.crm_token}" }],
          "data": "{\"instagram\": \"{contact.alias}\", \"nome\": \"{contact.name}\", \"email\": \"{email}\", \"mensagem\": \"{message.trigger}\"}",
          "next": ["confirmar", "avisar_falha"]
        }
      },
      "confirmar": {
        "type": "message",
        "data": { "text": "Pronto, {contact.first_name}! Recebemos o seu contato.", "next": [null] }
      },
      "avisar_falha": {
        "type": "message",
        "data": {
          "text": "Não conseguimos registrar agora. Pode mandar o seu e-mail de novo daqui a pouco?",
          "next": [null]
        }
      }
    }
  }
}

A boas-vindas do início da página também tem arquivo.

Validar, criar e publicar

Os passos, com os escopos de cada um:

  1. Validar a definição (workflows:read). POST /v1/workflows/validate recebe só channel, trigger e graph, os três obrigatórios (com title, description ou folder_id, a resposta é 400), e não salva nada:

    jq '{channel, trigger, graph}' boas-vindas.json |
      curl https://api.gaio.social/v1/workflows/validate \
        -H "Authorization: Bearer $GAIO_API_KEY" \
        -H "Content-Type: application/json" \
        -d @-
    
    { "valid": true, "errors": [], "warnings": [] }
    

    valid é true quando errors está vazio. Nesse caso, a publicação não recusa a automação com workflow_invalid, mas o motor de automações ainda pode recusar a automação compilada, com 422 e o código workflow_not_publishable (o motivo vem em context.details). Os warnings não impedem a publicação, mas apontam algo que provavelmente não faz o que você queria. Validar conta como leitura no limite de uso.

  2. Criar (workflows:write). POST /v1/workflows recebe a definição inteira e cria um rascunho:

    curl https://api.gaio.social/v1/workflows \
      -H "Authorization: Bearer $GAIO_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
      -d @boas-vindas.json
    

    A resposta, 201, é a automação, com status: "draft" e o cabeçalho ETag: "0". A criação confere o grafo, o canal e o gatilho, mas não tudo o que a publicação confere: valide antes de criar.

  3. Publicar (workflows:publish). POST /v1/workflows/{workflowId}/publish, com o ETag da última resposta em If-Match, publica exatamente aquela revisão:

    curl https://api.gaio.social/v1/workflows/$WORKFLOW_ID/publish \
      -X POST \
      -H "Authorization: Bearer $GAIO_API_KEY" \
      -H 'If-Match: "0"'
    

    A resposta, 200, traz status: "published". Daí em diante, a automação responde os seus contatos.

Para mudar uma automação, leia com GET /v1/workflows/{workflowId}, altere e envie com PATCH, com o ETag da leitura em If-Match (veja Editar e publicar uma automação). No PATCH, graph substitui o grafo inteiro, e um bloco que ficou de fora é apagado; trigger também substitui o gatilho inteiro. Antes de publicar de novo, POST /v1/workflows/{workflowId}/validate confere o rascunho salvo e responde a revision conferida.

Erros de validação

Cada problema vem com o código, o bloco e o caminho do campo:

{
  "valid": false,
  "errors": [
    {
      "code": "next_arity_invalid",
      "message": "A buttons node takes exactly 4 next entries (null for a branch that goes nowhere), got 2.",
      "node_id": "oferta",
      "path": "nodes.oferta.data.next"
    }
  ],
  "warnings": []
}

Os erros, que impedem a publicação:

CódigoO que está errado
schema_violationUm bloco não segue o catálogo: um tipo desconhecido, um campo que falta, sobra ou tem o formato errado.
graph_too_largeMais de 500 blocos ou mais de 256 KiB.
node_id_invalidUm id fora do formato.
root_missingFalta o bloco root do tipo root.
next_arity_invalidUm next com mais ou menos entradas do que as saídas do bloco.
node_not_foundUm next aponta para um bloco que não existe.
tag_not_found, custom_field_not_found, upload_not_foundO bloco cita uma etiqueta, um campo personalizado ou um upload que a conta não tem.
link_button_url_emptyUm botão com url vazia.
media_source_invalidUm media sem upload_id e sem media_url, ou com os dois.
randomizer_options_invalidUm randomizer com menos de 2 ou mais de 20 opções, com um percentual negativo ou com percentuais que não somam 100.
conditional_incompleteUma condição sem field_type, operator, field_name ou value, quando eles são necessários.
multichannel_mode_missing, multichannel_channels_invalidUm multichannel sem mode, sem canais ou com mais de 10, com um canal de connection_id vazio ou com a mesma conexão repetida.
handoff_number_disconnectedUm botão leva para um número de WhatsApp que não está conectado. O número vem em connection_id.
channel_required, channel_not_found, channel_type_mismatchO canal falta, não existe na conta ou é de outro tipo.
posts_without_event, next_post_without_comment, next_post_with_postsO gatilho tem posts sem o evento que eles limitam, ou next_post sem comment ou junto com posts do feed.
replies_list_not_supported, replies_list_not_foundA lista de respostas está numa automação do WhatsApp ou não existe na conta.

Os avisos, que não impedem a publicação:

CódigoO que provavelmente não funciona
keyword_collisionOutra automação publicada no mesmo canal responde ao mesmo gatilho. O aviso traz workflow_ids e keywords.
trigger_event_not_supportedUm evento que o canal não tem, como comment no WhatsApp. Ele nunca dispara.
trigger_has_no_keywordsUm match que pede palavras-chave sem nenhuma palavra-chave. A automação nunca dispara.
start_workflow_not_foundUm start aponta para uma automação que a conta não tem, nem na lixeira. A execução termina no bloco. O id vem em workflow_ids.
start_workflow_deletedUm start aponta para uma automação que está na lixeira. A execução termina no bloco. O id vem em workflow_ids, e o id e o título, em workflows.
start_workflow_unpublishedUm start aponta para uma automação que não está publicada. Enquanto ela não for publicada, a execução termina no bloco. O id vem em workflow_ids, e o id e o título, em workflows.
start_workflow_other_channelUm start aponta para uma automação publicada em outro canal. Uma automação só inicia outra do mesmo canal, e a execução termina no bloco. O id vem em workflow_ids, e o id e o título, em workflows.
answer_type_deprecatedUm question com answer_type: "number", que roda como any: a resposta não é conferida. Troque por any.
next_arity_invalidNum rascunho salvo pelo editor do console, um next com mais ou menos entradas do que as saídas. Ao ler, a API completa com null o que falta, mas não corta o que sobra: corte a sobra antes de reenviar o grafo, ou o PATCH responde invalid_graph.

Criar e alterar conferem só parte da tabela de erros:

  • um problema de formato, tamanho, ids, root, saídas ou recursos da conta (schema_violation, graph_too_large, node_id_invalid, root_missing, next_arity_invalid, node_not_found, tag_not_found, custom_field_not_found e upload_not_found) recebe 422, com o código invalid_graph e a lista em context.errors;
  • um problema do canal recebe 422, com invalid_channel e context.reason igual a required, not_found ou type_mismatch;
  • um problema do gatilho recebe 422, com invalid_trigger e o código da tabela em context.reason, um de cada vez.

Os outros só aparecem na validação e na publicação. Publicar responde 422, com workflow_invalid e os mesmos errors da validação, ou com workflow_not_publishable (veja o passo 1 de Validar, criar e publicar).

A validação confere em etapas. Com graph_too_large, schema_violation, node_id_invalid ou root_missing, ela não confere as saídas, os recursos da conta nem as regras de publicação. Em POST /v1/workflows/validate, um problema no canal também esconde as regras de publicação. Os erros do compilador (media_source_invalid, randomizer_options_invalid, conditional_incomplete, multichannel_mode_missing e multichannel_channels_invalid) só aparecem quando o grafo não tem nenhum outro erro de formato, de saídas ou de recursos. Eles vêm um de cada vez, do primeiro bloco com problema no caminho a partir do root. Um bloco que não se alcança a partir do root não passa por eles. Valide de novo depois de cada correção.

Os schema_violation de um bloco falam só do tipo que o type dele diz. Um type que o catálogo não tem recebe um único schema_violation, no caminho nodes.<id>.type.

O que está fora do formato do corpo, como um campo desconhecido fora do grafo, recebe 400, antes da validação (veja Erros).

O que a validação ainda não confere

Estas regras dependem do canal, e a validação não as confere:

  • No WhatsApp, os botões de buttons e de cards fora das regras do canal, um carrossel fora de 2 a 10 cards com imagem e legenda e o media_url do media fazem o passo falhar quando a automação roda.
  • Numa automação do WhatsApp, um botão com whatsapp_connection_id vira um botão de resposta comum e não leva para o outro número.
  • Numa automação do WhatsApp, uma condição follower_status é sempre falsa, com is_follower ou com not_follower.
  • No Instagram, os textos acima de 1000 caracteres são cortados.

Também não são conferidos: se a conexão do canal está conectada (uma conexão desconectada, mas não removida, passa na validação e na publicação, e a automação não responde enquanto ela estiver desconectada), os mínimos de 1 caractere dos textos, as variáveis, a automação de um start (só um aviso), as conexões de um multichannel e a planilha de um google-sheets. Confira esses pontos antes de publicar.

Pastas

As pastas organizam as automações da conta, como no console. Uma pasta pode ficar dentro de outra, e duas pastas podem ter o mesmo nome. Ler as pastas pede workflows:read; criar, alterar e excluir, workflows:write.

curl https://api.gaio.social/v1/folders \
  -H "Authorization: Bearer $GAIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d '{"name": "Black Friday"}'

A resposta, 201, é a pasta:

{
  "id": "…",
  "type": "workflow",
  "name": "Black Friday",
  "parent_id": null,
  "created_at": "…",
  "updated_at": "…"
}
  • Nome: de 1 a 128 caracteres, sem os espaços das pontas. Um nome em branco recebe 422, com invalid_name.
  • Pôr uma automação numa pasta: envie folder_id ao criar a automação ou num PATCH (com null, ela sai da pasta). Mudar de pasta muda a revision, mas não o que está rodando: uma automação publicada continua sem alterações para publicar. GET /v1/workflows?folder_id=… lista as automações que estão direto naquela pasta, sem as das subpastas.
  • Subpastas: parent_id é a pasta de cima, ou null fora de pastas. GET /v1/folders lista as pastas de todos os níveis, das mais antigas para as mais novas.
  • Renomear e mover: PATCH /v1/folders/{folderId} com name, parent_id ou os dois. As subpastas e as automações vão junto. Uma pasta não vai para dentro dela mesma nem de uma subpasta dela (invalid_parent).
  • Excluir: DELETE /v1/folders/{folderId} exclui a pasta e as subpastas, como no console. As automações delas não são excluídas: ficam fora de pastas, e o que está rodando não muda.
  • Pasta que não existe: um folder_id (na automação) ou um parent_id (na pasta) que não é uma pasta de automações da conta recebe 422, com folder_not_found e o campo em context.field.
  • type é o que a pasta guarda: workflow, o padrão em POST /v1/folders e no filtro type de GET /v1/folders. Hoje é o único valor, mas trate um valor que você não conhece: outros tipos de pasta podem chegar.

Listas de respostas

Uma lista de respostas é o que uma automação do Instagram publica embaixo de cada comentário que a inicia: uma das respostas da lista, sorteada a cada comentário, com as variáveis do contato e da conta preenchidas ({contact.first_name}, a chave de um campo personalizado). Só os comentários num post ou reel (comment) recebem a resposta; um comentário numa live, não. Quando a automação envia mensagem, a resposta pública sai depois que o Instagram aceita a primeira mensagem; se ela for recusada, ou se a execução seguir um caminho sem mensagem, o comentário fica sem resposta pública.

A automação usa a lista pelo replies_list_id do gatilho. Só as listas da própria conta servem: uma lista modelo de outra conta não pode ser usada no gatilho. Ler as listas pede workflows:read; criar, alterar e excluir, workflows:write.

curl https://api.gaio.social/v1/replies-lists \
  -H "Authorization: Bearer $GAIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d '{"name": "Cupom no direct", "replies": ["Te mandei no direct, {contact.first_name}!", "Confere o seu direct 😉"]}'

A resposta, 201, é a lista, com id, name, replies, created_at e updated_at.

  • Nome: de 1 a 128 caracteres, sem os espaços das pontas (em branco: invalid_name). Único entre as listas da conta, sem diferenciar maiúsculas de minúsculas (replies_list_already_exists, com o id da lista que já tem o nome). Uma lista pode trocar o próprio nome só nas maiúsculas.
  • Respostas: de 1 a 250, cada uma de 1 a 512 caracteres, guardadas como foram escritas e nenhuma em branco (invalid_reply, com a posição em index).
  • Alterar: PATCH /v1/replies-lists/{repliesListId} com name, replies ou os dois; replies substitui todas as respostas. As respostas novas valem na hora: as automações publicadas que usam a lista passam a publicá-las no próximo comentário, sem publicar a automação de novo.
  • Listar: GET /v1/replies-lists traz as listas com reply_count, sem as respostas. Leia uma lista para ter as respostas.
  • Excluir: DELETE /v1/replies-lists/{repliesListId} recusa uma lista que uma automação da conta usa, em rascunho ou publicada (replies_list_in_use, com até 50 automações em workflow_ids e o total em workflow_count). Os modelos e as automações excluídas não contam. Tire a lista do gatilho delas antes.
  • Motor indisponível: se o motor de automações não responder, nada é salvo e a resposta é 503, com service_unavailable. Tente de novo.