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

# Agents

> tally.agents — register, list, and retrieve agent identities.

The `tally.agents` resource manages agent identities — the things you run that spend money. See [Agents (concept)](/agents) for the model.

## Types

### `Agent`

```ts theme={null}
type Agent = {
  /** Stable user-provided identifier (e.g., "research-bot"). */
  id: string;
  /** "active" — at least one activated (non-revoked) signer is attached.
   *  "no_permissions" — no activated signer yet. */
  status: "no_permissions" | "active";
  mode: "test" | "live";
  /** ISO 8601 timestamp. */
  created_at: string;
  /** Account slug the agent belongs to. Use to build dashboard deep-links
   *  (e.g., guide the user to grant a permission). */
  account_slug: string;
  /** Count of non-revoked, activated signers. */
  active_signers: number;
  /** Count of non-revoked, not-yet-activated signers. */
  pending_signers: number;
  /** Wallets this agent has active permission to spend from. Empty array
   *  if no active grants. Pick one for `tally.payments.create({ wallet })`. */
  wallets: Array<{
    address: string;
    display_name: string;
    role_label: string | null;
    max_per_tx_usdc: string;
    daily_cap_usdc: string | null;
  }>;
};
```

### `AgentUpsertInput`

```ts theme={null}
type AgentUpsertInput = {
  /** User-provided stable string identifier. */
  id: string;
};
```

## Methods

### `agents.upsert(input)`

Creates an agent if it doesn't exist; returns the existing record if it does. Idempotent — safe to run on every deploy.

```ts theme={null}
const agent = await tally.agents.upsert({ id: "research-bot" });
```

**Parameters**

| Field      | Type     | Description                                                             |
| ---------- | -------- | ----------------------------------------------------------------------- |
| `input.id` | `string` | Stable identifier. Must be unique within the API key's (account, mode). |

**Returns:** `Promise<Agent>`. For a freshly registered agent, `status` will be `"no_permissions"` and both signer counts will be 0 until a user authorizes a grant. See [Permissions](/permissions) for the grant flow.

**Throws**

| Error                 | When                                                                  |
| --------------------- | --------------------------------------------------------------------- |
| `ValidationError`     | The `id` is missing, empty, or fails server-side format checks.       |
| `AuthenticationError` | The API key is missing, revoked, or out of its rotation grace window. |

### `agents.list()`

Returns every agent in the API key's (account, mode).

```ts theme={null}
const agents = await tally.agents.list();
```

**Returns:** `Promise<Agent[]>`. No pagination today — accounts with hundreds of agents will get the full list. Cursor-paginated variant is on the roadmap; until it lands, this is a single round-trip.

**Throws:** `AuthenticationError` if the API key is invalid.

### `agents.get(id)`

Fetches a single agent by id.

```ts theme={null}
const agent = await tally.agents.get("research-bot");
```

**Parameters**

| Field | Type     | Description                                               |
| ----- | -------- | --------------------------------------------------------- |
| `id`  | `string` | The agent's stable identifier. URL-encoded automatically. |

**Returns:** `Promise<Agent>`.

**Throws**

| Error                 | When                                                  |
| --------------------- | ----------------------------------------------------- |
| `NotFoundError`       | No agent with that id exists in this (account, mode). |
| `AuthenticationError` | The API key is invalid.                               |

### `agents.delete(id)`

Soft-deletes an agent. The DB row stays in place so historical transactions keep their attribution; future `list()` / `get()` responses hide it.

```ts theme={null}
await tally.agents.delete("retired-bot");
```

The 409 `has_active_grants` enforcement makes consent ordering explicit: an agent with active spending power isn't quietly removed from view while still attached to a Privy wallet. Revoke its permissions first, then retry.

**Resurrection** is supported: re-running `tally.agents.upsert({ id })` with the same id clears `deletedAt` and brings the agent back with no transaction history loss.

**Throws**

| Error                 | When                                                                                             |
| --------------------- | ------------------------------------------------------------------------------------------------ |
| `NotFoundError`       | No agent with that id exists (or already soft-deleted).                                          |
| `ConflictError`       | `err.code === "has_active_grants"` — agent still has non-revoked permissions. Revoke them first. |
| `AuthenticationError` | The API key is invalid.                                                                          |

## Patterns

### Idempotent registration on deploy

Because `upsert` is idempotent, the cleanest pattern is to register every agent your service needs at startup:

```ts theme={null}
const REQUIRED_AGENTS = ["research-bot", "summarizer", "publisher"] as const;

for (const id of REQUIRED_AGENTS) {
  await tally.agents.upsert({ id });
}
```

If you change `REQUIRED_AGENTS` in a later deploy, the new entries get registered automatically and existing ones are unchanged.

### Checking grant status before sending a payment

`Agent.status` reflects whether any wallet has authorized this agent to spend. Use it as a gate before invoking spend-related flows:

```ts theme={null}
const agent = await tally.agents.get("research-bot");

if (agent.status === "no_permissions") {
  // Surface to the end user: they need to grant permission via the dashboard.
  return;
}

// agent.status === "active" → safe to call tally.payments.create
```

Note `status: "active"` only confirms the agent has *some* grant; it doesn't tell you which wallets or what allowance remains. For that, today you have to either attempt the payment and branch on the error, or check the dashboard.

## Not yet in the SDK

* `agents.delete(id)` — soft-delete an agent. Tracked in BUILD\_LOG.md.
* `agents.list({ limit, starting_after })` — cursor pagination.
* `tally.permissions.list({ agent_id })` — per-grant inspection (allowance, policy summary, wallet). Currently dashboard-only.

When those land, the docs sweep notes in `mintlify-docs/permissions.mdx` get removed.
