# Agent identities

An agent identity is a delegated credential owned by a partner. It carries its own API ID and
secret, a scope set, a budget, an approval threshold and optional expiry. Agent keys are for
autonomous buying, not for account administration.

The main agent-management endpoints are in the published v1 OpenAPI document and served by the
v1 API. `PATCH /v1/agents/{agentId}/keys/{keyId}` is in the schema and served by the v1 API.
Budget enforcement is live: per-order, daily and rolling caps fail closed. Human approval above a threshold is **Planned** and returns `approval_required` today without an approval exchange.

## Principal key vs agent key

| Property | Principal key | Agent key |
| --- | --- | --- |
| Scope source | Set when the key is created | Inherits the agent's scopes |
| Can manage agents | Yes, with `agents:manage` | No, always rejected |
| Can issue or rotate keys | Yes, with `agents:manage` | No |
| Budget | Partner-level | Agent per-order, daily and rolling limits |
| Approval threshold | Partner default | Agent threshold |
| Environment | sandbox or live | Same as its agent |
| Default expiry | Optional | Agent expiry, or 90 days |

An agent key that calls a management path gets `403 forbidden`. The `agents:manage` scope is a
principal scope even though the public manifest lists it among assignable scopes. Agent keys cannot
receive it.

## Scopes

| Scope | Grants |
| --- | --- |
| `catalog:read` | Catalogue, quote reads, usage, activity, `agents/me` |
| `quotes:write` | Quote creation |
| `orders:read` | List and read purchases |
| `orders:write` | Create quote-based or one-call orders |
| `esims:read` | Reserved for issued eSIM metadata |
| `esims:redeem` | Create redemption batches |
| `wallet:read` | Read wallet balances |
| `wallet:fund` | Create funding sessions |
| `agents:manage` | Principal-only agent and key management |

Give an agent the smallest set that completes its job. A buying agent usually needs
`catalog:read`, `quotes:write`, `orders:read`, `orders:write`, `esims:redeem` and `wallet:read`.
Do not grant `wallet:fund` to an agent unless the principal explicitly delegates funding. Funding
authority is not implied by an order scope.

## Create an agent

```http
POST /v1/agents
```

Required scope: `agents:manage`. Principal only.

```json
{
  "name": "procurement-agent-eu",
  "purpose": "Buy connectivity for field staff in the EU",
  "scopes": ["catalog:read", "quotes:write", "orders:read", "orders:write", "esims:redeem", "wallet:read"],
  "perOrderLimitMinor": 5000,
  "dailyLimitMinor": 15000,
  "rollingLimitMinor": 25000,
  "approvalThresholdMinor": 1000,
  "expiresAt": "2027-02-01T00:00:00.000Z"
}
```

| Field | Rule |
| --- | --- |
| `name` | 2 to 100 characters. |
| `purpose` | Optional, up to 500 characters. |
| `scopes` | Any agent scope. Unknown values are dropped. Defaults to `catalog:read` when omitted. |
| `perOrderLimitMinor` | Integer 100 to 10,000,000. Default 5,000. |
| `dailyLimitMinor` | Integer 100 to 10,000,000. Default 15,000. |
| `rollingLimitMinor` | Integer 100 to 10,000,000. Default 25,000. |
| `approvalThresholdMinor` | Integer, 0 or more. Default 1,000. |
| `expiresAt` | Optional ISO timestamp. Agent keys default to 90 days when no agent expiry is set. |

Amounts are minor units. The response is the public agent object with its id, status, scopes and
limits. It never returns a secret.

## Issue a key

```http
POST /v1/agents/{agentId}/keys
```

```json
{
  "name": "production",
  "ipAllowlist": ["203.0.113.0/24", "2001:db8:1234::/48"]
}
```

The response contains `apiId`, `apiSecret`, `environment`, `scopes` and a one-time notice. The
secret is shown once. Store it immediately and never print it in logs, prompts, chat or client
bundles.

```json
{
  "agentId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
  "apiId": "rw_sandbox_xxxxxxxxxxxx",
  "apiSecret": "REDACTED-ONE-TIME-SECRET",
  "environment": "sandbox",
  "scopes": ["catalog:read", "quotes:write", "orders:read", "orders:write", "esims:redeem", "wallet:read"],
  "notice": "Store apiSecret now. It cannot be retrieved again."
}
```

## Manage keys

```http
PATCH /v1/agents/{agentId}/keys/{keyId}
POST  /v1/agents/{agentId}/keys/{keyId}/rotate
```

- `PATCH` updates the allowlist or sets `revoked: true`. The response returns the key metadata, never
  the secret.
- `POST .../rotate` issues a new API ID and secret. The old key expires after 24 hours. Update your
  secret manager before the window closes.
- A revoked key returns `401 unauthorized` on the next request.
- Rotating a key does not change its scopes. Update the agent scopes first if the mandate changed.

Both paths require `agents:manage` and a principal key.

## Read the current agent

```http
GET /v1/agents/me
```

Returns the calling agent's id, name, partner id, environment and scopes. It never returns a
secret. A principal key returns `403 forbidden` because it is not an agent.

## Update and revoke

```http
PATCH  /v1/agents/{agentId}
DELETE /v1/agents/{agentId}
```

- `PATCH` updates scopes, any of the three limits, the approval threshold, or status
  (`active`, `suspended`, `revoked`).
- `DELETE` revokes the agent and every key attached to it.
- Changes take effect on the next request. There is no token cache to flush.
- An agent cannot edit itself. The API rejects an agent key on both paths.

## Lifecycle

```text
create agent
    │
    ├── issue key ──> active ──> rotate ──> old key expires after 24h
    │                    │
    │                    ├── suspend ──> 401 agent_inactive
    │                    └── revoke  ──> 401 unauthorized on all keys
    │
    └── expiresAt reached ──> 401 agent_inactive
```

Agent status is checked on every authenticated request. A suspended, revoked or expired agent
cannot call the API even if its key has not expired.

## Budget enforcement

The agent record stores `perOrderLimitMinor`, `dailyLimitMinor`, `rollingLimitMinor` and
`approvalThresholdMinor`. The current order handler does not yet enforce those values. Budget
enforcement and human approval are **Planned**. Until they ship:

- The principal must set the mandate and enforce the budget in the calling system.
- The agent must check the quote or expected total against its mandate before ordering.
- The agent must stop above its approval threshold and ask the principal.
- Do not treat the presence of a stored limit as a server-side guarantee.

This gap is intentional and visible so no partner assumes a control that is not active. See
[Agent payment](/docs/agent-payment) and [Approval flow](/docs/approval-flow).

## Security rules

- One agent per workload or purpose. Do not share an agent key between unrelated systems.
- Use a stable egress and an IP allowlist for live agents.
- Keep the secret server-side only. The API ID is public; the secret is not.
- Rotate on a schedule and immediately after any suspected exposure.
- Do not paste the secret into a prompt, ticket, dashboard note or chat.
- Report suspected exposure to [security@roamworks.io](mailto:security@roamworks.io).

## Related

- [Authentication](/docs/authentication) for the signing string.
- [IP allowlist](/docs/ip-allowlist) for network controls.
- [Agent payment](/docs/agent-payment) for the buying flow.
- [Approval flow](/docs/approval-flow) for threshold decisions.
