# Agents and tokens

> Give a coding agent a key of its own, narrow it with scopes or an agent class, connect a hosted client through OAuth, and read the audit of what the agent did.

A coding agent works on Rytiv with an API key like any other client. What sets its key apart is that you hand it over without watching every request, so the platform, not the agent, has to know what the key is meant for. Rytiv gives you three tools for that: scopes, agent tokens with a class, and an audit of every judged request.

## One key per agent

Create a key for each agent and name it after the agent. A key is shown once; store it where the agent reads it (`RYTIV_TOKEN`, a `.mcp.json` with `${RYTIV_TOKEN}`, your CI's secret store).

**CLI**

```bash
ry tokens create claude-code --agent sandbox --app web --app web-try
```

**curl**

```bash
curl -X POST "https://api.rytiv.app/v1/tokens" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "claude-code", "agent": {"class": "sandbox", "apps": ["web", "web-try"]}}'
```

**TypeScript**

```ts
const res = await fetch("https://api.rytiv.app/v1/tokens", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "claude-code", agent: { class: "sandbox", apps: ["web", "web-try"] } }),
});
const { token } = await res.json(); // shown in this answer only
```

**Python**

```python
import os, requests

res = requests.post(
    "https://api.rytiv.app/v1/tokens",
    headers={"Authorization": f"Bearer {os.environ['RYTIV_TOKEN']}"},
    json={"name": "claude-code", "agent": {"class": "sandbox", "apps": ["web", "web-try"]}},
)
token = res.json()["token"]  # shown in this answer only
```

Creating a key needs a key with full access; your first one comes from the console or from `ry login`.

## Agent tokens: a class and a list of apps

An `agent` block makes the key an agent token. The platform enforces two facts about it on every request:

| Class | What the agent may do |
| --- | --- |
| `sandbox` | Everything an API key may do, inside the apps on its list. For trying things out. |
| `twin` | The same, for a per-task copy of production: a preview app, a database branch, a computer. |
| `production` | Read only. Status, releases and logs are open; what production holds (files, a shell, query results) needs `prodDataRead: true` on the token. Any change is refused with `agent-token-production-readonly`. |

`apps` names the apps the token may touch; a request about another app, or about something that is not an app (a bucket, a volume), is refused with `agent-token-app-scope`. Without the list, the token reaches the whole organization.

Whatever the class, an agent token can never mint, reveal or revoke a credential, administer the organization, or act for another organization (`agent-token-credential`, `agent-token-delegation`). An agent that is told "this is production, only read" by its prompt is one injected sentence away from forgetting it; the token does not forget.

With `ry tokens create`: `--agent <class>`, `--app <name>` (repeatable) and `--read-prod-data`.

## Scopes: less than full access

Scopes narrow a key the way a CI job or a partner needs it. A key without scopes is the whole organization; a key with scopes can only do less.

| Scope | Allows |
| --- | --- |
| `read` | Every read of the product API that is a status: apps, machines, deploys, databases, buckets, domains, schedules, webhooks. Not credentials, not billing, not the files of a machine. |
| `deploy` | The deploy routes of every app: build, deploy, releases, rollback, logs, machines, secrets. Never creating or deleting an app, never a key. |
| `deploy:<app>` | The same for exactly that app. The app list shows only the apps the key may deploy. |
| `mail:send` | Sending mail from any proven domain of the organization. |
| `mail:send:<domain>` | Sending from exactly that domain. |

```bash
ry tokens create ci-web --deploy web --expires-in 30d
ry tokens create status-page --scope read
```

A scoped key cannot open a shell, a tunnel or the console of a machine; those need full access. A route the scope does not cover answers `403` with `token-scope` and names the scope needed. Scopes and an agent class combine; the narrower rule wins.

## Expiry

A key may stop working at a moment (`expiresAt`) or after a duration (`ttlSeconds`, in the CLI `--expires-in 12h` or `--expires-at 2027-01-31T00:00:00Z`): at least 60 seconds and at most 365 days ahead. A key with an expiry cannot create a key that outlives it; without an explicit expiry, the new key inherits the creator's.

```bash
ry tokens list
ry tokens revoke tok-1a2b3c4d
```

Revoking a key rejects requests already using it right away.

## Connect a hosted client with OAuth

A client that has no place for a pasted key (a hosted connector) connects to the MCP server through OAuth 2.1:

1. `POST https://api.rytiv.app/v1/mcp` without a key answers `401` with a `WWW-Authenticate` header that names the discovery document `/.well-known/oauth-protected-resource/v1/mcp`.
2. The client reads `/.well-known/oauth-authorization-server`, registers itself at `POST /v1/oauth/register` (no client secret; every client is a public client bound to PKCE), and sends you to `GET /v1/oauth/authorize`.
3. You land on `https://console.rytiv.com/connect`, signed in, and approve the client for the active team.
4. The client exchanges its code at `POST /v1/oauth/token` and receives an access token (valid 1 hour) and a refresh token (30 days, renewed on use).

The access token is an ordinary key of the organization, named after the client, and appears under **Team, Tokens & keys** in the console, where you revoke it. Scopes of the OAuth server: `mcp` and `offline_access`.

In Claude Code, Codex and Cursor you do not need OAuth: add the server with your key as a bearer header, see the [quickstart](https://docs.rytiv.com/quickstart.md).

## Read what the agent did

Every request judged for an agent token is recorded: the route, the verdict, the token.

```bash
curl "https://api.rytiv.app/v1/agent-actions?limit=100" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

`?token=<id>` narrows the list to one key. Reading the audit needs a key with full access.

## Confirmations

A change in production by an agent waits for a person. The console carries the page for it (`/confirm/<id>`), and the platform refuses the change until the confirmation exists; in this release the refusal stands, and the person makes the change with their own key or in the console. See [Confirmations](https://docs.rytiv.com/concepts/confirmations.md).
