# Management API

Contexts, keys, and operator lifecycle.

The management API provides control-plane operations: creating Contexts, managing API keys, and operator lifecycle. All management endpoints are under `/api/v1/` and require a management key.

## Authentication

Management endpoints require a **management** API key as a Bearer token:

```http
Authorization: Bearer <management-key>
```

Keys are printed once by bootstrap or minted via `spectrond keys generate-key` / management REST. They are distinct from per-Context end-user keys.

## Contexts

### List Contexts

```http
GET /api/v1/contexts
```

Returns all Contexts registered on this SurrealDB Agent Memory deployment.

**Response:**
```json
{
  "contexts": [
    {
      "id": "acme-prod",
      "namespace": "acme",
      "database": "prod",
      "config": {
        "token_limit": 1000000,
        "models": { "extraction": "openai/gpt-4o-mini" },
        "providers_configured": ["openai"]
      },
      "created_at": "2026-01-15T10:00:00Z"
    }
  ]
}
```

### Create a Context

```http
POST /api/v1/contexts/{context_id}
Content-Type: application/json

{
  "namespace": "acme",
  "database": "prod",
  "admin_external_id": "surreal-cloud:usr_01HF…",
  "admin_display_name": "Alice Admin",
  "config": {
    "token_limit": 1000000,
    "models": {
      "extraction": "openai/gpt-4o-mini",
      "response": "openai/gpt-4o"
    },
    "providers": {
      "openai": "sk-..."
    }
  }
}
```

Creates the Context, provisions the SurrealDB namespace and database, and applies schema migrations. Returns `201 Created`.

If provisioning fails **after** the database was created by this request, SurrealDB Agent Memory **discards that database** so a retry starts from a clean migration state. Pre-existing databases (operator-provided or left over from a crash) are never removed - only a database this request created is eligible for discard.

When **`admin_external_id`** is set, SurrealDB Agent Memory seeds an initial **`principal:admin`** bound to that opaque, issuer-qualified identity (for example `surreal-cloud:<user_id>`) with full `/*` grants - so a Cloud proxy can create a Context and immediately resolve the org owner without a follow-up principal call. **`admin_display_name`** is an optional human-readable label. Omit both fields to keep the generic admin principal.

**Response:**
```json
{
  "id": "acme-prod",
  "namespace": "acme",
  "database": "prod",
  "config": {
    "token_limit": 1000000,
    "models": { "extraction": "openai/gpt-4o-mini" },
    "providers_configured": ["openai"]
  },
  "created_at": "2026-05-12T10:00:00Z"
}
```

### Migration status

```http
GET /api/v1/migrations
```

Returns a fleet-wide view of per-Context schema catch-up after a binary upgrade: version histogram, Contexts still behind the embedded latest, and DLQ entries.

### Get a Context

```http
GET /api/v1/contexts/{context_id}
```

Returns the Context record. Provider API key values are replaced with a `providers_configured` summary (per-Context keys only). For the full provider and model catalogue - including providers reachable via deployment-wide keys - use **`GET /api/v1/{context_id}/providers`** on the data-plane API.

### Update Context config

```http
PATCH /api/v1/contexts/{context_id}
Content-Type: application/json

{
  "config": {
    "token_limit": 2000000,
    "models": {
      "reflection": "anthropic/claude-opus-4-7"
    }
  }
}
```

Deep-merges the provided config into the existing config. Unset fields are left unchanged.

Top-level fields such as **`enforcement_blocked`** (pay-as-you-go vs hard block - see [Configuration](/docs/agent-memory/reference/configuration.md#top-level-context-fields-outside-config)) may be set on the same `PATCH` body outside `config`.

### Delete a Context

```http
DELETE /api/v1/contexts/{context_id}
```

Drops the bound SurrealDB database (removing all authoritative knowledge and experiential memory data), deletes the associated API keys, and removes the control-plane registry entry. This is irreversible. Returns `204 No Content`.

## Grant verbs

SurrealDB Agent Memory authorises data-plane and admin operations with seven scoped verbs in `<noun>:<verb>` form:

| Verb | Purpose |
| --- | --- |
| `memory:read` | Read facts and document chunks/keywords within the granted region (recall, query, chat, document GET/list) |
| `memory:write` | Write facts and ingest documents (chunks inherit the uploader’s write region) |
| `memory:forget` | Soft or hard forget, entity delete, scoped subtree erasure |
| `scope:read` | List scope names visible within the grant (does not imply data access) |
| `scope:create` | Register new scope paths |
| `scope:delete` | Remove scope paths |
| `grant:manage` | Grant or revoke access on principals |

Documents are governed by **`memory:*`** verbs - there is no separate `document:*` namespace. Upload and reprocess require `memory:write`; document reads are per-row under `memory:read`.

List the machine-readable catalog (for grant pickers and validation):

```http
GET /api/v1/verbs
Authorization: Bearer <management-key>
```

Grant verbs use namespaced forms (`memory:read`, `memory:write`, …). Flat names such as `read` and `write` are rejected.

## Principals and external identity

Principals are the data-plane identity SurrealDB Agent Memory authorises. Create one with an optional **`external_id`** so upstream systems (SurrealDB Cloud, OIDC, on-prem IdP) can resolve “which principal is this user?” without a side mapping table:

```http
POST /api/v1/contexts/{context_id}/principals
Content-Type: application/json

{
  "display_name": "Alice",
  "external_id": "surreal-cloud:usr_01HF…",
  "grants": {
    "memory:read": [{ "org": "acme", "user": "alice" }],
    "memory:write": [{ "org": "acme", "user": "alice" }]
  }
}
```

When **`external_id`** is present, create is **create-or-get**: a repeat call with the same issuer-qualified id returns the existing principal unchanged (even if `display_name` differs). Without `external_id`, each call mints a fresh principal. A duplicate **`display_name`** when no matching `external_id` applies returns **`409 Conflict`**. The value is opaque to SurrealDB Agent Memory - qualify it with your issuer prefix.

### Reserved built-in principals

Every Context seeds two reserved principals:

| Principal | Policy |
| --- | --- |
| **`principal:system`** | Fully **immutable** - update, grant change, and delete all return **`403`**. Background jobs stamp provenance against this identity. |
| **`principal:admin`** | **Undeletable** (delete returns **`403`**) but otherwise mutable - rename, re-kind, and grant changes are allowed. Recovery from a broken admin grant map uses the control-plane management key, which is independent of this data-plane principal. |

Grant changes and key minting for ordinary principals enforce **attenuation only** at the store layer: key grants must be a subset of the bound principal's grants on **every** mint path (management nested mint and self-service **`POST /keys`**).

## API keys

Data-plane keys are always **bound to a principal**. Mint them under that principal on every path (management nested mint, Cloud broker, or self-service `POST /{ctx}/keys`). Keys with no principal binding are rejected with **`401`**.

### Mint a key (principal-nested)

```http
POST /api/v1/contexts/{context_id}/principals/{principal_id}/keys/{key_name}?ttl_seconds=2592000
Content-Type: application/json

{
  "grants": {
    "memory:read": [{ "org": "acme", "agent": "planner" }]
  }
}
```

Optional **`grants`** in the body **attenuate** (narrow) the principal’s grants per verb (intersect only - widening returns `400`). Omit grants to inherit the principal’s full region. The secret is returned once.

Duplicate key names within a Context return **`409 Conflict`** (names are unique per Context, not per principal).

### List keys for a principal

```http
GET /api/v1/contexts/{context_id}/principals/{principal_id}/keys
```

Each entry includes **`id`**, **`name`**, and optional **`grants`** - the per-key attenuating grant map. When **`grants`** is omitted, the key inherits the principal's grants wholesale; when present, it only narrows per verb.

### Rotate or delete (principal-nested)

```http
POST   /api/v1/contexts/{context_id}/principals/{principal_id}/keys/{key_name}/rotate?ttl_seconds=2592000
DELETE /api/v1/contexts/{context_id}/principals/{principal_id}/keys/{key_name}
```

On nested rotate/delete, a key that does not exist, is unbound, or belongs to a **different** principal returns **`404`** (no cross-principal leakage).

### Context-wide operator views

Flat list endpoints remain for operators auditing all keys in a Context:

```http
GET    /api/v1/contexts/{context_id}/keys
DELETE /api/v1/contexts/{context_id}/keys/{key_name}
POST   /api/v1/contexts/{context_id}/keys/{key_name}/rotate?ttl_seconds=2592000
```

List responses include optional **`grants`** on each key (same semantics as the principal-nested list).

The secret is never returned after creation except on mint and rotate.

**Principal types:**

| Principal | Capabilities |
|---|---|
| `agent` | Create sessions, add turns, recall context, search knowledge. Cannot persist reflections or manage keys. |
| `supervisor` | All agent capabilities plus persisting reflections within the supervisor’s grant region. |

### Cloud-brokered access tokens

For SurrealDB Studio and other Cloud proxies that must mint a **short-lived, member-scoped** key in one round-trip without handing every member a management key:

```http
POST /api/v1/contexts/{context_id}/access-tokens
Content-Type: application/json

{
  "external_id": "surreal-cloud:usr_01HF…",
  "display_name": "Alice",
  "ttl_seconds": 3600,
  "grants": {
    "memory:read": [{ "org": "acme", "user": "alice" }],
    "memory:write": [{ "org": "acme", "user": "alice" }]
  }
}
```

Composes **create-or-get principal by `external_id`**, optional grant re-assertion, and **mint a bound key**. **`ttl_seconds` is required** - brokered tokens cannot be unbounded. Re-broker before expiry; there are no refresh tokens. The plaintext secret is returned once.

Member-facing **self-service** key management (after a brokered or admin-minted key is held) lives on the [REST API](/docs/agent-memory/reference/rest-api.md#self-service-keys-and-me) - not the management API. **`issue_access_token`** (Cloud broker path) emits a server warning when the caller sends no grants and the resulting principal is still entirely zero-grant - diagnostic only, behaviour unchanged.

## Knowledge upload (management-only)

Document ingestion requires a management key or an agent key with explicit upload capability. See [Uploading documents](/docs/agent-memory/ingest/authoritative/uploading-documents.md) for the full upload API.

```http
POST /api/v1/{context_id}/documents
Content-Type: multipart/form-data
Authorization: Bearer <key>

file=@document.pdf
title=Product Catalogue
scope[org]=acme
```

## Lifecycle operations

### Trigger decay pass

Runs the lifecycle decay pass immediately, expiring `context`-category memories past their TTL and pruning expired semantic cache entries:

```http
POST /api/v1/contexts/{context_id}/lifecycle/decay
```

### Force reindex

Triggers re-embedding and re-indexing of all chunks in a Context (e.g. after changing the embedding model):

```http
POST /api/v1/contexts/{context_id}/lifecycle/reindex
```

This is a long-running operation. Poll the returned job ID to track progress.

## Pagination

List endpoints return the full result set as a JSON array. There is no cursor or
`limit` parameter, and no pagination envelope:

```http
GET /api/v1/contexts/{context_id}/keys
```

**Response:**
```json
[
  { "id": "cak_01HF...", "name": "primary", "grants": ["memory:write"] }
]
```
