---
title: "Agent Keys"
description: "Create and manage scope-restricted API keys for agents and automated workflows, with optional TTL expiry and fine-grained RBAC permission manifests"
canonical: "https://docs.ainative.studio/docs/agent-cloud/agent-keys"
last-updated: "2026-10-13T21:42:35.442Z"
---

# Agent Keys

Source: https://docs.ainative.studio/docs/agent-cloud/agent-keys

> Create and manage scope-restricted API keys for agents and automated workflows, with optional TTL expiry and fine-grained RBAC permission manifests

# Agent Keys

Agent Keys are user-owned, scope-restricted API keys designed for AI agents and automated workflows. Each key is scoped to specific services and namespaces, supports optional TTL expiry, and can carry a fine-grained RBAC permission manifest that controls per-tool, per-namespace, and per-route access.

**Key properties:**

- **Format:** `ak_` followed by a `token_urlsafe(32)` string (e.g., `ak_dGhpcyBpcyBhIHRlc3Qga2V5...`)
- **Storage:** Only the SHA-256 hash is persisted. The raw key is returned exactly once at creation and cannot be recovered.
- **Expiry:** Keys with an `expires_at` timestamp past the current time are rejected with 401.
- **Limit:** Maximum 100 active keys per user.

## Authentication

All endpoints require a valid session (JWT bearer token or existing API key) via the unified auth middleware.

## Base URL

```
/api/v1/auth
```

---

## Scope Format

Scopes follow the pattern `<service>:<permission>[:<namespace>]`:

```
zerodb:read:project/my-project
zerodb:write:project/my-project
inference:read
memory:write:session:abc123
```

**Valid services:** `zerodb`, `inference`, `memory`, `file`, `agent`, `mcp`

**Namespace** is optional. When present, it restricts the key to a specific data partition.

---

## Permission Manifest (RBAC)

A permission manifest provides fine-grained access control beyond scopes. All fields are optional; omitting a field means unrestricted access for that dimension.

```json
{
  "allowed_tools": ["zerodb_store_memory", "zerodb_recall"],
  "allowed_namespaces": ["project/my-project"],
  "denied_routes": ["/api/v1/billing/**", "/api/v1/admin/**"],
  "max_memory_bytes": 1048576
}
```

| Field | Type | Description |
|-------|------|-------------|
| `allowed_tools` | string[] | Whitelist of MCP tool names this key may invoke. Omit to allow all. |
| `allowed_namespaces` | string[] | Whitelist of namespaces. Valid formats: `global`, `project:<name>`, `project/<name>`, `session:<name>`. Omit to allow all. |
| `denied_routes` | string[] | Glob patterns for routes this key is denied. Supports `**` wildcard (e.g., `/api/v1/billing/**`). |
| `max_memory_bytes` | integer | Maximum memory the key may store, in bytes. Maximum: 104857600 (100 MB). |

**Permission evaluation order:**

1. Key must exist and be active (not revoked, not expired).
2. If `allowed_tools` is set and the requested tool is not in the list, the request is denied.
3. If `allowed_namespaces` is set and the requested namespace is not in the list, the request is denied.
4. If `denied_routes` contains a pattern matching the request path, the request is denied.
5. Otherwise, the request is allowed.

An empty manifest `{}` grants unrestricted access.

---

## Endpoints

### POST /api/v1/auth/keys

Create a new agent-scoped API key.

**Request body:**

```json
{
  "name": "ci-agent-key",
  "scopes": ["zerodb:read:project/my-project", "zerodb:write:project/my-project"],
  "ttl_seconds": 86400,
  "permissions": {
    "allowed_tools": ["zerodb_store_memory", "zerodb_recall"],
    "allowed_namespaces": ["project/my-project"],
    "denied_routes": ["/api/v1/billing/**"],
    "max_memory_bytes": 1048576
  }
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Human-readable label (1-128 characters) |
| `scopes` | string[] | Yes | At least one scope in `service:permission[:namespace]` format |
| `ttl_seconds` | integer | No | Seconds until expiry. Omit for a non-expiring key. |
| `permissions` | object | No | RBAC permission manifest. Omit for unrestricted access. |

**Response (201 Created):**

```json
{
  "id": "a1b2c3d4-...",
  "user_id": "u-5678-...",
  "name": "ci-agent-key",
  "key": "ak_dGhpcyBpcyBhIHRlc3Qga2V5...",
  "scopes": ["zerodb:read:project/my-project", "zerodb:write:project/my-project"],
  "permissions": {
    "allowed_tools": ["zerodb_store_memory", "zerodb_recall"],
    "allowed_namespaces": ["project/my-project"],
    "denied_routes": ["/api/v1/billing/**"],
    "max_memory_bytes": 1048576
  },
  "expires_at": "2026-06-02T12:00:00+00:00",
  "last_used_at": null,
  "is_active": true,
  "created_at": "2026-06-01T12:00:00+00:00"
}
```

The `key` field is only returned at creation time. Store it immediately.

---

### GET /api/v1/auth/keys

List API keys for the authenticated user.

**Query parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `include_inactive` | boolean | `false` | Include revoked/expired keys |
| `limit` | integer | 50 | Max results (1-200) |
| `offset` | integer | 0 | Pagination offset |

**Response (200 OK):**

```json
[
  {
    "id": "a1b2c3d4-...",
    "user_id": "u-5678-...",
    "name": "ci-agent-key",
    "scopes": ["zerodb:read:project/my-project"],
    "permissions": {},
    "expires_at": "2026-06-02T12:00:00+00:00",
    "last_used_at": "2026-06-01T14:30:00+00:00",
    "is_active": true,
    "created_at": "2026-06-01T12:00:00+00:00"
  }
]
```

The raw key is never included in list responses.

---

### DELETE /api/v1/auth/keys/\{key_id\}

Permanently revoke a key. This cannot be undone.

**Response:** 204 No Content

**Errors:**

| Status | Condition |
|--------|-----------|
| 404 | Key not found or already revoked |

---

### GET /api/v1/auth/keys/\{key_id\}/permissions

Return the RBAC permission manifest for a key that belongs to the authenticated user.

**Response (200 OK):**

```json
{
  "allowed_tools": ["zerodb_store_memory"],
  "allowed_namespaces": ["project/my-project"],
  "denied_routes": ["/api/v1/billing/**"],
  "max_memory_bytes": 1048576
}
```

An empty object `{}` means the key has unrestricted access.

---

### POST /api/v1/auth/keys/\{key_id\}/check-permission

Evaluate a key's RBAC manifest against a specific tool, namespace, or route.

**Request body:**

```json
{
  "tool": "zerodb_store_memory",
  "namespace": "project/my-project",
  "route": "/api/v1/memory/v2/remember"
}
```

All fields are optional. Only the supplied fields are checked.

**Response (200 OK):**

```json
{
  "allowed": true,
  "reason": "all checks passed"
}
```

When denied:

```json
{
  "allowed": false,
  "reason": "tool 'zerodb_delete' not in allowed_tools"
}
```

---

## Key Validation at Request Time

When an agent presents a key in the `Authorization` header, the validation logic:

1. Confirms the key starts with `ak_`.
2. Computes `SHA-256(raw_key)` and looks up the hash in `agent_api_keys_v2`.
3. Checks `is_active = true`.
4. Checks `expires_at` is null or in the future.
5. Updates `last_used_at` on successful validation.
6. Returns the key's metadata (user_id, scopes, permissions) to the caller.

---

## Error Responses

| Status | Meaning |
|--------|---------|
| 401 | Missing or invalid authentication |
| 404 | Key not found or does not belong to the user |
| 422 | Validation error (invalid scopes, permissions, or name) |
