# A2A Agent Card identity

> An A2A Agent Card says what an agent can do; what proves who runs it and who it acts for?

## The short answer

An A2A Agent Card says what an agent can do and how to call it: its name, skills, interfaces and how to authenticate. A Post card says who the agent is: its address, which company runs it, who it acts for and its standing, signed so anyone can check it. The two fit together, because an A2A card can carry a Post identity as an ordinary A2A extension, `https://pidgeon.ai/post/a2a/v0`, signed with the same key that signs the agent's email and HTTP requests.

## What an A2A Agent Card says

Google's A2A is how agents call each other: one agent publishes an Agent Card, conventionally at `/.well-known/agent-card.json`, and another reads it to learn what it can ask for. As A2A 1.0.0 specifies it (read 4 November 2026), the card carries:

- **What the agent is called and does**: `name`, `description`, and `skills`.
- **How to reach it**: its interfaces and transports.
- **How to authenticate to it**: its security schemes.
- **What it supports**: `capabilities`, including `extensions`, a list of named additions the card opts into.
- **Who provides it**: `provider`, an organisation name and URL.
- **Optionally, signatures**: `signatures`, a list of JWS signatures over the card.

That is a description of capabilities, written by the agent's operator. The `provider` field is the operator's own statement of who it is. A2A leaves the question of whether that statement is true to whoever reads the card.

## What a Post card says

A Post card is the other half. It is a compact JWS (`typ: agent-card+jwt`, Ed25519), signed by its issuer and fetchable by anyone with no key:

```json
{
  "iss": "https://post.pidgeon.email",
  "sub": "8f0c…",
  "v": "post0",
  "address": "research-agent@acme.com",
  "kind": "agent",
  "on_behalf_of": "Acme Ltd",
  "operator": { "domain": "acme.com", "verified": true },
  "keys": [{ "type": "post", "domain": "acme.com", "kid": "…" }],
  "reputation": { "standing": "new", "since": "2026-11" }
}
```

`operator.verified` is `true` only when the domain's owner proved it, through a DNS record under `_post`. `standing` is `new` for 30 days, `restricted` while the agent is suspended, and `established` otherwise. The card is good for a day and is signed again each time it is read, so it is never stale and a revoked one stops answering at once. Every card issued, changed or revoked is also an entry in a public, append-only log.

The two cards answer different questions. Nothing in a Post card says what the agent can do, and nothing in an A2A card proves who is behind it.

## A Post identity inside an A2A card

Post uses two mechanisms A2A already has, so an A2A client needs nothing new.

**An extension.** An entry in `capabilities.extensions` with `uri` set to `https://pidgeon.ai/post/a2a/v0`, `required: false`, and `params` carrying the Post card:

```json
{
  "uri": "https://pidgeon.ai/post/a2a/v0",
  "description": "Post agent identity: a signed card for this agent’s address and operator.",
  "required": false,
  "params": {
    "address": "research-agent@acme.com",
    "kind": "agent",
    "on_behalf_of": "Acme Ltd",
    "operator": "acme.com",
    "card_url": "https://pidgeon.ai/api/v1/cards/8f0c…",
    "card_jws": "eyJhbGciOiJFZERTQSIs…"
  }
}
```

`card_jws` is the authority. The other members are there to be read. Because `required` is false, a client that does not know Post ignores the extension and reads the rest of the card as before.

**A signature.** An entry in `signatures`: JWS JSON form with the payload left out, over the RFC 8785 canonical form of the A2A card without its `signatures`, by the operator's own key. The protected header has `alg: EdDSA`, `kid` the key's JWK thumbprint, and `jku` pointing at `https://post.acme.com/.well-known/http-message-signatures-directory`. That is the same key directory Web Bot Auth verifiers read for the agent's HTTP requests. So one key, published once, signs the agent's mail, its requests and its A2A card.

The fields map without overlap. The Post card's `address` and `kind` have no A2A counterpart and travel in the extension. Its `operator` corresponds to A2A's `provider`, which the Post card proves. The A2A card's name, description, skills and interfaces have no Post counterpart and stay where they are.

## How a client checks it

A client that knows Post checks three things, in order:

1. The A2A card's signature holds under the key at `jku`, fetched only from the operator's own `post.` host.
2. `card_jws` holds under its issuer's published keys, at `<iss>/.well-known/post.json`.
3. The Post card's `address` is at the domain whose key signed the A2A card.

If all three hold, the A2A card was published by the domain the Post card names, and that domain runs this agent for the party in `on_behalf_of`. If the first fails, the A2A card may have been copied or edited. If the third fails, somebody has pasted another agent's identity into their own card.

Step 2 works today with no library: fetch `card_url`, or the card by address at `https://post.acme.com/.well-known/post/card?address=research-agent@acme.com`, and check its `jws` against the issuer's keys.

## Building it today

`pidgeon id` makes the operator's key and issues the card with no account:

```sh
npm install -g @pidgeon-ai/cli
pidgeon id init acme.com
pidgeon id issue research-agent --on-behalf-of "Acme Ltd" --json
```

The `--json` output is the card document the extension is built from: `url`, `jws` and `card`. The open library, `@pidgeon-ai/id`, has `signA2aCard`, which adds the extension and the signature in one call, and `verifyA2aCard` for the other side. It is Apache-2.0 and coming soon to npm. Checking an A2A card in the [checker](https://pidgeon.ai/verify) is being built.

## What stays out of the card

An A2A card is public and names no customer, so what an agent may do for one particular customer does not belong in it. Post's draft carries that, as a grant, in the A2A message's metadata under the same extension URI, with the task it applies to.

Two limits are worth knowing before you ship:

- **Canonical form.** The signature is over the card exactly as built. A library that re-serialises the card and drops default values, such as a `false`, can change the bytes and break the signature. Publish the card as signed.
- **Proof is per domain.** The A2A signature is the operator's key, so it proves the domain published the card. Which agent on the domain is speaking comes from the Post card's `address`, and from the signed `From` on each request.

## Start

[Agents](https://pidgeon.ai/docs/agents.md) shows the card in full and where anyone fetches it. The A2A mapping is section 3.5.6 of the draft, linked from [the protocol](https://pidgeon.ai/protocol) page. [Checking a message](https://pidgeon.ai/docs/verifying.md) covers the verifier, which checks the same card on email and on signed HTTP requests.

## Questions

### Does an A2A Agent Card prove who runs an agent?

Not on its own. It describes what the agent can do and how to call it, and its provider field is the operator's own statement. A Post card, signed by its issuer, says which proved domain runs the agent and who it acts for.

### How do I add a Post identity to an A2A Agent Card?

Add an entry to capabilities.extensions with uri https://pidgeon.ai/post/a2a/v0, required false, and params carrying the Post card, then sign the A2A card with the operator's key. A client that does not know Post ignores the extension.

### Which key signs the A2A card?

The operator's own Ed25519 key, the same one that signs the agent's email and HTTP requests. The signature's jku points at the domain's Web Bot Auth key directory on its post host.

### Can Pidgeon's checker verify an A2A Agent Card?

Not yet; that is being built. Today you can check the card_jws in the extension by fetching the card and checking it against its issuer's published keys.
