Expor ações
Um app que expõe ações descreve, em um único documento, o que ele sabe fazer: cada ação tem um nome, uma permissão exigida, um nível de autenticação e uma operação HTTP. Com esse manifesto de ações, três consumidores passam a usar o seu app sem código específico:
- o Workflow oferece cada ação como um nó de automação (e cada gatilho como um evento);
- o Iugu for AI oferece cada ação como uma ferramenta para assistentes de IA (
<tag>__<nome>), chamada em nome da pessoa que conversa com o assistente; - a CLI iugu lista e chama as ações (
iugu app actions list|call) durante o desenvolvimento, e chama ações de apps provedores como a pessoa comiugu call <app> <ação>.
Pré-requisitos
- Na aba Privilégios, marque Actions Provider e informe a URL da API de Serviço (uma URL base, por exemplo
https://acme.example/api). - Na aba Permissões, declare como ações implementadas todas as permissões que o manifesto vai citar (
invoice.create,invoice.read…). O Console só aceita uma ação cujoauthorization.actionseja uma das ações implementadas do app. - Sirva o manifesto em
GET {URL da API de Serviço}/actions— público (nada secreto dentro),Content-Type: application/json, de preferência comCache-Control: max-age=300eETag.
O Console lê o manifesto ao salvar a URL da API de Serviço, os privilégios ou as ações implementadas, e o mantém em cache respeitando ETag/Cache-Control. Limites: 256 KB, 4 s, sem redirecionamentos para outra origem.
O manifesto (iugu.actions/v1)
{
"spec": "iugu.actions/v1",
"app": {
"client_id": "6cMyiqcbF1bDiVz0SfULTk",
"tag": "acme-invoices",
"name": "Acme Invoices",
"service_api_url": "https://acme.example/api",
"docs_url": "https://acme.example/docs"
},
"actions": [
{
"name": "create_invoice",
"title": "Emitir fatura",
"label": "Emite uma fatura em aberto para um cliente da área de trabalho",
"description": "Emite uma fatura em aberto. Valor em centavos (BRL). Exige um segundo fator recente (config).",
"authorization": { "action": "acme-invoices:invoice.create", "acr": "config" },
"url": "https://acme.example/api/invoices",
"method": "POST",
"annotations": { "readOnlyHint": false, "destructiveHint": true, "idempotentHint": false, "openWorldHint": false },
"parameters": [
{ "name": "customer", "type": "string", "required": true, "direction": "ParamInput", "source": "body", "description": "Nome do cliente", "label": "Cliente" },
{ "name": "amount_cents", "type": "integer", "required": true, "direction": "ParamInput", "source": "body", "description": "Valor em centavos (BRL)", "label": "Valor", "format": "money_cents" },
{ "name": "description", "type": "textarea", "required": false, "direction": "ParamInput", "source": "body", "description": "Texto livre", "label": "Descrição" },
{ "name": "id", "type": "string", "required": true, "direction": "ParamOutput", "description": "Id da fatura" },
{ "name": "status", "type": "select", "required": true, "direction": "ParamOutput", "description": "open|paid",
"options": [ { "value": "open", "label": "Em aberto" }, { "value": "paid", "label": "Paga" } ] }
]
},
{
"name": "list_invoices",
"title": "Listar faturas",
"label": "Faturas da área de trabalho, mais recentes primeiro",
"authorization": { "action": "acme-invoices:invoice.read" },
"url": "https://acme.example/api/invoices",
"method": "GET",
"parameters": [
{ "name": "data", "type": "json", "required": true, "direction": "ParamOutput", "description": "Lista de faturas" }
]
}
],
"triggers": [
{
"name": "invoice_paid",
"title": "Fatura paga",
"label": "Dispara quando uma fatura é paga",
"authorization": { "action": "acme-invoices:invoice.read" },
"parameters": [
{ "name": "id", "type": "string", "required": true, "direction": "ParamOutput", "description": "Id da fatura" },
{ "name": "amount_cents", "type": "integer", "required": true, "direction": "ParamOutput", "description": "Valor" }
]
}
]
}
No nível mais alto: spec (obrigatório, iugu.actions/v1), app (recomendado), actions[] e triggers[] (qualquer um pode estar vazio). Chaves desconhecidas são ignoradas pelos consumidores; dentro da v1 só há mudanças aditivas.
Ação
| Campo | Obrigatório | Significado |
|---|---|---|
name |
sim | Identificador estável em snake_case, único no manifesto. É o que o Workflow, o Iugu for AI (acme_invoices__create_invoice) e a CLI usam. |
title |
sim | Título curto para pessoas. |
label |
sim | Uma linha de descrição, exibida em listas. |
description |
não | Texto mais longo para agentes (vira a descrição da ferramenta MCP). |
icon |
não | URL absoluta (SVG/PNG). |
authorization |
sim | action (obrigatório): a permissão <tag>:<ação> que o principal precisa ter na área de trabalho — uma das ações implementadas do app. acr (opcional): informations (padrão) · config · transfers — o nível de autenticação que a pessoa precisa ter; consumidores sem pessoa (um app agindo por si) não conseguem satisfazer config/transfers. |
url |
sim | Absoluta, na mesma origem da URL da API de Serviço (qualquer outra é recusada); :nome marca parâmetros de caminho. |
method |
sim | GET · POST · PUT · PATCH · DELETE |
annotations |
não | Anotações de ferramenta MCP. Padrões: GET → readOnlyHint: true, idempotentHint: true; demais → destructiveHint: true. |
parameters |
sim | Entradas e saídas (abaixo). |
input_schema, output_schema |
não | JSON Schema que substitui o derivado de parameters (para corpos que não são objetos planos). |
confirmation |
não | Só em ações elevadas: { "url": "https://…/preview" } — uma leitura que o Console faz para mostrar à pessoa o que esta chamada vai fazer (veja Confirmação). |
widget |
não | Transforma uma ação de leitura em um cartão do painel do Console (veja Widgets). |
Parâmetro
| Campo | Obrigatório | Significado |
|---|---|---|
name |
sim | snake_case; para source: path, corresponde ao :nome na url. |
type |
sim | string · textarea · number · integer · boolean · datetime · json · select · color · lookup |
required |
sim | |
direction |
sim | ParamInput (entrada) · ParamOutput (saída) |
description |
sim | |
source |
entradas | query · path · body (padrão: body fora de GET, query em GET) |
default |
não | Valor padrão. |
options |
select |
[{ "value", "label" }] → enum no JSON Schema. |
lookup |
lookup |
{ "endpoint", "method", "params" } — opções dinâmicas, consultadas com um token de app. |
hidden, disabled, alias |
não | Dicas para o editor do Workflow; parâmetros hidden não viram entradas de ferramenta. |
label, format, items |
não | Dicas de exibição para a confirmação e a Auditoria (abaixo). |
O JSON Schema das ferramentas é derivado de parameters (string/textarea/color/lookup → string; number; integer; boolean; datetime → string com format: date-time; json → object/array; select → string com enum; required: true → required[]).
Gatilho
name (o valor de event que o app envia ao Workflow), title, label, icon, description, authorization.action e parameters (todos ParamOutput). O Iugu for AI ignora gatilhos na v1; o Workflow os usa como eventos.
Atender uma chamada
Toda chamada a uma ação chega ao seu backend assim:
Authorization |
Bearer <JWT> com aud contendo Iugu.Platform.<id do seu app> |
Workspace |
id curto da área de trabalho em que a ação é executada |
Content-Type |
application/json quando há corpo; as entradas vão para query, path ou body conforme source |
O que o seu backend faz, nesta ordem:
- Valida o JWT: JWKS em
https://identity.iugu.com/.well-known/jwks.json,iss=https://identity.iugu.com/,aud,typ=at+JWT,exp. - Verifica a permissão do
subcomPOST /verify(workspace_id= o cabeçalhoWorkspace,principals: [token.sub],actions: [authorization.action]). Osubé a pessoa (user:…) — quando a chamada vem do Iugu for AI ou do Workflow em nome dela — ou um app agindo por si (app:…). Oclient_iddo token é só atribuição (iugu-for-ai, um workflow): registre-o, não autorize por ele. - Aplica o nível para
subdo tipouser:: se a claimacrdo token for menor que oacrda ação, responda401comWWW-Authenticate: Bearer error="insufficient_user_authentication", acr_values="urn:iugu:grant_scopes:config"(RFC 9470). O Iugu for AI mostra à pessoa um cartão de confirmação e repete a chamada com um token elevado. - Responde um objeto JSON cujas chaves de primeiro nível são os nomes dos parâmetros
ParamOutput. Erros no formato RFC 9457 (error,error_description).
Warning
Tokens do Iugu for AI para o seu app duram 2 minutos e trazem o sub da pessoa. Não use /userinfo para autorizar uma chamada de ponte: verifique o sub com /verify.
Confirmação de ações elevadas
Quando uma ação exige config ou transfers, o Iugu for AI não a executa direto: a pessoa recebe um link para uma página do Console que mostra o app, a ação, os argumentos exatos, a área de trabalho e quem pediu — e confirma com o segundo fator. A confirmação vale para uma repetição com os mesmos argumentos. Duas coisas melhoram o que a pessoa vê:
Dicas de exibição nos parâmetros de entrada: label (≤ 40 caracteres, no lugar da chave), format (money_cents, money, date, datetime, percent, count, text) e, para um json que é uma lista de registros, items com as colunas na ordem desejada:
{ "name": "invoices", "type": "json", "required": true, "direction": "ParamInput", "source": "body", "label": "Faturas",
"items": { "customer": { "label": "Cliente" }, "amount_cents": { "label": "Valor", "format": "money_cents" },
"due_date": { "label": "Vencimento", "format": "date" } } }
O Console faz toda a renderização e os totais (uma coluna money_cents ganha soma automaticamente); uma dica pode rotular um valor, nunca escondê-lo.
Prévia com confirmation.url: para ações cujos argumentos são só ids (publicar a versão X de um plano), aponte uma leitura GET na mesma origem, com os mesmos :placeholders de caminho da ação. O Console a chama como a pessoa, ao montar a página, e exibe o retorno em “O que esta chamada vai fazer”:
{ "summary": "Publica a versão 3 do plano Pro com 4 preços",
"items": [ { "label": "Plano", "value": "Pro" }, { "label": "Vigência", "value": "01/10/2026" } ],
"table": { "columns": [ { "key": "event", "label": "Evento" }, { "key": "price_cents", "label": "Preço", "format": "money_cents" } ],
"rows": [ { "event": "invoice.created", "price_cents": 150 } ] } }
Todas as chaves são opcionais; se a prévia falhar, a página mostra só os argumentos. É uma prévia, não um contrato: o seu backend continua decidindo na hora da chamada.
Widgets
Uma ação de leitura com um bloco widget vira um cartão no painel da área de trabalho no Console, visível para os membros que têm a authorization.action — sem iframe, sem HTML do seu app dentro do Console. Um widget não é oferecido como ferramenta ao Iugu for AI (a resposta é um documento de exibição, o título é o do cartão): mantenha ao lado dele a leitura simples que o assistente vai chamar.
{ "name": "revenue_widget", "title": "Receita", "label": "Faturado, recebido e em aberto nas últimas competências",
"url": "https://acme.example/api/widgets/revenue", "method": "GET",
"authorization": { "action": "acme-invoices:reports.read" },
"widget": { "title": "Receita", "size": { "w": 2, "h": 1 }, "refresh": 300, "link": "https://acme.example/reports" },
"parameters": [ { "name": "stat", "type": "json", "required": false, "direction": "ParamOutput", "description": "Indicador principal" } ] }
Requisitos: method GET, acr padrão, nenhum parâmetro de entrada (a área de trabalho vem no cabeçalho), size.w 1–4, size.h 1–3, refresh ≥ 60 s, link na mesma origem. O Console chama a ação como a pessoa (cache de 60 s por widget, pessoa e área de trabalho) e renderiza um documento de exibição com os componentes dele:
| Chave | Forma | Aparece como |
|---|---|---|
summary |
texto | uma linha sob o título |
stat |
{ "value": "R$ 1.509,70", "label": "Faturado em setembro", "delta": "+12%", "tone": "positive" } |
o número em destaque |
items |
[{ "label", "value" }] |
lista rótulo/valor |
table |
{ "columns": [{ "key", "label", "format"? }], "rows": [ … ] } |
tabela com formatos e totais; células status ({ "label", "tone" }) viram etiquetas e link ({ "label", "url" }) viram links |
series |
{ "label"?, "format"?, "points": [{ "x": "2026-08", "y": 1509.7 }] } (≤ 60 pontos) |
gráfico de linha |
updated_at |
ISO 8601 | “atualizado há 2 min” |
Uma resposta não-2xx, um tempo maior que 5 s ou um corpo inválido aparecem como “‹app› não respondeu”; um 403 do seu backend aparece como “sem permissão”.
Conferir o que o Console aceitou
O Console valida cada ação e exclui as que violam alguma regra (permissão fora das ações implementadas, url em outra origem, dica de exibição inválida, widget com entrada…), mantendo as demais. Para ver o resultado e cada problema:
iugu app actions list --json # o manifesto como o Console o leu, com `problems`
iugu app actions list --refresh --json # força uma nova leitura
iugu app actions call list_invoices --json # chama uma ação com o token do próprio app (auxílio de teste)
Pela Lifecycle API: GET https://api.console.iugu.com/v1/apps/{id}/actions[?refresh=true]. Depois de publicado, o app aparece nos assistentes conectados ao Iugu for AI para quem tem a permissão de cada ação — e cada chamada, inclusive de leitura, fica registrada na Auditoria da área de trabalho com os argumentos enviados.
Info
A lista de ferramentas de um assistente é montada quando a conexão começa. Um manifesto que passa a ser aceito (ou muda) durante uma sessão só aparece como ferramenta na próxima conexão — reconecte o assistente (no Claude Desktop, desative e reative o conector). A CLI (iugu call, iugu app actions) lê o manifesto na hora.