# Integreren — Co·Legal A2A v1.0 agent

> **Canonical HTML:** https://co-legal.be/developers
> **Last updated:** 2026-05-20

## Discovery

- **AgentCard:** `https://agent.co-legal.be/.well-known/agent-card.json` — ES256 detached JWS over JCS-canonical bytes (RFC 7515 + 8785). Protected header carries `alg`, `kid`, `jku` (no `typ:JWT`).
- **JWKS:** `https://agent.co-legal.be/.well-known/jwks.json`
- **Legacy alias:** `https://agent.co-legal.be/.well-known/agent.json` (A2A v0.x-pinned crawlers).
- **MCP discovery:** `https://agent.co-legal.be/.well-known/mcp.json` (geen MCP-transport vandaag — verwijst naar A2A-card).
- **Extended AgentCard:** `https://agent.co-legal.be/extendedAgentCard` — 401 zonder `x-api-key`, voor paid skills morgen.

## Endpoint

```
POST https://agent.co-legal.be/a2a/jsonrpc
Content-Type: application/json
```

Optionele header: `x-api-key: <key>` (verhoogt naar keyed-tier).

## Methodes (A2A v1.0 PascalCase, met v0.3-aliassen voor backwards-compat)

| v1.0 method | v0.3 alias | Wat |
|---|---|---|
| `SendMessage` | `message/send` | Single + multi-turn (via `taskId`) |
| `SendStreamingMessage` | `message/stream` | SSE — frames: `status-update` → `tool_call`/`tool_result` → `artifact-update` → `status-update`(final:true) |
| `GetTask` | `tasks/get` | Task object met history |
| `ListTasks` | `tasks/list` | Tasks van de caller (gescoped op API-key hash) |
| `CancelTask` | `tasks/cancel` | Task in `TASK_STATE_CANCELED` |

## Invocation patterns

### Path A — LLM-routing via text-part

```bash
curl -sX POST https://agent.co-legal.be/a2a/jsonrpc \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage",
       "params":{"message":{"parts":[
         {"text":"Geef de Justel-link voor art. 4.71 BW."}
       ]}}}'
```

De LLM kiest een skill, voert hem uit, en synthetiseert het antwoord. Multi-turn: stuur `params.taskId` op vervolg-calls.

### Path B — Direct skill-dispatch via data-part

```bash
curl -sX POST https://agent.co-legal.be/a2a/jsonrpc \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage",
       "params":{"message":{"parts":[
         {"data":{
           "tool":"be.legal.lookup",
           "args":{"code":"BW","article":"4.71"}
         }}
       ]}}}'
```

Synchroon, geen LLM-cost, ~50ms p50. Geschikt voor peer-agents die de skill kennen en alleen het resultaat willen.

### Python

```python
import httpx

resp = httpx.post(
    "https://agent.co-legal.be/a2a/jsonrpc",
    json={
        "jsonrpc": "2.0", "id": 1, "method": "SendMessage",
        "params": {"message": {"parts": [
            {"data": {
                "tool": "be.kbo.lookup",
                "args": {"enterprise_number": "0403.170.701"},
            }},
        ]}},
    },
    timeout=10,
)
result = resp.json()["result"]
text = next(p["text"] for p in result["parts"] if "text" in p)
data = next(p["data"] for p in result["parts"] if "data" in p)
```

### TypeScript

```ts
const r = await fetch("https://agent.co-legal.be/a2a/jsonrpc", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({
    jsonrpc: "2.0", id: 1, method: "SendMessage",
    params: {message: {parts: [{
      data: {tool: "be.vies.validate", args: {vat: "BE0403170701"}},
    }]}},
  }),
});
const {result} = await r.json();
```

## Skills (4 live + 4 roadmap)

Live:

```json
{
  "be.legal.lookup":    {"code": "BW|WVV|WIB92|VCF|WBTW|WBE|Sw|Ger.W", "article": "string"},
  "be.legal.search":    {"keyword": "string (2-120 chars)"},
  "be.kbo.lookup":      {"enterprise_number": "string (10 digits)"},
  "be.vies.validate":   {"vat": "string (country + identifier)"}
}
```

Roadmap (zie `/`-homepage): `be.entity.extract`, `be.relationship.graph`, `be.fiscal.compute`, `be.statute.cite`.

Volledige JSON-Schemas op de AgentCard via de `co-legal.be/a2a/extensions/tool-schema/v1` extension — strict mode, `additionalProperties: false`.

## Rate limits

| Tier | Per uur | Burst |
|---|---|---|
| Anoniem (per IP) | 60 | 20 |
| API-key (free) | 1000 | 100 |

Headers op elke response:

```
A2A-Version: 1.0
RateLimit-Limit: <n>          # IETF draft
RateLimit-Remaining: <n>
RateLimit-Reset: <seconds>
X-RateLimit-*: ...            # legacy
Retry-After: <seconds>        # alleen op 429
```

## Error codes

| JSON-RPC code | Meaning |
|---|---|
| `-32600` | Invalid Request envelope |
| `-32601` | Method/skill niet gevonden |
| `-32602` | Invalid params / schema-validation fout |
| `-32603` | Internal error (LLM-failure, context-expiry) |
| `-32001` | Permission denied / task not found / API-key required |
| `-32029` | Rate-limited (also returned als HTTP 429) |

Errors carry `data.reason` constants: `INVALID_ARGUMENT`, `TASK_NOT_FOUND`, `CONTEXT_EXPIRED`, `PERMISSION_DENIED`, `RATE_LIMIT_EXCEEDED`.

## A2A v1.0 enums op output

- Role: `"ROLE_USER"` / `"ROLE_AGENT"` (SCREAMING_SNAKE_CASE per v1.0 §4)
- Task state: `TASK_STATE_SUBMITTED` / `TASK_STATE_WORKING` / `TASK_STATE_COMPLETED` / `TASK_STATE_CANCELED` (single L!) / `TASK_STATE_FAILED`
- Parts: member-based discrimination — `{"text": "..."}` of `{"data": {...}}`, geen `kind`-veld

## Signature-verify recipe (Python)

```python
import json, base64, httpx
from joserfc import jws
import rfc8785

card_url = "https://agent.co-legal.be/.well-known/agent-card.json"
card = httpx.get(card_url).json()

sig_block = card["signatures"][0]
protected = json.loads(base64.urlsafe_b64decode(sig_block["protected"] + "=="))

# Fetch the publisher's JWKS via the jku URL
jwks = httpx.get(protected["jku"]).json()
public_key = next(k for k in jwks["keys"] if k["kid"] == protected["kid"])

# Reconstruct canonical body (sans signatures, with A2A v1.0 prune-defaults)
body = {k: v for k, v in card.items() if k != "signatures"}
canonical = rfc8785.canonicalize(body)

# Verify ECDSA(P-256, SHA-256)
signing_input = sig_block["protected"].encode() + b"." + base64.urlsafe_b64encode(canonical).rstrip(b"=")
ok = jws.deserialize_compact(
    signing_input + b"." + sig_block["signature"].encode(),
    public_key,
)
assert ok, "signature invalid — do not trust this card"
```

## CORS

Origins die `Access-Control-Allow-Origin` terugkrijgen:

```
https://co-legal.be
https://www.co-legal.be
https://agent.co-legal.be
http://localhost:8000
http://localhost:5173
http://127.0.0.1:8000
```

Exposed headers: `A2A-Version`, `RateLimit-*`, `X-RateLimit-*`, `Retry-After`.

## Verder

- [AgentCard live](https://agent.co-legal.be/.well-known/agent-card.json)
- [llms-full.txt](/llms-full.txt) — complete LLM-friendly content dump
- [Skills catalog op /](https://co-legal.be/) — live + roadmap
