> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dairo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent identity & provenance

> Give your agents a signed identity so recipients can verify the mail genuinely came from them.

Give an automated sender its own verifiable identity. An agent passport is a
portable `agt_…` identity that signs the email your agents send, so anyone
receiving that mail can confirm it genuinely came from your agent — not a spoofer.

This is an optional layer. You don't need a passport to send or receive email;
reach for it when you want recipients (or downstream agents) to be able to
*prove* who sent a message. Reads use `agents:read`; creating and binding use
`agents:write`. Verifying is public — no scope or API key.

## Create a passport

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl -X POST https://api.dairo.app/v1/agents \
    -H "Authorization: Bearer $DAIRO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "display": "Support Bot", "description": "Handles inbound support email" }'
  ```

  ```ts title="TypeScript" theme={null}
  const { agent } = await dairo.agents.create({
    display: "Support Bot",
    description: "Handles inbound support email",
  });
  console.log(agent.agentId); // "agt_…", stable for the passport's lifetime
  ```

  ```python title="Python" theme={null}
  agent = dairo.agents.create(
      display="Support Bot",
      description="Handles inbound support email",
  )
  print(agent.agent_id)
  ```

  ```text title="MCP" theme={null}
  Tool: manage_agents  (scope agents:write, confirm required)
  Args: { "action": "create", "display": "Support Bot", "confirm": true }
  ```
</CodeGroup>

You get back the passport, including a portable `agentId`:

```json theme={null}
{
  "agent": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "agentId": "agt_7h2k9q4m3xeyrt6bv8wn0pcd",
    "display": "Support Bot",
    "description": "Handles inbound support email",
    "status": "active",
    "createdAt": "2026-06-12T10:00:00Z",
    "updatedAt": "2026-06-12T10:00:00Z"
  }
}
```

Use the portable `agentId` (`agt_…`) — stable for the life of the passport — in
anything you publish or share.

Creating a passport isn't idempotent: each call mints a new `agentId`, so create
once and store the id rather than retrying.

### List and read

Read a passport by its `id` or its portable `agt_…` id. Listing returns an
`agents` array, newest first — there's no pagination or `limit`, so one call
returns every passport in your organization.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl https://api.dairo.app/v1/agents \
    -H "Authorization: Bearer $DAIRO_API_KEY"

  curl https://api.dairo.app/v1/agents/agt_7h2k9q4m3xeyrt6bv8wn0pcd \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const agents = await dairo.agents.list();
  const agent = await dairo.agents.get("agt_7h2k9q4m3xeyrt6bv8wn0pcd");
  ```

  ```python title="Python" theme={null}
  agents = dairo.agents.list()
  agent = dairo.agents.get("agt_7h2k9q4m3xeyrt6bv8wn0pcd")
  ```
</CodeGroup>

<Note>
  A passport supports create, read, bind, and delete — there's no edit. Deleting a
  passport clears its attribution from your keys, inboxes, and sent mail without
  touching the messages themselves. Those messages keep their provenance headers,
  but verifying one afterward returns `unknown_agent`, since the passport it named
  is gone.
</Note>

## Bind a key or inbox

A passport on its own is only an identity. **Binding** connects it to the
resources an agent actually sends through — an API key and/or an inbox — so every
email those resources produce is signed with the agent's provenance and
attributed to it in your send-health view.

Pass **at least one** of `apiKeyId` / `inboxId` — both must be resources you own.
Reference the passport by its `id` or its portable `agt_…` id.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl -X POST https://api.dairo.app/v1/agents/agt_7h2k9q4m3xeyrt6bv8wn0pcd/bindings \
    -H "Authorization: Bearer $DAIRO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "inboxId": "7c9e6679-7425-40de-944b-e07fc1f90ae7" }'
  ```

  ```ts title="TypeScript" theme={null}
  const result = await dairo.agents.bindings("agt_7h2k9q4m3xeyrt6bv8wn0pcd", {
    inboxId: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  });
  console.log(result.bound);
  ```

  ```python title="Python" theme={null}
  result = dairo.agents.bind(
      "agt_7h2k9q4m3xeyrt6bv8wn0pcd",
      inbox_id="7c9e6679-7425-40de-944b-e07fc1f90ae7",
  )
  ```

  ```text title="MCP" theme={null}
  Tool: manage_agents  (scope agents:write, confirm required)
  Args: {
    "action": "bind",
    "agentId": "agt_7h2k9q4m3xeyrt6bv8wn0pcd",
    "inboxId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "confirm": true
  }
  ```
</CodeGroup>

The response echoes what was bound:

```json theme={null}
{
  "agentId": "agt_7h2k9q4m3xeyrt6bv8wn0pcd",
  "apiKeyId": null,
  "inboxId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "bound": true
}
```

Bind both at once by passing `apiKeyId` and `inboxId` together.

<Note>
  You can only bind your own resources to your own passport — there's no way to
  attach someone else's key or inbox.
</Note>

After binding, mail from that key or inbox carries the agent's
`X-Dairo-Provenance` signature, and cross-account agent-to-agent hop receipts
record the bound `agentId`. From then on, the agent's reputation and any
per-agent send limit gate what it can send.

## How provenance works

When a send goes out from a bound key or inbox, Dairo signs a stable summary of
the message (the `from`, `to`, `subject`, and timestamp) and stamps two headers
onto the outgoing mail:

* `X-Dairo-Provenance` — the signature token.
* `X-Dairo-Verify` — a verify URL anyone can click.

Signing is best-effort and never blocks a send. A recipient re-derives the same
summary from the message they received and checks the signature against the
public key Dairo publishes in its JWKS — so they can confirm authenticity
without trusting you, or even calling Dairo.

## Verify a signed message

Provenance is only useful if anyone can check it. Two **public** endpoints let
them: a verify endpoint that returns a plain yes/no for a given message, and a
JWKS that publishes the public signing keys so anyone can check a signature on
their own. Neither needs a scope or an API key.

### Get a verdict

The simplest path: hand Dairo a message id and get back a verdict, with no crypto
on your side. The endpoint **always returns `200` with a verdict** — a "no" is a
result, not an error.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl "https://api.dairo.app/v1/agents/verify?id=3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ```

  ```ts title="TypeScript" theme={null}
  const verdict = await dairo.agents.verify({ id: "3fa85f64-5717-4562-b3fc-2c963f66afa6" });
  console.log(verdict.verified, verdict.agent?.agentId, verdict.reason);
  ```

  ```python title="Python" theme={null}
  verdict = dairo.agents.verify(message_id="3fa85f64-5717-4562-b3fc-2c963f66afa6")
  print(verdict.verified, verdict.reason)
  ```
</CodeGroup>

A verdict carries `verified`, the matched `agent` (when known), the `keyId`, the
`issuer` (`dairo.app`), and a `checkedAt` timestamp:

```json theme={null}
{
  "verified": true,
  "agent": { "agentId": "agt_7h2k9q4m3xeyrt6bv8wn0pcd", "display": "Support Bot", "status": "active" },
  "keyId": "dairo-ed25519-2026-06",
  "issuer": "dairo.app",
  "checkedAt": "2026-06-12T12:00:01Z"
}
```

A "no" returns the same shape with `"verified": false` and a `reason` such as
`missing_parameters`, `message_not_found`, `unknown_kid`, or `bad_signature`.

<Tip>
  Because verify never errors, branch on the `verified` field — not on the HTTP
  status. A `200` with `"verified": false` is the expected "no" answer.
</Tip>

### Verify a signature yourself

To check a signature without relying on Dairo's lookup — say, from the headers on
a message you received — pass the signature (`agent` + `kid` + `sig`) together
with the signed fields (`from`, `to`, `subject`, `ts`):

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl "https://api.dairo.app/v1/agents/verify?agent=agt_7h2k9q4m3xeyrt6bv8wn0pcd&kid=dairo-ed25519-2026-06&sig=…&from=bot@acme.dairo.app&to=user@example.com&subject=Your%20receipt&ts=2026-06-12T12:00:00Z"
  ```

  ```ts title="TypeScript" theme={null}
  await dairo.agents.verify({
    agent: "agt_7h2k9q4m3xeyrt6bv8wn0pcd",
    kid: "dairo-ed25519-2026-06",
    sig: "…",
    from: "bot@acme.dairo.app",
    to: "user@example.com",
    subject: "Your receipt",
    ts: "2026-06-12T12:00:00Z",
  });
  ```
</CodeGroup>

### Check it offline with the public keys

`GET /v1/agents/jwks` serves the public signing keys as a standard JSON Web Key
Set, so you can verify a signature entirely on your own — no call back to Dairo.
It serves **active and retired** keys, so a `kid` that signed an old message stays
verifiable forever. It's also reachable at the conventional
`/.well-known/dairo-jwks.json` alias.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl https://api.dairo.app/v1/agents/jwks
  ```

  ```ts title="TypeScript" theme={null}
  const jwks = await dairo.agents.jwks();
  console.log(jwks.keys[0].kid);
  ```

  ```python title="Python" theme={null}
  jwks = dairo.agents.jwks()
  print(jwks.keys[0].kid)
  ```
</CodeGroup>

```json theme={null}
{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "alg": "EdDSA",
      "use": "sig",
      "kid": "dairo-ed25519-2026-06",
      "x": "_h5rlaNeTyovkSiWHs2nG3LPyxd3gq3_CrDwknputUM"
    }
  ]
}
```

Find the key whose `kid` matches the message's signature, then verify the signed
fields against it with any standard EdDSA library.

## Next steps

<CardGroup cols={2}>
  <Card title="Agent-to-agent messages" icon="arrows-left-right" href="/receiving/agent-messages">
    Cross-account hop receipts that record provenance.
  </Card>

  <Card title="Send limits & reputation" icon="gauge" href="/agents/reputation">
    A per-agent safety valve for send health.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference">
    `GET/POST /v1/agents`, `DELETE /v1/agents/{id}`, `POST /v1/agents/{id}/bindings`, `GET /v1/agents/verify`, and `GET /v1/agents/jwks`.
  </Card>
</CardGroup>
