Ligações a serviços externos
Esta página não é sobre o mesmo que Ligações. Lá,
uma ligação faz uma linha de uma tabela apontar para linhas de outra
(link_rows, create_link_column). Aqui, uma ligação a serviço externo é o
registo, numa organização, de como as automações falam com um sistema de fora:
o Metabase, ou qualquer serviço HTTP. É a mesma coisa que o diálogo «Nova
ligação» das automações cria na aplicação.
Os argumentos de cada ferramenta estão em Ferramentas MCP, gerados do código. Uma automação usa a ligação pelo nome (ver Automações (WorkflowV2)).
O que uma ligação guarda
- Uma linha com o
name, olabel, otype(httppara os dois modelos suportados) e oconfig: o endereço base, o tipo de autenticação e os endpoints conhecidos. - Os segredos (a chave de API, o token, a palavra-passe) vivem à parte, num
cofre, por slot (
apiKey,bearer,password). Oconfignunca os contém.
Os segredos nunca se leem de volta, nem pela API nem pelo MCP. Quem os
escreveu não os consegue ver outra vez, e pela API não há como substituir um:
regista uma ligação nova ou muda-o na aplicação. A resposta de create_connection diz só os nomes dos slots escritos
(secret_slots); list_connections e get_connection devolvem a lista de slots
vazia.
Permissões
Criar e testar exigem a permissão de gerir ligações (connection.manage) na
organização, a mesma que a aplicação pede, e, numa chave, os scopes write e
structure (o structure é aditivo ao write). Na prática:
- Um admin da organização passa.
- Uma chave de API não é admin, mas passa se tem o scope
structuree o papel editor ou acima (migração 423). Como só um admin cunha chaves, é uma delegação explícita: o admin que lhe deustructuredeu-lhe também esta permissão. - Chaves sem
structure, ou dos papéisusereview, recebem 403permission_denied. - Uma chave confinada a um workspace também recebe 403 ao criar e ao testar: a ligação é um recurso da organização, não do workspace. A lista fica presa ao workspace da chave.
Ler (list_connections, get_connection) só pede uma chave com o scope read:
vê o que a pertença à organização deixa ver.
Criar: create_connection
| Argumento | Significado |
|---|---|
organization_id | A organização dona da ligação. Obrigatório. |
name | O nome, de 1 a 80 caracteres. É por ele que as automações a referem. |
template | metabase ou http. Qualquer outro dá 422 unsupported_template. |
label | O rótulo visível. Por omissão, o name. |
workspace_id | Opcional. Restringe a ligação a um workspace da organização. |
fields | Só com metabase: {baseUrl, apiKey}. |
config | Só com http: o config HTTP cru (baseUrl, auth, endpoints). Até 64 KB. |
secrets | Só com http: {slot: valor}, cada valor de 1 a 4096 caracteres. |
idempotency_key | Opcional. Torna a repetição segura. |
Metabase
Dás o endereço e a chave de API; o servidor constrói o resto exatamente como o
template da aplicação: autenticação por cabeçalho x-api-key e um endpoint
current_user (GET /api/user/current). O baseUrl é aparado e perde as
barras finais.
{
"name": "create_connection",
"arguments": {
"organization_id": "3d6e8a1b-92c4-4f07-b5a3-7c1e0d9f2468",
"name": "metabase-dourum",
"label": "Metabase Dourum",
"template": "metabase",
"fields": {
"baseUrl": "https://metabase.exemplo.pt/",
"apiKey": "mb_xxxxxxxxxxxxxxxx"
}
}
}
A resposta traz o registo criado e o nome do slot escrito, nunca o valor:
{
"id": "b81f4c27-0e5a-4d93-a6c8-1f3e7b9d0a52",
"type": "http",
"name": "metabase-dourum",
"secret_slots": ["apiKey"],
"connection": {
"id": "b81f4c27-0e5a-4d93-a6c8-1f3e7b9d0a52",
"organization_id": "3d6e8a1b-92c4-4f07-b5a3-7c1e0d9f2468",
"workspace_id": null,
"type": "http",
"name": "metabase-dourum",
"label": "Metabase Dourum",
"config": {
"baseUrl": "https://metabase.exemplo.pt",
"auth": { "type": "apiKey", "headerName": "x-api-key" },
"endpoints": [
{ "name": "current_user", "kind": "data", "method": "GET", "path": "/api/user/current" }
],
"template": "metabase"
}
}
}
(A resposta real inclui também created_by, created_at e updated_at.)
HTTP genérico
Para qualquer outro serviço dás tu o config e os segredos.
config.baseUrlé obrigatório e começa porhttp://ouhttps://.config.auth.typeé um denone(omissão),apiKey,bearer,basicouendpointLogin.config.endpointsé uma lista de{name, method, path}; okindédata(omissão) ouauth. O resto doconfigpassa tal como o enviaste.- Cada tipo de autenticação lê um slot do cofre, e o servidor recusa a criação se
faltar:
apiKeypedesecrets.apiKey,bearerpedesecrets.bearer,basicpedesecrets.password.noneeendpointLoginnão pedem nenhum.
{
"name": "create_connection",
"arguments": {
"organization_id": "3d6e8a1b-92c4-4f07-b5a3-7c1e0d9f2468",
"name": "faturacao",
"template": "http",
"config": {
"baseUrl": "https://api.faturacao.exemplo.pt",
"auth": { "type": "bearer" },
"endpoints": [
{ "name": "estado", "method": "GET", "path": "/v1/estado" }
]
},
"secrets": { "bearer": "tok_xxxxxxxxxxxxxxxx" }
}
}
Equivalente REST
POST /v1/connections
Authorization: Bearer outdo_sk_...
Content-Type: application/json
{
"organization_id": "3d6e8a1b-92c4-4f07-b5a3-7c1e0d9f2468",
"name": "metabase-dourum",
"template": "metabase",
"fields": { "baseUrl": "https://metabase.exemplo.pt", "apiKey": "mb_xxxxxxxxxxxxxxxx" }
}
Responde 201 com o registo (ConnectionOut) e secret_slots. Precisa dos scopes
write e structure e da permissão de gerir ligações na organização.
Como o servidor cria
- Confirma a permissão de gerir ligações antes de escrever seja o que for.
- Grava a linha da ligação com a identidade da chave.
- Escreve cada segredo no cofre, por ordem.
- Se um segredo falhar, apaga a linha e responde 502
secret_store_failed; se nem apagar conseguir, responde 502secret_store_failed_connection_keptcom oconnection_idque ficou, para o limpares à mão. Nunca fica uma ligação a meio sem o dizer.
Listar e ler
list_connections (GET /v1/connections) devolve as ligações visíveis, por
ordem do nome. Filtra por organization_id e workspace_id; sem filtros lista
tudo o que vês. Na REST a página traz next_cursor e aceita cursor e limit.
{
"name": "list_connections",
"arguments": { "organization_id": "3d6e8a1b-92c4-4f07-b5a3-7c1e0d9f2468" }
}
get_connection (GET /v1/connections/{connection_id}) lê uma só, com o type,
o name e o config; 404 se não existir ou não estiver visível. Nunca traz
valores de segredos.
Testar: test_connection
Chama o serviço de verdade e diz se a ligação funciona. Pede os scopes write e
structure e a permissão de gerir ligações na organização.
| Argumento | Significado |
|---|---|
connection_id | A ligação a testar. |
endpoint_name | O endpoint a chamar. Sem ele, faz um GET ao baseUrl. |
allow_mutating | false por omissão. Um endpoint cujo método pode alterar dados (POST, PUT, PATCH, DELETE) é recusado se não for true. |
Para o Metabase, chama o endpoint current_user:
{
"name": "test_connection",
"arguments": {
"connection_id": "b81f4c27-0e5a-4d93-a6c8-1f3e7b9d0a52",
"endpoint_name": "current_user"
}
}
A resposta é {ok, status, message, body}. Um ok verdadeiro com um body que
traz um email prova que a chave é válida.
Equivalente REST
POST /v1/connections/{connection_id}:test
Authorization: Bearer outdo_sk_...
Content-Type: application/json
{ "endpoint_name": "current_user" }
Responde 200 com o veredito. O teste é feito pelo servidor da plataforma, por isso depende de ele estar configurado neste ambiente.
Arquivar e restaurar automações
Arquivar tira uma automação das listas por omissão sem a apagar; restaurar
devolve-a. Pede os scopes write e structure e a permissão de gerir a
automação, como /versions e /apply (uma chave editor com structure chega).
| Operação | REST | MCP |
|---|---|---|
| Arquivar | POST /v1/workflows-v2/{workflow_id}:archive | archive_workflow_v2 |
| Restaurar | POST /v1/workflows-v2/{workflow_id}:restore | restore_workflow_v2 |
{
"name": "archive_workflow_v2",
"arguments": { "workflow_id": "9a4e6c31-5d28-4b70-8e19-0f7a3c2d1b64" }
}
- Arquivar torna todas as versões do grupo inativas e marca-as arquivadas. É seguro repetir.
- Restaurar tira a marca de arquivo e não reativa: a automação continua inativa até uma pessoa a ativar na aplicação, como sempre.
- As duas respondem com a versão representativa da automação; 404 se o
workflow_idnão existir ou não estiver visível. GET /v1/workflows-v2aceitaarchived=true(só as arquivadas) ouarchived=false(só as que não estão).
Erros
Os erros trazem um code estável no corpo do problema, e as ferramentas MCP
devolvem o mesmo code na mensagem.
| Estado | code | Quando |
|---|---|---|
| 403 | permission_denied | Falta a permissão de gerir ligações, o scope structure, ou a chave está confinada a um workspace. |
| 404 | not_found | A organização, o workspace ou a ligação não existem ou não estão visíveis. |
| 422 | unsupported_template | O template não é metabase nem http. |
| 422 | fields_only | config ou secrets com o template metabase. |
| 422 | config_only | fields com o template http. |
| 422 | secret_required | A autenticação pede um slot que secrets não traz; detail.slot diz qual. |
| 422 | workspace_not_in_organization | O workspace_id é de outra organização. |
| 502 | secret_store_failed | Um segredo não ficou no cofre; a ligação foi removida. |
| 502 | secret_store_failed_connection_kept | Idem, e a ligação não pôde ser removida; detail.connection_id diz qual. |
| 503 | upstream_unavailable | O teste não está configurado neste ambiente, ou o servidor falhou. |
Há ainda erros de validação do corpo sem code próprio, também 422: baseUrl
em falta ou que não é http(s), apiKey vazia, auth.type desconhecido, um
config acima de 64 KB. No teste, um 429 rate_limited quer dizer que há
pedidos a mais. Um teste que corre mas dá ok: false não é um erro da chamada:
é o veredito, e a message diz o que correu mal com o serviço de fora.
O que fica fora
- Outros modelos.
mcp,smtpeai_providernão se criam por aqui (422unsupported_template); usa a aplicação. - Editar ou apagar uma ligação, e ler ou listar segredos. Pela API, para mudar um segredo regista uma ligação nova; na aplicação também se edita.
- Ativar uma automação continua a ser só na aplicação.