# What is an agent ID?

> What should an AI agent’s ID prove to the app or person it reaches, and why can’t they see who is accountable for it today?

## The short answer

An agent ID is a signed statement anyone can check without asking the agent's owner: which agent this is, which company stands behind it, who it acts for, and, on each message it sends, that the message arrived unchanged. In Pidgeon the ID is an agent card, a small JWS signed under a key the agent's domain publishes, and each message or request the agent sends carries its own signature from the same domain. An API key says the caller may use your API; an agent ID says who the caller is.

## Why an app cannot see who is behind an agent

Most agents run on credentials borrowed from a person: the owner's OAuth token, an API key made in the owner's account, a password in an environment variable. The app at the other end sees that person. It cannot tell the person from the agent, or one of their agents from another, and revoking the credential revokes all of them.

Email is no better. DKIM and DMARC say that a domain handled a message. They say nothing about whether a person or a program wrote it, or for whom, and a display name costs nothing to type.

So when an agent does something wrong with valid credentials, the app has a token and a timestamp. It has no answer to the question that matters: whose agent was this?

## What an agent ID should prove

Four things, in this order:

1. **Who the agent is.** A stable identifier and an address, so two agents from one company are two different things.
2. **Which company stands behind it.** A domain the company has proved it controls, not a name it typed.
3. **Who it acts for.** The organisation responsible for what the agent does.
4. **That the message is unchanged.** A signature over what the agent sent: the recipients, the text and every attachment, or the HTTP request.

The first three belong to the agent and change rarely. The fourth belongs to each message. Post, the open protocol under Pidgeon, keeps them apart: a card carries the first three, and a signature on each message carries the fourth.

## A worked example: the Post agent card

This is the card for `support-agent@acme.com`, an agent on a domain Acme has proved, decoded for reading:

```json
{
  "iss": "https://post.acme.com",
  "sub": "8f0c…",
  "iat": 1793782800,
  "exp": 1793869200,
  "v": "post0",
  "address": "support-agent@acme.com",
  "kind": "agent",
  "on_behalf_of": "Acme Ltd",
  "operator": { "domain": "acme.com", "verified": true },
  "created_at": "2026-11-04T09:00:00Z",
  "keys": [{ "type": "post", "domain": "acme.com", "kid": "…" }],
  "reputation": { "standing": "new", "since": "2026-11" },
  "url": "https://pidgeon.ai/api/v1/cards/8f0c…"
}
```

Each claim answers one of the four questions, or helps you weigh the answer:

- **`sub`** is the agent's id. It is never given to another agent, even if the address is deleted and created again, so it is the value to key an account on.
- **`kind`** is `agent`, or `service` for software that is not an agent, such as a workflow. Never `person`: a card says software is speaking.
- **`operator`** is the domain the company proved, with `verified: true`.
- **`on_behalf_of`** is the name the company set for itself, or its verified domain until it sets one. It is never taken from a person's email address.
- **`keys`** names the key that signs the agent's mail, so a receiver knows what signature to expect.
- **`reputation`** is coarse on purpose: `new` for the first 30 days, then `established`, and `restricted` while the agent is suspended. Never a count.
- **`exp`** is a day after `iat`. Pidgeon signs a card when it is read rather than storing one, so nothing goes stale, and a copy somebody holds lapses on its own.

What a card never says: the account behind it, its members, its plan, a message count, or anything in the agent's inbox. It carries only what the agent's own mail already says, and facts about its domain.

## How anyone checks it

The card is public, with no key:

```sh
curl https://pidgeon.ai/api/v1/cards/<id>
curl "https://post.acme.com/.well-known/post/card?address=support-agent@acme.com"
```

The answer holds `jws`, the signed card, and `card`, its payload decoded. The `jws` is the authority. To check it yourself, take the domain from `iss`, fetch `https://post.acme.com/.well-known/post.json`, find the key its `kid` names, verify the Ed25519 signature, then check `exp` and that `address` is the address you asked about. Any JOSE library can do this offline.

For a message, the verifier does all of it in one call. Send the raw message or its headers:

```sh
curl -X POST https://pidgeon.ai/api/v1/verify \
  -H 'content-type: application/json' \
  -d '{"message": "<the raw message, or its headers>", "recipient": "refunds@you.com"}'
```

The answer's `verdict` is `verified`, `caution`, `failed` or `unsigned`, with the sender, its operator and its card. On a good message the `advice` reads like this: "acme.com signed this as sent by an AI agent, and nothing it signed has changed. Act on it as you would on mail from acme.com." The verifier keeps nothing you send it. It checks 300 messages an hour per IP.

## What an agent ID does not prove

- **That the agent is right.** A signature says who is accountable for a message, not that the message is true or should be obeyed. An agent's mail is still mail from outside.
- **What the agent may do.** Permissions stay with your own authorisation system. The card tells it who is asking.
- **That the domain is honest.** Anyone can register `acme-support.com` and prove it. The verifier flags domains made to look like Pidgeon's own; a look-alike of your domain is not detected. Every card Pidgeon issues is in a [public log](https://pidgeon.ai/protocol), so you can watch it for cards that use your name.
- **The state of the last minute.** A copy of a card is believed until its `exp`, at most a day. When something matters, fetch it again.

## When you do not need one

If your agents only call your own services with credentials you issued, your identity provider and your service accounts already answer who is calling. An agent ID earns its place when the agent reaches somebody who did not issue its credentials: a customer's inbox, a vendor's API, a sign-up form on someone else's site.

## Start

[Agents](https://pidgeon.ai/docs/agents.md) has the card's claims and how to issue, suspend and revoke one. [Checking a message](https://pidgeon.ai/docs/verifying.md) explains each verdict. Paste a message into [the checker](https://pidgeon.ai/verify) to see a card from the receiving side, or read [Post](https://pidgeon.ai/protocol), the draft that defines it.

## Questions

### What is an agent ID?

A signed statement anyone can check about an AI agent: which agent it is, which company stands behind it and who it acts for. Each message the agent sends carries its own signature, which proves the message is unchanged.

### What is a Post agent card?

A compact JWS, typ agent-card+jwt, signed under a key the agent’s domain publishes at post.<domain>/.well-known/post.json. It names the agent’s address and stable sub, its kind, its operator domain, who it acts for and a coarse standing.

### Does an agent ID say what the agent may do?

No. It says who the agent is and who answers for it. What it may do stays with your own authorisation system.

### How long is an agent card valid?

A day. Pidgeon signs a card when it is read, so a held copy lapses on its own; fetch it again before anything that matters.
