LIVE
← API, MCP e A2A

Programadores · A2A

De agente para agente (A2A)

O A2A (Agent2Agent) permite que um agente de IA chame outro agente com pedidos tipados. O A2A de Dados Foral dá aos seus agentes a API de Dados Foral como competências A2A: os mesmos dados, a mesma chave e a mesma quota da API REST, sem modelo de linguagem. Os agentes de empresas no Foral também falam A2A (última secção).

1. A2A de Dados Foral

O A2A de Dados Foral é um agente A2A 1.0 sobre a API de Dados Foral. O seu agente envia um pedido tipado — uma competência e a respetiva entrada — e recebe logo os dados: contratação pública (Portal BASE), sinais municipais, concelhos, os seus alertas e o catálogo de indicadores. As respostas são leituras determinísticas: sem modelo de linguagem, sem tarefa para acompanhar.

É para contas Premium e para os respetivos agentes. São os mesmos dados, a mesma chave e a mesma quota da API REST e do MCP de Dados.

O quêValor
Cartão do agentehttps://data.foral.ai/.well-known/agent-card.json
Endpoint (JSON-RPC 2.0)https://data.foral.ai/a2a
ProtocoloA2A 1.0 — envie A2A-Version: 1.0 em todos os pedidos
AutenticaçãoA chave da API de Dados (sk_live_…) ou um token de acesso OAuth

2. Ler o cartão do agente

O cartão é público: não precisa de chave. Com A2A-Version: 1.0 (cabeçalho ou ?A2A-Version=1.0) recebe o cartão A2A 1.0. Sem ele, recebe o antigo cartão de compatibilidade 0.3.

O cartão lista todas as competências. O JSON Schema da entrada de cada competência está na extensão Foral do cartão (capabilities.extensions[0].params.inputSchemas).

Ler o cartão

curl -s https://data.foral.ai/.well-known/agent-card.json \
  -H "A2A-Version: 1.0"

3. Autenticação

Envie Authorization: Bearer sk_live_… — a mesma chave da API REST. Crie-a na página Chaves de API. Guarde-a no seu servidor — nunca numa página de browser.

Um token de acesso OAuth também funciona: o token que um cliente obtém para o MCP de Dados (resource=https://data.foral.ai/mcp) também é aceite em /a2a. O cartão indica o servidor de autorização. Pode desligar uma aplicação a qualquer momento em Aplicações ligadas.

4. Chamar uma competência

O SendMessage recebe uma mensagem com exatamente UMA parte de dados: {"skill": "<id>", "input": {…}}, com mediaType: application/json. Partes de texto, ficheiro ou URL são recusadas com -32005.

A resposta é uma message com uma parte de dados: o mesmo JSON do endpoint REST da competência. Uma lista (as duas pesquisas) vem como {"items": [...]}. Não é criada nenhuma tarefa.

O messageId é seu (1 a 128 caracteres). Não é deduplicado: cada competência é uma leitura, e cada chamada conta como um pedido, como um GET repetido.

procurement_lookup_municipalities

curl -s https://data.foral.ai/a2a \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "A2A-Version: 1.0" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "SendMessage",
    "params": {"message": {
      "messageId": "msg-0001", "role": "ROLE_USER",
      "parts": [{"data": {"skill": "procurement_lookup_municipalities",
                          "input": {"q": "Faro"}},
                 "mediaType": "application/json"}]
    }}
  }'

Resposta

{
  "jsonrpc": "2.0", "id": 1,
  "result": {"message": {
    "role": "ROLE_AGENT", "messageId": "msg_…",
    "parts": [{"data": {"items": [
      {"municipality": "<code>", "slug": "faro", "name": "Faro",
       "district_name": "Faro", "match": "exact", …}
    ]}, "mediaType": "application/json"}],
    "metadata": {"skill": "procurement_lookup_municipalities",
                 "rest": "GET /v1/procurement/lookup/municipalities"}
  }}
}

5. As suas competências (cartão alargado)

O GetExtendedAgentCard devolve o cartão reduzido às competências que a sua chave pode chamar: os domínios de dados do seu plano e os âmbitos da chave. Uma competência fora deles é recusada com -32004 e o erro indica o motivo (DOMAIN_NOT_IN_PLAN ou INSUFFICIENT_SCOPE).

Cartão alargado

curl -s https://data.foral.ai/a2a \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "A2A-Version: 1.0" \
  -d '{"jsonrpc": "2.0", "id": 2, "method": "GetExtendedAgentCard"}'

6. Competências

As competências são as ferramentas do MCP de Dados, com os mesmos nomes e a mesma entrada. Comece por me: devolve o plano, os domínios, os âmbitos e os limites.

CompetênciaDomínio de dados
me—
municipalities_searchindicators
municipalities_getindicators
indicators_listindicators
indicators_seriesindicators
indicators_snapshotindicators
signals_listsignals
signals_getsignals
signal_cases_listsignals
procurement_contractsprocurement
procurement_aggregateprocurement
procurement_entitiesprocurement
procurement_entity_profileprocurement
procurement_lookup_municipalitiesprocurement
procurement_lookup_entitiesprocurement
procurement_announcementsprocurement
procurement_amendmentsprocurement
alert_rulesalerts
alert_matchesalerts

7. Python

Um cliente JSON-RPC simples com httpx: encontra o código de um concelho pelo nome e depois agrega os respetivos contratos por ano.

foral_data_a2a.py

# pip install httpx
import uuid

import httpx

URL = "https://data.foral.ai/a2a"
HEADERS = {
    "Authorization": "Bearer sk_live_...",
    "A2A-Version": "1.0",
}


def skill(name: str, data: dict) -> dict:
    body = {"jsonrpc": "2.0", "id": str(uuid.uuid4()), "method": "SendMessage",
            "params": {"message": {
                "messageId": str(uuid.uuid4()), "role": "ROLE_USER",
                "parts": [{"data": {"skill": name, "input": data},
                           "mediaType": "application/json"}],
            }}}
    reply = httpx.post(URL, json=body, headers=HEADERS, timeout=60).json()
    if "error" in reply:
        raise RuntimeError(reply["error"])
    return reply["result"]["message"]["parts"][0]["data"]


faro = skill("procurement_lookup_municipalities", {"q": "Faro"})["items"][0]
totals = skill("procurement_aggregate", {"executed_in": [faro["municipality"]],
                                         "from": "2025-01-01", "group_by": ["year"]})
print(totals)

8. Limites e quota

Os limites são os da API REST. Cada pedido HTTP conta para o limite por minuto. Cada SendMessage a uma competência de dados conta como um pedido na quota mensal; me, o cartão e o GetExtendedAgentCard não gastam quota. Uma resposta 429 traz Retry-After (segundos).

9. Resolução de problemas

Uma recusa dos dados (quota, plano, âmbito, não encontrado, filtro inválido) é um erro JSON-RPC. O respetivo data traz um google.rpc.ErrorInfo (o reason é o código de erro da API REST) e o documento de erro completo da API REST.

RespostaO que significa
401Chave ou token em falta, errado, expirado ou revogado.
403O pedido veio de uma página de browser noutro site (cabeçalho Origin), ou o seu plano não inclui acesso à API de Dados (api-access-required).
-32009Falta o cabeçalho A2A-Version: 1.0 (uma versão vazia significa 0.3).
-32005Uma parte de texto, ficheiro ou URL. Envie uma parte de dados {skill, input}.
-32602Competência desconhecida ou entrada inválida — o erro indica o campo. Também um filtro ou cursor inválido (VALIDATION_ERROR, INVALID_CURSOR).
-32004Recusado: QUOTA_EXCEEDED (quota mensal esgotada), DOMAIN_NOT_IN_PLAN, INSUFFICIENT_SCOPE, NOT_FOUND, SERVICE_UNAVAILABLE. Também o SendStreamingMessage (sem fluxo).
-32001Tarefa não encontrada. Este agente nunca cria tarefas; o ListTasks está sempre vazio.
429Limite de pedidos. Espere Retry-After segundos.

10. Agentes de empresas e Mercado

O agente Foral de uma empresa também pode ser publicado no canal A2A. O agente de outra empresa fala então com ele através de pedidos tipados: verificar uma área de serviço, avaliar um projeto, pesquisar uma entidade adjudicante.

Cartão: https://agents.foral.ai/<slug>/.well-known/agent-card.json. Endpoint: https://agents.foral.ai/<slug>/a2a. <slug> é o identificador da publicação, visível no estúdio da empresa. As regras do A2A-Version: 1.0 e das partes de dados são as mesmas de cima.

Autenticação: uma credencial do agente (sk_live_…) ligada a UMA publicação e ao canal A2A. Um administrador da empresa cria-a no estúdio (passo Publicar). Veja Agentes Foral.

Compradores no Mercado: uma credencial licenciada chega às competências de leitura do agente. O seu cartão é o autenticado (GetExtendedAgentCard).

Limites: 60 pedidos/minuto por credencial e 200/minuto por publicação (valores por omissão), partilhados com os outros canais de máquina do agente.

Ler o cartão de uma empresa

curl -s https://agents.foral.ai/<slug>/.well-known/agent-card.json \
  -H "A2A-Version: 1.0"

assess_project_fit → task

curl -s https://agents.foral.ai/<slug>/a2a \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "A2A-Version: 1.0" \
  -d '{
    "jsonrpc": "2.0", "id": 2, "method": "SendMessage",
    "params": {"message": {
      "messageId": "msg-0002", "role": "ROLE_USER",
      "parts": [{"data": {"skill": "assess_project_fit",
                          "input": {"project_type": "moradia", "municipality": "Lagos",
                                    "area_m2": 180}},
                 "mediaType": "application/json"}]
    }}
  }'

Ler a tarefa

curl -s https://agents.foral.ai/<slug>/a2a \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "A2A-Version: 1.0" \
  -d '{"jsonrpc": "2.0", "id": 3, "method": "GetTask", "params": {"id": "<task id>"}}'
CompetênciaO que fazResposta
check_service_areaDiz se um concelho está dentro da área de serviço declarada.message
assess_project_fitFactos tipados do projeto; devolve um veredicto tipado com motivos.task
procurement_research_buyerUma entidade adjudicante e um período: perfil e contratos, com citações.task
procurement_compare_contractsConcelhos numa dimensão e num ano, ou dois NIF lado a lado.task
procurement_track_opportunitiesAnúncios abertos por distrito e prefixo CPV, os que fecham primeiro à frente.task
prepare_action / commit_actionUma ação com consequências: primeiro uma proposta, depois uma confirmação separada.task
get_artifactUma síntese já publicada deste agente, como referência.message
Num agente de empresa, uma competência que responde com uma task lê-se com GetTask até o estado ser final. O cartão de cada publicação é a fonte de verdade: só as competências que lista podem ser chamadas. Um 404 significa um <slug> errado, uma publicação fora de produção ou uma credencial de outra publicação.