# Agent tokens

> First-class tokens for AI agents, with scopes, environments, a spend budget, expiry and an approval channel.

First-class tokens for AI agents, with scopes, environments, a spend budget, expiry and an approval channel.

| Operation | Method and path |
| --- | --- |
| [List agent tokens](#list-agent-tokens) | `GET /v1/workspaces/{workspace}/agent-tokens` |
| [Create an agent token](#create-an-agent-token) | `POST /v1/workspaces/{workspace}/agent-tokens` |
| [Get an agent token](#get-an-agent-token) | `GET /v1/workspaces/{workspace}/agent-tokens/{token_id}` |
| [Change an agent token's scopes, environments, budget or channel](#change-an-agent-tokens-scopes-environments-budget-or-channel) | `PATCH /v1/workspaces/{workspace}/agent-tokens/{token_id}` |
| [Revoke an agent token](#revoke-an-agent-token) | `DELETE /v1/workspaces/{workspace}/agent-tokens/{token_id}` |

## List agent tokens

```http
GET /v1/workspaces/{workspace}/agent-tokens
```

- **Operation:** `listAgentTokens`
- **Scopes:** `tokens:read`
- **Paginated:** `?limit=` and `?cursor=`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |
| `status` | query | `active`, `expired`, `revoked` |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | A page of AgentToken objects. | [AgentTokenList](/docs/api/objects/agent-token-list/) |

**Errors:** `400`, `401`, `403`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/workspaces/acme/agent-tokens \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "data": [
    {
      "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
      "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
      "name": "claude-code-on-stewart-laptop",
      "description": "Claude Code on Stewart's MacBook",
      "prefix": "guv_agt_Qm4x",
      "scopes": [
        "projects:read",
        "deploy:preview",
        "deploy:staging",
        "logs:read",
        "secrets:write-only",
        "db:branch"
      ],
      "projects": null,
      "environments": [
        "preview",
        "staging"
      ],
      "budget": {
        "spend_increase_per_day": {
          "amount": "10.00",
          "currency": "GBP",
          "display": "£10.00"
        }
      },
      "approval_channel": [
        "push",
        "email"
      ],
      "status": "active",
      "summary": [
        "Can deploy to staging and previews.",
        "Cannot touch production without your approval.",
        "Can set secrets but never read them.",
        "Can raise spend by up to £10.00 a day."
      ],
      "created_by": {
        "type": "user",
        "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
        "display": "Stewart Webb"
      },
      "expires_at": "2026-11-22T09:00:00Z",
      "last_used_at": "2026-10-23T13:58:00Z",
      "revoked_at": null,
      "created_at": "2026-10-23T09:00:00Z"
    }
  ],
  "next_cursor": "string",
  "has_more": false
}
```

## Create an agent token

```http
POST /v1/workspaces/{workspace}/agent-tokens
```

Only humans can create agent tokens. The token value is returned once, with ready-made setup snippets.

- **Operation:** `createAgentToken`
- **Scopes:** `tokens:write`
- **CLI:** `guv tokens create-agent`
- **Idempotency-Key:** required; supports `?dry_run=true`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |

**Request body** (`application/json`, required): [AgentTokenCreate](/docs/api/objects/agent-token-create/)

| Field | Type | Description |
| --- | --- | --- |
| `name` * | string |  |
| `description` | string |  |
| `scopes` * | array of [Scope](/docs/api/objects/scope/) |  |
| `projects` | array of string or null |  |
| `environments` * | array of string |  |
| `budget` | [AgentTokenBudget](/docs/api/objects/agent-token-budget/) |  |
| `expires_in` | string | Lifetime, at most 365d. Default `30d`. |
| `approval_channel` | array of [ApprovalChannel](/docs/api/objects/approval-channel/) | Default `["push","email"]`. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `201` Created | Created. | [AgentTokenCreated](/docs/api/objects/agent-token-created/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/workspaces/acme/agent-tokens \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "claude-code-on-stewart-laptop",
  "scopes": [
    "projects:read",
    "deploy:preview",
    "deploy:staging",
    "logs:read",
    "secrets:write-only",
    "db:branch"
  ],
  "environments": [
    "preview",
    "staging"
  ],
  "budget": {
    "spend_increase_per_day": {
      "amount": "10.00",
      "currency": "GBP"
    }
  },
  "expires_in": "30d",
  "approval_channel": [
    "push",
    "email"
  ]
}'
```

**Example response**

```json
{
  "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "name": "claude-code-on-stewart-laptop",
  "description": "Claude Code on Stewart's MacBook",
  "prefix": "guv_agt_Qm4x",
  "scopes": [
    "projects:read",
    "deploy:preview",
    "deploy:staging",
    "logs:read",
    "secrets:write-only",
    "db:branch"
  ],
  "projects": null,
  "environments": [
    "preview",
    "staging"
  ],
  "budget": {
    "spend_increase_per_day": {
      "amount": "10.00",
      "currency": "GBP",
      "display": "£10.00"
    }
  },
  "approval_channel": [
    "push",
    "email"
  ],
  "status": "active",
  "summary": [
    "Can deploy to staging and previews.",
    "Cannot touch production without your approval.",
    "Can set secrets but never read them.",
    "Can raise spend by up to £10.00 a day."
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "expires_at": "2026-11-22T09:00:00Z",
  "last_used_at": "2026-10-23T13:58:00Z",
  "revoked_at": null,
  "created_at": "2026-10-23T09:00:00Z",
  "token": "string",
  "setup": {
    "env": "export GUV_TOKEN=guv_agt_…",
    "mcp_command": "claude mcp add guv --transport http https://mcp.guv.dev --header 'Authorization: Bearer guv_agt_…'"
  }
}
```

## Get an agent token

```http
GET /v1/workspaces/{workspace}/agent-tokens/{token_id}
```

- **Operation:** `getAgentToken`
- **Scopes:** `tokens:read`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |
| `token_id` * | path | string | Agent token id. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [AgentToken](/docs/api/objects/agent-token/) |

**Errors:** `400`, `401`, `403`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/workspaces/acme/agent-tokens/tok_01jbx2m9k7q4r8t5v6w3y0z1pr \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "name": "claude-code-on-stewart-laptop",
  "description": "Claude Code on Stewart's MacBook",
  "prefix": "guv_agt_Qm4x",
  "scopes": [
    "projects:read",
    "deploy:preview",
    "deploy:staging",
    "logs:read",
    "secrets:write-only",
    "db:branch"
  ],
  "projects": null,
  "environments": [
    "preview",
    "staging"
  ],
  "budget": {
    "spend_increase_per_day": {
      "amount": "10.00",
      "currency": "GBP",
      "display": "£10.00"
    }
  },
  "approval_channel": [
    "push",
    "email"
  ],
  "status": "active",
  "summary": [
    "Can deploy to staging and previews.",
    "Cannot touch production without your approval.",
    "Can set secrets but never read them.",
    "Can raise spend by up to £10.00 a day."
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "expires_at": "2026-11-22T09:00:00Z",
  "last_used_at": "2026-10-23T13:58:00Z",
  "revoked_at": null,
  "created_at": "2026-10-23T09:00:00Z"
}
```

## Change an agent token's scopes, environments, budget or channel

```http
PATCH /v1/workspaces/{workspace}/agent-tokens/{token_id}
```

- **Operation:** `updateAgentToken`
- **Scopes:** `tokens:write`
- **Idempotency-Key:** required; supports `?dry_run=true`
- **Concurrency:** send the `ETag` you read as `If-Match`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |
| `token_id` * | path | string | Agent token id. |

**Request body** (`application/json`, required): [AgentTokenUpdate](/docs/api/objects/agent-token-update/)

| Field | Type | Description |
| --- | --- | --- |
| `name` | string |  |
| `description` | string |  |
| `scopes` | array of [Scope](/docs/api/objects/scope/) |  |
| `projects` | array of string or null |  |
| `environments` | array of string |  |
| `budget` | [AgentTokenBudget](/docs/api/objects/agent-token-budget/) |  |
| `approval_channel` | array of [ApprovalChannel](/docs/api/objects/approval-channel/) |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. With `dry_run=true`: a Plan (application/vnd.guv.plan+json) and nothing changes. | [AgentToken](/docs/api/objects/agent-token/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `412`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X PATCH https://api.guv.dev/v1/workspaces/acme/agent-tokens/tok_01jbx2m9k7q4r8t5v6w3y0z1pr \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "description": "string",
  "scopes": [
    "deploy:staging"
  ],
  "projects": [
    "string"
  ],
  "environments": [
    "string"
  ],
  "budget": {
    "spend_increase_per_day": {
      "amount": "7.42",
      "currency": "GBP",
      "display": "£7.42"
    },
    "max_deploys_per_hour": 1
  },
  "approval_channel": [
    "push"
  ]
}'
```

**Example response**

```json
{
  "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "name": "claude-code-on-stewart-laptop",
  "description": "Claude Code on Stewart's MacBook",
  "prefix": "guv_agt_Qm4x",
  "scopes": [
    "projects:read",
    "deploy:preview",
    "deploy:staging",
    "logs:read",
    "secrets:write-only",
    "db:branch"
  ],
  "projects": null,
  "environments": [
    "preview",
    "staging"
  ],
  "budget": {
    "spend_increase_per_day": {
      "amount": "10.00",
      "currency": "GBP",
      "display": "£10.00"
    }
  },
  "approval_channel": [
    "push",
    "email"
  ],
  "status": "active",
  "summary": [
    "Can deploy to staging and previews.",
    "Cannot touch production without your approval.",
    "Can set secrets but never read them.",
    "Can raise spend by up to £10.00 a day."
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "expires_at": "2026-11-22T09:00:00Z",
  "last_used_at": "2026-10-23T13:58:00Z",
  "revoked_at": null,
  "created_at": "2026-10-23T09:00:00Z"
}
```

## Revoke an agent token

```http
DELETE /v1/workspaces/{workspace}/agent-tokens/{token_id}
```

- **Operation:** `revokeAgentToken`
- **Scopes:** `tokens:write`
- **Idempotency-Key:** required; supports `?dry_run=true`
- **Concurrency:** send the `ETag` you read as `If-Match`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `workspace` * | path | string | Workspace id (`ws_…`) or slug. |
| `token_id` * | path | string | Agent token id. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `204` No Content | Done. No body. | none |

**Errors:** `400`, `401`, `403`, `404`, `409`, `412`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X DELETE https://api.guv.dev/v1/workspaces/acme/agent-tokens/tok_01jbx2m9k7q4r8t5v6w3y0z1pr \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```
