JSON-RPC 2.0 · ES256 signed · multi-turn · zero-deps
Integreer de A2A v1.0 agent.
Alles wat u nodig heeft om de Co·Legal Public Assistant aan te roepen vanuit uw eigen agent,
script of integratie. Volledige Linux Foundation A2A v1.0 compliance: JSON-RPC 2.0 over
HTTPS, signed AgentCard (ES256 detached JWS over JCS), multi-turn via taskId, google.rpc.Status
error details.
Een spec-conforme A2A client probeert eerst /.well-known/agent-card.json.
Onze card is ES256-gesigneerd; verifieer de signature tegen de publieke sleutel uit JWKS
voordat u actie onderneemt op basis van de inhoud.
/.well-known/agent.json — alias voor oudere probes
/.well-known/jwks.json — publieke ES256-sleutel
/extendedAgentCard — authenticated card met paid skills (401 zonder x-api-key)
/icon.svg — agent-icoon (verwezen via iconUrl in card)
/health — liveness probe (200 OK)
Endpoint
POST /a2a/jsonrpc
Eén endpoint voor alle methodes — JSON-RPC 2.0 envelope. Geen URL-routing per method,
geen Bearer/OAuth (alleen optionele x-api-key header). Returnt altijd
HTTP 200 met een JSON-RPC envelope; HTTP 429 alleen bij rate-limiting.
Methodes
Method
Wat het doet
Auth
message/send
Stuur een vraag (nieuw of voortzetting via taskId)
Optioneel
tasks/get
Haal volledige history van één task op
Optioneel (eigenaar-scoped)
tasks/list
Lijst recente tasks (gescoped op uw sleutel)
Optioneel
tasks/cancel
Flip een task naar CANCELLED
Optioneel (eigenaar-scoped)
tasks/pushNotificationConfig/*
Niet ondersteund — return -32003
—
Publieke skills
Vier free-tier skills, anoniem aanroepbaar.
Skill-IDs zijn reverse-DNS en forever-stable. Volledige JSON-Schemas voor elk komen
uit het /.well-known/agent-card.json →
capabilities.extensions[0].params[skill_id].
Trefwoord → Justel zoek-URL + statute-hints (welke codex regelt het thema). Result-pagina's zijn JS-rendered, dus we returnen de live click-through URL.
EU BTW-nummer (alle 27 lidstaten + XI Noord-Ierland) → geldigheids-status + handelsnaam + adres waar door de lidstaat blootgesteld.
ec.europa.eu/taxation_customs/vies
free / anoniem
capabilities.extendedAgentCard: true in de AgentCard advertiseert dat een
authenticated GET /extendedAgentCard met x-api-key header een
superset van skills levert. Vandaag is deze identiek aan de publieke card — placeholder
voor toekomstige paid skills (entity-graph extraction, fiscal compute, DOCX clause-draft)
die we via deze gate gaan blootstellen zonder de publieke card te wijzigen.
Twee aanroep-modi
Natuurlijke taal of directe skill-dispatch.
Beide modi gebruiken dezelfde endpoint (POST /a2a/jsonrpc) en methode
(message/send). Het verschil zit in de part-kind: een tekst-part
stuurt door naar de LLM; een data-part dispatcht direct naar de tool.
Path A · LLM-routing
Voor humans & generieke agents.
Stuur een natuurlijke vraag als kind:"text". De LLM ziet de
AgentCard.skills, beslist autonoom welke tool relevant is, roept ze aan, en
integreert het resultaat met bron-citatie in een sober antwoord.
parts: [
{ "kind": "text",
"text": "Geef de Justel-link voor art. 4.71 BW" }
]
Path B · directe dispatch
Voor peer-agents & scripts.
Stuur een kind:"data" part met {tool, args}. De server
bypast de LLM volledig, valideert de args tegen het JSON-Schema, dispatcht naar de
handler, en retourneert text + structured data. Nul LLM-cost, nul hallucinatie,
deterministisch.
Welke kies je? Path A voor verkenning / open-eind vragen / als je niet
weet welke tool je nodig hebt. Path B als je weet wat je nodig hebt,
voor latency-kritische integraties, of als je exact zeker wilt zijn over de output-shape
(geen LLM-reformulering). Beide retourneren een spec-conforme A2A v1.0 message.
Snippet
Direct te kopiëren — kies uw taal.
# PATH A — natural language → LLM autonomously calls tools
curl -X POST https://agent.co-legal.be/a2a/jsonrpc \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0", "id": 1, "method": "message/send",
"params": { "message": { "parts": [
{"kind": "text", "text": "Is BE0403170701 een geldig BTW-nummer?"}
]}}
}'
# PATH B — direct skill dispatch (no LLM)
curl -X POST https://agent.co-legal.be/a2a/jsonrpc \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0", "id": 2, "method": "message/send",
"params": { "message": { "parts": [
{"kind": "data",
"data": { "tool": "be.vies.validate",
"args": { "vat": "BE0403170701" } }}
]}}
}'
# Multi-turn — bewaar de taskId uit turn 1, stuur 'm terug
curl -X POST https://agent.co-legal.be/a2a/jsonrpc \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0", "id": 3, "method": "message/send",
"params": {
"taskId": "8c1ad2…",
"message": { "parts": [
{"kind": "text", "text": "En voor BE0888778965?"}
]}
}
}'
const AGENT = "https://agent.co-legal.be";
let taskId: string | null = null;
async function ask(text: string): Promise<string> {
const params: Record<string, unknown> = {
message: { parts: [{ kind: "text", text }] },
};
if (taskId) params.taskId = taskId;
const r = await fetch(`${AGENT}/a2a/jsonrpc`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0", id: Date.now(),
method: "message/send", params,
}),
});
const json = await r.json();
if (json.error) throw new Error(json.error.message);
taskId = json.result.taskId;
return json.result.parts[0].text;
}
// Multi-turn werkt automatisch — taskId is module-state
console.log(await ask("Wat is BTW?"));
console.log(await ask("En voor verzekeringen?"));
Multi-turn semantics
Hoe een task echt werkt.
Een task is een sessie. De eerste message/send zonder
taskId maakt 'm aan; de server stuurt een verse taskId mee in
result.taskId. Elke vervolg-call met diezelfde taskId rehydrateert de OpenAI-
context server-side via previous_response_id — u stuurt nooit de history mee.
Limieten op een task: 50 berichten totaal, 24 u idle-TTL
(na 24 u zonder activiteit verloopt de OpenAI-context, krijgt u CONTEXT_EXPIRED
en moet u een nieuwe task starten). Tasks zijn per eigenaar afgeschermd: een
anonieme taskId is gebonden aan de hashed IP-bucket; een keyed taskId aan de
x-api-key.
Een task kan in deze statussen leven: SUBMITTED → WORKING →
COMPLETED (niet-terminaal — een COMPLETED task kan een vervolg-message krijgen
en wordt dan weer WORKING) → FAILED of CANCELLED (terminaal).
Authenticatie
Optionele API-sleutel voor hogere quota.
Anonieme calls hebben een tight rate limit (60 vragen/uur per IP, burst 100)
— bewust krap zodat de agent niet als gratis API-rate-onbeperkt cron-target dient.
Voor productie-integraties geven we API-sleutels uit. Mail
ops@co-legal.be met uw use-case; we minten een sleutel
en delen 'm via een beveiligd kanaal.
Anoniem
geen header
60 vragen/uur per IP · burst 100
X-RateLimit-Tier: anon
Met sleutel
x-api-key: sk_agent_<48hex>
200 vragen/uur per sleutel · burst 100
X-RateLimit-Tier: keyed
Elke 200-respons bevat:
X-RateLimit-Tier — anon of keyed
X-RateLimit-Remaining — resterende tokens in uw bucket
X-RateLimit-Capacity — totale bucket-grootte (= burst)
Bij 429: Retry-After header (seconden) + JSON-RPC error -32029.
Errors
JSON-RPC + google.rpc.Status details.
Alle errors volgen de A2A v1.0 conventie: standaard JSON-RPC error-code plus een
google.rpc.Status detail-blok in error.data dat een aggregatie-aware monitor
kan parsen.
Verifieer de raw-r||s signature tegen de public key uit JWKS
Onze eigen Python-implementatie (~80 regels) is open-source in
backend/services/a2a_signer.py op
(implementatie op aanvraag, ops@co-legal.be) — vrij te kopiëren voor uw eigen verifier.