# API Reference

Complete reference for all AgentPlaybooks API endpoints.

## Base URL

```
https://apbks.com/api
```

For self-hosted instances, replace with your domain.

### Playbook identifiers

A playbook has two identifiers: its **GUID**, the short id in every URL and on
the Integrations tab (for example `a1b2c3d4e5f60718`), and its **id**, a UUID.
Every endpoint accepts either one wherever a path says `:guid` or `:id`, so use
whichever you have; the GUID is the one to share.

## Agent Reliability Loop

For production agent workflows, use this loop:

1. **Read fresh context** at task start (`GET /playbooks/:guid` in your preferred format, or MCP resources/tools).
2. **Execute with minimal context** (target specific skills/memory keys instead of loading everything).
3. **Write back durable learnings** (`PUT /playbooks/:guid/memory/:key`) with tags and description.
4. **Store long-form notes in canvas** (`/api/playbooks/:guid/canvas*`) and keep memory compact.

This mirrors the same reliability principle used in modern context systems: fresh docs + persistent learnings.

---

## Authentication

AgentPlaybooks supports two authentication methods:

### 1. User Authentication (JWT)

For dashboard and management endpoints. Pass Supabase JWT token:

```http
Authorization: Bearer <supabase_jwt_token>
```

### 2. Playbook API Key Authentication

For playbook memory write-back endpoints on a specific playbook:

```http
Authorization: Bearer apb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

### 3. User API Key Authentication

For programmatic access to ALL your playbooks (used by AI agents for management):

```http
Authorization: Bearer apb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

User API Keys work with the Management API (`/api/manage/*`) and Management MCP Server (`/api/mcp/manage`).

See [Management API & MCP](./management-api.md) for details.

### Human editor authorization

Signed-in editors use the same Supabase JWT/session authentication as owners. Their accepted membership authorizes content edits only. Visibility, collaborators, playbook API keys, secrets, ownership, and deletion are always owner-only. Playbook API keys cannot manage human collaborators.

---

## Connect with ChatGPT (Custom GPT Actions)

You can connect any public playbook to ChatGPT as a Custom GPT Action. This allows ChatGPT to use your playbook's skills, read/write memory, and follow your persona's instructions.

### Step-by-Step Setup

1. **Open ChatGPT GPT Builder**
   - Go to [chat.openai.com](https://chat.openai.com)
   - Click "Explore GPTs" → "Create"
   - Or edit an existing GPT

2. **Add an Action**
   - Go to **Configure** tab
   - Scroll to **Actions** → Click **Create new action**

3. **Import OpenAPI Specification**
   - Click **Import from URL**
   - Enter your playbook's OpenAPI URL:
   ```
   https://apbks.com/api/playbooks/{your-guid}?format=openapi
   ```
   - ChatGPT will automatically parse the available endpoints

4. **Set Up Authentication**
   - Under **Authentication**, select **API Key**
   - **Auth Type**: `Bearer`
   - **API Key**: Your playbook API key (e.g., `apb_live_xxxxx...`)
   
   > 💡 Create an API key at: Dashboard → Your Playbook → Settings → API Keys

5. **Configure Privacy Policy** (optional)
   - Add a privacy policy URL if publishing publicly

6. **Save and Test**
   - Click **Save**
   - Test by asking ChatGPT to use one of your playbook's skills

### What ChatGPT Can Do

Once connected, ChatGPT can:

| Action | Endpoint | Description |
|--------|----------|-------------|
| Read Memory | `GET /memory` | Access stored data |
| Search Memory | `GET /memory?search=...&tags=...` | Find specific entries |
| Write Memory | `PUT /memory/:key` | Save new data (requires API key) |
| Delete Memory | `DELETE /memory/:key` | Remove entries |
| List Skills | `GET /skills` | See available capabilities |
| Get Skill | `GET /skills/:id` | Get skill metadata and SKILL.md content |
| List Personas | `GET /personas` | Get AI personality instructions |

### Example: Using Memory

Ask ChatGPT:
> "Save my preference for dark mode to the playbook memory"

ChatGPT will call:
```http
PUT /api/playbooks/your-guid/memory/user_preferences
Authorization: Bearer apb_live_xxxxx
Content-Type: application/json

{
  "value": { "theme": "dark" },
  "tags": ["preferences", "ui"],
  "description": "User's display preferences"
}
```

### Security Notes

- **API Key** is stored encrypted by OpenAI
- Keys only grant access to the specific playbook
- Use separate keys for different integrations
- Rotate keys periodically via Dashboard

### OpenAPI Spec Contents

The `?format=openapi` response includes:

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "Your Playbook Name",
    "description": "Your playbook description"
  },
  "servers": [{ "url": "https://apbks.com/api" }],
  "paths": { /* memory, skills, personas endpoints */ },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key starting with apb_live_"
      }
    }
  },
  "x-playbook": {
    "guid": "your-guid",
    "persona": { /* embedded persona data */ },
    "skills": [/* full instructional skill records */],
    "mcp_servers": [/* MCP server configs */]
  }
}
```

The `x-playbook` extension provides full context to the AI, including system prompts, instructional skills, and federated tool metadata.

---

## Public Endpoints

These endpoints don't require authentication. They only work for playbooks marked as public.

### Get Playbook

Retrieve a playbook with all its contents.

```http
GET /api/playbooks/:guid
```

**Query Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `format` | string | `json` | Output format: `json`, `openapi`, `mcp`, `anthropic`, `markdown` |

**Response (JSON):**

```json
{
  "id": "uuid",
  "guid": "abc123def456",
  "name": "My AI Assistant",
  "description": "A helpful coding assistant",
  "is_public": true,
  "persona": {
    "id": "uuid",
    "name": "Coder",
    "system_prompt": "You are a helpful coding assistant..."
  },
  "skills": [
    {
      "id": "uuid",
      "name": "Code review guide",
      "description": "Review code for issues",
      "content": "# Code review\nFollow these steps...",
      "licence": "MIT",
      "priority": 80
    }
  ],
  "mcp_servers": [
    {
      "id": "uuid",
      "name": "file_tools",
      "tools": [...],
      "resources": [...]
    }
  ]
}
```

**Note:** AgentPlaybooks uses **1 playbook = 1 persona**. For backward compatibility, some endpoints still expose `GET /personas` which returns an array (length 1).

**Response (OpenAPI):**

Returns an OpenAPI 3.1 specification suitable for ChatGPT Custom Actions.

**Response (MCP):**

Returns an MCP-compatible server manifest with tools and resources.

**Response (Anthropic):**

Returns Anthropic-compatible federated tool definitions, instructional skills, and system prompt.

```json
{
  "playbook": {
    "name": "My AI Assistant",
    "description": "...",
    "guid": "abc123def456"
  },
  "system_prompt": "## Coder\n\nYou are a helpful...",
  "tools": [
    {
      "name": "SERVER_NAME__code_review",
      "description": "Review code for issues",
      "input_schema": { ... }
    }
  ],
  "mcp_servers": [...]
}
```

**Response (Markdown):**

Returns a human-readable Markdown document.

### Get Memory (Public Read with Search)

```http
GET /api/playbooks/:guid/memory
GET /api/playbooks/:guid/memory?key=specific_key
GET /api/playbooks/:guid/memory?search=user
GET /api/playbooks/:guid/memory?tags=settings,preferences
```

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | string | Get specific memory by key |
| `search` | string | Search in keys and descriptions |
| `tags` | string | Filter by tags (comma-separated, any match) |

**Response:**

```json
[
  {
    "key": "user_preferences",
    "value": { "theme": "dark" },
    "tags": ["settings", "user"],
    "description": "User preference settings",
    "updated_at": "2024-01-15T10:30:00Z"
  }
]
```

### List Skills (Public Read)

Get all skills (capabilities/rules) for a playbook:

```http
GET /api/playbooks/:guid/skills
```

**Response:**

```json
[
  {
    "id": "uuid",
    "name": "Code review guide",
    "description": "Review code for issues and improvements",
    "content": "# Code review\nInspect correctness, security, and tests.",
    "licence": "MIT",
    "priority": 80
  }
]
```

### Get Skill by ID

```http
GET /api/playbooks/:guid/skills/:skillId
```

Supports lookup by UUID or skill name.

### List Personas (Public Read)

```http
GET /api/playbooks/:guid/personas
```

**Response:**

```json
[
  {
    "id": "uuid",
    "name": "Coder",
    "system_prompt": "You are a helpful coding assistant...",
    "metadata": {}
  }
]
```

---

## Authenticated Endpoints (User JWT Required)

These endpoints require a valid Supabase JWT token for the logged-in user.

### List Playbooks

```http
GET /api/playbooks
```

**Response:**

```json
[
  {
    "id": "uuid",
    "guid": "abc123def456",
    "name": "My AI Assistant",
    "description": "A helpful coding assistant",
    "is_public": false,
    "persona_count": 1,
    "skill_count": 5,
    "mcp_server_count": 1,
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-15T12:00:00Z"
  }
]
```

### Create Playbook

```http
POST /api/playbooks
Content-Type: application/json

{
  "name": "My New Playbook",
  "description": "Optional description",
  "is_public": false,
  "config": {}
}
```

**Response (201):**

```json
{
  "id": "uuid",
  "guid": "xyz789abc123",
  "name": "My New Playbook",
  "description": "Optional description",
  "is_public": false,
  "config": {},
  "created_at": "2024-01-15T10:00:00Z",
  "updated_at": "2024-01-15T10:00:00Z"
}
```

### Update Playbook

```http
PUT /api/playbooks/:id
Content-Type: application/json

{
  "name": "Updated Name",
  "description": "Updated description",
  "is_public": true
}
```

### Delete Playbook

```http
DELETE /api/playbooks/:id
```

**Response:**

```json
{ "success": true }
```

---

## Persona (Embedded in Playbook)

**Architecture: 1 Playbook = 1 Persona**

Each playbook has exactly one persona embedded directly in the playbook record. This simplifies the architecture and ensures consistent behavior.

### Get Persona

The persona is returned directly with the playbook:

```http
GET /api/playbooks/:guid
```

Returns:

```json
{
  "id": "uuid",
  "guid": "abc123",
  "name": "My Playbook",
  "persona_name": "Expert Coder",
  "persona_system_prompt": "You are a helpful coding assistant...",
  "persona_metadata": { "avatar": "👨‍💻" },
  "instructions": "# Project rules\n\n- Use pnpm.",
  ...
}
```

`persona_system_prompt` carries the agent's identity; `instructions` carries the
always-on rules of this project (`AGENTS.md` / `CLAUDE.md` content). They are
stored as separate fields, and clients that need one system prompt receive them
composed persona-first.

### List Personas (Backward Compatibility)

For backward compatibility with existing integrations, a `/personas` endpoint is still available but returns a single-element array:

```http
GET /api/playbooks/:id/personas
```

**Response:**

```json
[
  {
    "id": "uuid",
    "playbook_id": "uuid",
    "name": "Expert Coder",
    "system_prompt": "You are a helpful coding assistant...",
    "metadata": { "avatar": "👨‍💻" }
  }
]
```

### Update Persona

Update the persona via playbook update:

```http
PUT /api/playbooks/:id
Content-Type: application/json

{
  "persona_name": "Updated Name",
  "persona_system_prompt": "Updated prompt...",
  "persona_metadata": { "avatar": "🤖" }
}
```

Or via the legacy persona endpoint (updates the playbook's embedded persona):

```http
PUT /api/playbooks/:id/personas/:id
Content-Type: application/json

{
  "name": "Updated Name",
  "system_prompt": "Updated prompt..."
}
```

**Note:** Persona cannot be deleted separately - it's part of the playbook. Clear the values to reset to defaults.

---

## Skills CRUD (Authenticated)

Manage skills within a playbook you own.

### List Skills

```http
GET /api/playbooks/:id/skills
```

### Create Skill

```http
POST /api/playbooks/:id/skills
Content-Type: application/json

{
  "name": "Code review guide",
  "description": "Use when reviewing a proposed code change",
  "content": "# Code review\n1. Inspect correctness...",
  "licence": "MIT",
  "priority": 80
}
```

### Update Skill

```http
PUT /api/playbooks/:id/skills/:sid
Content-Type: application/json

{
  "description": "Updated description",
  "content": "# Updated instructions\n...",
  "priority": 90
}
```

### Delete Skill

```http
DELETE /api/playbooks/:id/skills/:sid
```

---

## Memory Write Endpoints (API Key or Owner)

Write operations require either an API key with `memory:write` permission or owner authentication.

### Write Memory

```http
PUT /api/playbooks/:guid/memory/:key
Authorization: Bearer apb_live_xxx
Content-Type: application/json

{
  "value": { "theme": "dark", "language": "en" },
  "tags": ["settings", "user"],
  "description": "User preference settings"
}
```

**Request Body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `value` | object | Yes | Any JSON value to store |
| `tags` | string[] | No | Tags for categorization and search |
| `description` | string | No | Human-readable description |

**Response:**

```json
{
  "key": "user_preferences",
  "value": { "theme": "dark", "language": "en" },
  "tags": ["settings", "user"],
  "description": "User preference settings",
  "updated_at": "2024-01-15T10:30:00Z"
}
```

**Note:** If the key already exists, it will be updated (upsert behavior). Tags and description can be updated along with the value.

### Delete Memory

```http
DELETE /api/playbooks/:guid/memory/:key
Authorization: Bearer apb_live_xxx
```

**Response:**

```json
{ "success": true }
```

---

## Human Collaboration (Session Authenticated)

Owners can invite human editors without sharing a bearer API key. These endpoints require a signed-in Supabase session/JWT; playbook API keys cannot manage collaborators.

### List editors and invitations

```http
GET /api/playbooks/:id/collaborators
Authorization: Bearer <supabase_jwt_token>
```

Owner only. Returns active editors and pending or expired invitations. Raw invite tokens are never returned by this endpoint.

### Create an editor invitation

```http
POST /api/playbooks/:id/collaborators
Authorization: Bearer <supabase_jwt_token>
```

**Response (201):**

```json
{
  "id": "uuid",
  "invite_expires_at": "2026-07-24T12:00:00.000Z",
  "created_at": "2026-07-21T12:00:00.000Z",
  "status": "pending",
  "invite_path": "/invite/one-time-token",
  "warning": "This invite link is shown once and expires in 72 hours."
}
```

The link can be accepted once and expires after 72 hours. A playbook can have at most 25 active editor and pending invite rows. Copy the URL immediately and treat it as a temporary credential.

### Preview an invitation

```http
GET /api/collaboration-invites/:token
```

Returns only the playbook name and expiry for a valid pending invitation. Invalid, expired, revoked, or already accepted tokens return `404`.

### Accept an invitation

```http
POST /api/collaboration-invites/:token
Authorization: Bearer <supabase_jwt_token>
```

Atomically binds the invitation to the signed-in account. Owners cannot accept an invitation to their own playbook. Reuse returns `404` or `409` depending on whether another request won the acceptance race.

### Remove an editor or revoke an invitation

```http
DELETE /api/playbooks/:id/collaborators/:collaboratorId
Authorization: Bearer <supabase_jwt_token>
```

Owner only. Revocation ends membership access immediately but does not revoke any separately issued API key.

See [Team Collaboration](./team-collaboration.md) for the permission matrix and operational guidance.

---

## API Keys Management (Authenticated)

Manage API keys for your playbooks. Only the playbook owner can access these endpoints.

### List API Keys

```http
GET /api/playbooks/:id/api-keys
```

**Response:**

```json
[
  {
    "id": "uuid",
    "key_prefix": "apb_live_xxx...",
    "name": "Production Key",
    "permissions": ["memory:read", "memory:write"],
    "last_used_at": "2024-01-15T10:00:00Z",
    "expires_at": null,
    "is_active": true,
    "created_at": "2024-01-01T00:00:00Z"
  }
]
```

### Create API Key

```http
POST /api/playbooks/:id/api-keys
Content-Type: application/json

{
  "name": "Production Key",
  "permissions": ["memory:read", "memory:write", "skills:write"],
  "expires_at": "2025-01-01T00:00:00Z"
}
```

**Available Permissions:**

| Permission | Description |
|------------|-------------|
| `memory:read` | Read memory entries |
| `memory:write` | Write/delete memory entries |
| `skills:write` | Add/update skills |
| `full` | All permissions |

**Response (201):**

```json
{
  "id": "uuid",
  "key_prefix": "apb_live_xxx...",
  "name": "Production Key",
  "permissions": ["memory:read", "memory:write", "skills:write"],
  "expires_at": "2025-01-01T00:00:00Z",
  "is_active": true,
  "created_at": "2024-01-15T10:00:00Z",
  "key": "apb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "warning": "Save this key now! It will not be shown again."
}
```

⚠️ **Important:** The full API key is only returned once at creation. Store it securely!

### Delete API Key

```http
DELETE /api/playbooks/:id/api-keys/:kid
```

---

## Secrets

Encrypted credential storage for playbook tools and integrations. Values are AES-256-GCM encrypted with per-user derived keys.

**Security model:** Secret values are AES-256-GCM encrypted. By default, agents reference secrets by name and use the `use_secret` (GET, HEAD) and `use_secret_write` (POST, PUT, PATCH, DELETE) MCP tools, or the `/proxy` REST endpoint, to inject secrets into HTTP requests server-side. Agents can only access the raw secret value if the `allow_api_key_reveal` flag is set to true for that specific secret.

> 📘 **Python integration guide:** See [Python Examples](./secrets-python-examples.md) for ready-to-use code covering proxy, reveal, store, rotate, and delete operations.

### List Secrets

```http
GET /api/playbooks/:guid/secrets
GET /api/playbooks/:guid/secrets?category=api_key
Authorization: Bearer <jwt_or_api_key>
```

Returns metadata only (names, categories, usage stats). Never returns secret values.

### Store Secret

```http
POST /api/playbooks/:guid/secrets
Authorization: Bearer <jwt_or_api_key>
Content-Type: application/json

{
  "name": "OPENAI_API_KEY",
  "value": "sk-...",
  "description": "OpenAI API key for GPT-4",
  "category": "api_key",
  "expires_at": "2027-01-01T00:00:00Z",
  "allow_api_key_reveal": false
}
```

The `value` is encrypted immediately and never stored in plaintext.

`expires_at` is optional and must carry a timezone (`Z` or `+02:00`); a time without one is refused with `400`, because the server would otherwise have to guess which instant it means. Once it passes, the secret can no longer be used: the proxy, `use_secret`, federated servers, and reveal by API key all refuse it with `403`, and the refusal is recorded in the audit log. The owner can still reveal it in the dashboard, in order to replace it.

### Reveal Secret

```http
GET /api/playbooks/:guid/secrets/reveal/:name
Authorization: Bearer <jwt_or_api_key>
```

Returns the decrypted value. If called with an API key, the request will only succeed if the secret has `allow_api_key_reveal` set to true. Otherwise, it will return a 403 Forbidden error (Proxy Only mode).

### Load Runtime Secrets

For application startup loading, use `POST /api/playbooks/:guid/secrets/resolve`
with `names`, optional `optional_names`, and `auth_mode` (`api_key` or `mtls`).
API keys require `secrets:read`. Certificate clients require a Cloudflare-verified,
owner-registered certificate with explicit allowed names. Both require the secret's
`allow_api_key_reveal` flag and refuse expired secrets. The response contains `values`
and `missing_optional`; a failed batch contains no plaintext values. Maximum 100
distinct names. See [Python Secrets](./python-secrets.md) for the client package,
Docker examples, and owner-only certificate management endpoints.

### Use Secret (Proxy)

```http
POST /api/playbooks/:guid/secrets/proxy
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "secret_name": "OPENAI_API_KEY",
  "url": "https://api.openai.com/v1/models",
  "method": "GET",
  "header_name": "Authorization",
  "header_prefix": "Bearer "
}
```

Makes an HTTP request with the secret injected server-side into the request to the provider. The caller authenticates with the owner's session JWT or a playbook API key with `secrets:read` / `full` permission. The provider key is not included in the proxy response. Set `allowed_hosts` on the secret to trusted provider hosts; the provider controls its response body.

By default (`response_mode: "json"`), the response is buffered and returned as `{ "status": 200, "status_text": "OK", "body": ... }`. Existing callers keep this format.

#### Streaming REST/OpenAPI without MCP

For interactive model output, call this HTTP endpoint directly with **`response_mode: "stream"`**. APB forwards response bytes as they arrive, without assembling a JSON envelope, parsing SSE events, or truncating the output. This works with browser `fetch`, Python, curl, and other HTTP clients. Use the provider's own streaming option as well (often `body.stream: true`); the proxy cannot make a non-streaming provider generate incremental output.

The endpoint is also advertised as `proxySecretRequest` in each playbook's generated OpenAPI 3.1 document:

```http
GET /api/playbooks/:guid?format=openapi
```

The operation declares `response_mode`, the SSE, NDJSON, JSON, and binary response media types, and the `x-streaming: true` extension. Generated clients may still buffer responses by default, so configure the client to expose the raw response stream.

```bash
curl --no-buffer --request POST \
  "https://agentplaybooks.ai/api/playbooks/$PLAYBOOK_GUID/secrets/proxy" \
  --header "Authorization: Bearer $PLAYBOOK_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "secret_name": "MODEL_API_KEY",
    "url": "https://provider.example/v1/chat/completions",
    "method": "POST",
    "response_mode": "stream",
    "body": {"model": "MODEL_ID", "messages": [{"role": "user", "content": "Hello"}], "stream": true}
  }'
```

```javascript
// In the APB frontend: authFetch uses the signed-in owner's session.
import { authFetch } from "@/lib/auth-fetch";

const abort = new AbortController();
const response = await authFetch(`/api/playbooks/${guid}/secrets/proxy`, {
  method: "POST",
  signal: abort.signal, // Call abort.abort() from your Stop button.
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    secret_name: "MODEL_API_KEY",
    url: providerEndpoint,
    method: "POST",
    response_mode: "stream",
    body: { model: modelId, messages, stream: true },
  }),
});
if (!response.ok) throw new Error(`Model request failed (${response.status})`);
if (!response.body) throw new Error("Missing response stream");

const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
try {
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    // Raw provider text. Feed this into your provider's incremental SSE/NDJSON
    // parser: one network chunk may contain partial or multiple events.
    console.log(value);
  }
} finally {
  await reader.cancel();
  reader.releaseLock();
}
```

External browser applications use `fetch` with an authorized APB credential and must be in the deployment's `ALLOWED_ORIGINS`. The provider key stays in the vault. CLI/backend callers can use a scoped APB API key; no MCP client or session initialization is required.

Streaming mode preserves the upstream HTTP status, including JSON error responses such as 429. SSE, JSON, NDJSON and binary content types are retained; other content types are served as `application/octet-stream` with `nosniff`. Cookies, redirects and arbitrary provider headers are not forwarded. `Retry-After` is retained. APB validation errors before streaming remain JSON errors. An upstream failure or timeout after headers have been sent terminates the stream; consumers must handle read failures.

`timeout_ms` covers the upstream request and response stream: streaming defaults to 300,000 ms with the same maximum; buffered mode defaults to 30,000 ms and is capped at 60,000 ms. Hosting infrastructure may impose a shorter limit. Cancelling the response or aborting the request cancels upstream work. Forwarding follows consumer demand, and usage/audit writes happen at stream completion or interruption, so they do not delay the first chunk. Responses disable caching and request that intermediaries avoid transformation/buffering.

[MCP itself supports SSE transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports), but APB's `use_secret` / `use_secret_write` tools currently collect a response into one tool result. Use those tools for agent workflows; use direct HTTP streaming for incremental model output. The vault's server-side credential injection does not depend on MCP.

### Rotate Secret

```http
PUT /api/playbooks/:guid/secrets/:name
Authorization: Bearer <jwt_or_api_key>
Content-Type: application/json

{
  "value": "sk-new-key-...",
  "description": "Updated key"
}
```

Rotating keeps the expiry: it is the owner's rule, not part of the value. To bring an expired secret back, send a later `expires_at`, or `null` to remove it.

### Delete Secret

```http
DELETE /api/playbooks/:guid/secrets/:name
Authorization: Bearer <jwt>
```

Owner only. Permanently deletes the secret.

### Audit Trail

Vault operations are recorded in the same `audit_logs` trail as federated MCP calls, namespaced
`secret.*` in `operation`, and read at the shared endpoint:

```http
GET /api/playbooks/:guid/audit?operation=secret.
GET /api/playbooks/:guid/audit?operation=secret.use,secret.reveal&secret=OPENAI_API_KEY&limit=200
Authorization: Bearer <jwt_or_user_api_key>
```

Every operation — `secret.create`, `secret.rotate`, `secret.update`, `secret.delete`,
`secret.reveal`, `secret.use`, `secret.list` — is recorded, including the ones that were refused.
Each entry carries the outcome (`success`, `denied`, `error`), who acted (the owner's user id, or
an API key's prefix), the destination **host** in `target` for `secret.use`, and a short
`error_code` such as `not_authorized`, `destination_not_allowed` or
`reveal_not_permitted_for_api_key`.

Entries never contain a secret value, a full outbound URL (a path or query string carries data
of its own), or an API key.

Owner access only: a session, or a **user** API key with `playbooks:read`. A playbook API key —
the credential agents use to perform vault operations — cannot read the record of them.

```json
{
  "logs": [
    {
      "id": "…",
      "mcp_server_id": null,
      "operation": "secret.use",
      "target": "attacker.example.com",
      "status": "denied",
      "error_code": "destination_not_allowed",
      "actor_type": "api_key",
      "actor_id": "apb_live_a1b2",
      "secret_name": "OPENAI_API_KEY",
      "request_id": "…",
      "created_at": "2026-08-19T09:12:44.000Z"
    }
  ]
}
```

`limit` defaults to 100 and is capped at 500. `operation` takes a comma-separated list of exact
names, or a single trailing-dot prefix (`secret.`) to mean every vault event; `secret` filters by
name, which keeps working after the secret itself is deleted. The same endpoint answers at the
original `GET /api/mcp/audit/:guid`.

### Secret Categories

| Category | Description |
|----------|-------------|
| `api_key` | API keys (OpenAI, Anthropic, etc.) |
| `password` | Database or service passwords |
| `token` | OAuth tokens, webhook tokens |
| `certificate` | TLS/SSL certificates |
| `connection_string` | Database connection strings |
| `general` | Other credentials |

---

## User API Keys (Account-level)

User API Keys provide access to ALL your playbooks, enabling AI agents to create and manage playbooks programmatically. Manage these at [Dashboard Settings](https://apbks.com/dashboard/settings).

### List User API Keys

```http
GET /api/user/api-keys
```

### Create User API Key

```http
POST /api/user/api-keys
Content-Type: application/json

{
  "name": "Claude Desktop",
  "permissions": ["playbooks:read", "playbooks:write", "skills:write", "memory:read", "memory:write"]
}
```

**Available Permissions:**

| Permission | Description |
|------------|-------------|
| `playbooks:read` | List and view your playbooks |
| `playbooks:write` | Create, update, delete playbooks (incl. persona) |
| `skills:write` | Add/update/delete skills |
| `memory:read` | Read memory entries |
| `memory:write` | Write/delete memory entries |
| `full` | All permissions |

**Response (201):**

```json
{
  "id": "uuid",
  "key_prefix": "apb_live_xxx...",
  "name": "Claude Desktop",
  "permissions": ["playbooks:read", "playbooks:write", "skills:write", "memory:read", "memory:write"],
  "is_active": true,
  "created_at": "2024-01-15T10:00:00Z",
  "key": "apb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "warning": "Save this key now! It will not be shown again."
}
```

### Delete User API Key

```http
DELETE /api/user/api-keys/:kid
```

---

## Management API (User API Key)

For programmatic playbook management by AI agents. See [Management API Documentation](./management-api.md) for details.

| Endpoint | Description |
|----------|-------------|
| `GET /api/manage/playbooks` | List all playbooks |
| `POST /api/manage/playbooks` | Create playbook |
| `GET /api/manage/playbooks/:id` | Get playbook with contents |
| `PUT /api/manage/playbooks/:id` | Update playbook (incl. persona fields) |
| `DELETE /api/manage/playbooks/:id` | Delete playbook |
| `POST /api/manage/playbooks/:id/skills` | Add skill |
| `PUT/DELETE /api/manage/playbooks/:id/skills/:sid` | Update/Delete skill |
| `GET /api/manage/playbooks/:id/memory` | List/search memories |
| `GET /api/manage/playbooks/:id/memory/:key` | Get specific memory |
| `PUT /api/manage/playbooks/:id/memory/:key` | Write memory |
| `DELETE /api/manage/playbooks/:id/memory/:key` | Delete memory |
| `GET /api/manage/openapi.json` | OpenAPI specification |

**MCP Server:** `/api/mcp/manage`

---

## Skill Attachments

Skills can have file attachments for additional context (code files, docs, etc.).

### List Attachments

```http
GET /api/manage/skills/:skillId/attachments
```

### Get Attachment

```http
GET /api/manage/skills/:skillId/attachments/:attachmentId
GET /api/manage/skills/:skillId/attachments/:attachmentId?raw=true
```

### Create Attachment

```http
POST /api/manage/skills/:skillId/attachments
Content-Type: application/json

{
  "filename": "helper.py",
  "content": "def helper(): ...",
  "file_type": "python",
  "description": "Helper functions"
}
```

**Supported file types:** `typescript`, `javascript`, `python`, `go`, `rust`, `sql`, `markdown`, `json`, `yaml`, `text`, `cursorrules`, `shell`

**Limits:** 50KB per file, 10 files per skill

### Update Attachment

```http
PUT /api/manage/skills/:skillId/attachments/:attachmentId
```

### Delete Attachment

```http
DELETE /api/manage/skills/:skillId/attachments/:attachmentId
```

## MCP Endpoints

Model Context Protocol endpoints for Claude and MCP-compatible clients.

### Get MCP Manifest

```http
GET /api/mcp/:guid
```

**Response:**

```json
{
  "protocolVersion": "2025-03-26",
  "serverInfo": {
    "name": "My AI Assistant",
    "version": "1.0.0"
  },
  "capabilities": {
    "tools": {},
    "resources": {}
  },
  "tools": [
    {
      "name": "code_review",
      "description": "Review code for issues",
      "inputSchema": { ... }
    }
  ],
  "resources": [
    {
      "uri": "playbook://abc123/memory",
      "name": "Memory",
      "mimeType": "application/json"
    }
  ]
}
```

### MCP JSON-RPC

```http
POST /api/mcp/:guid
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}
```

**Supported Methods:**

| Method | Description |
|--------|-------------|
| `initialize` | Initialize MCP connection |
| `tools/list` | List available tools |
| `resources/list` | List available resources |
| `resources/read` | Read a resource |
| `tools/call` | Call a tool (limited) |

---

## Public Repository

### Browse Skills

```http
GET /api/public/skills
GET /api/public/skills?tags=coding,automation
GET /api/public/skills?search=code+review
```

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `tags` | string | Comma-separated tags to filter by |
| `search` | string | Search in name and description |

**Response:**

```json
[
  {
    "id": "uuid",
    "name": "Code Reviewer",
    "description": "Reviews code for bugs and improvements",
    "tags": ["coding", "review"],
    "usage_count": 1234,
    "is_verified": true
  }
]
```

### Get Skill Details

```http
GET /api/public/skills/:id
```

### Browse MCP Servers

```http
GET /api/public/mcp
GET /api/public/mcp?tags=database,automation
```

### Get MCP Server Details

```http
GET /api/public/mcp/:id
```

---

## Agent Skills Discovery (`.well-known/skills`)

The [`.well-known/skills`](https://agentskills.io/specification) convention lets
any agent client install skills straight from a website — no registry, no
sign-up, no credential. Point a client at a base URL and it reads the index.

```http
GET /.well-known/skills/index.json
GET /.well-known/skills/:name/SKILL.md
GET /.well-known/skills/:name/:file
```

The same three routes exist per playbook, which is the form to hand a team: it
publishes exactly that playbook's skills, with no chance of another publisher's
name shadowing one of theirs.

```http
GET /playbooks/:guid/.well-known/skills/index.json
GET /playbooks/:guid/.well-known/skills/:name/SKILL.md
```

**Index response:**

```json
{
  "skills": [
    {
      "name": "code-review",
      "description": "Review a diff for bugs. Use when a pull request needs review.",
      "files": ["SKILL.md", "references/CHECKLIST.md"]
    }
  ]
}
```

`SKILL.md` is served as `text/markdown` and is the complete document, including
frontmatter fields outside the Agent Skills spec (`version`, `platforms`,
`metadata.<client>.*`) exactly as the author wrote them. `files` lists what the
skill bundles; each is fetched from `:name/:file` and is restricted to the spec's
directories (`scripts/`, `references/`, `assets/`, `examples/`, `templates/`).

Notes:

- **Public playbooks only.** These routes are unauthenticated, so a private or
  unlisted playbook is never published through them — use the CLI's `pull` for
  those.
- **Names are unique.** A skill name is a directory name, so the site-wide index
  serves one skill per name (the most recent) rather than the same name twice.
- Responses are cacheable (`max-age=300`) and CORS-enabled (`GET`, `OPTIONS`).

**Example — installing into Hermes Agent:**

```bash
hermes skills search https://agentplaybooks.ai/playbooks/<guid> --source well-known
hermes skills install well-known:https://agentplaybooks.ai/playbooks/<guid>/.well-known/skills/<name>
```

---

## Health Check

```http
GET /api/health
```

**Response:**

```json
{
  "status": "ok",
  "timestamp": "2024-01-15T10:00:00.000Z"
}
```

---

## Error Responses

All endpoints return consistent error responses:

```json
{
  "error": "Error message here"
}
```

**HTTP Status Codes:**

| Code | Description |
|------|-------------|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid or missing authentication |
| 403 | Forbidden - No permission for this resource |
| 404 | Not Found - Resource doesn't exist or is private |
| 500 | Internal Server Error |

---

## Rate Limits

Application-level rate limits are planned but are not currently part of the API contract.
The hosting platform may still throttle abusive traffic. Clients should handle `429 Too Many
Requests` responses with exponential backoff and must not rely on `X-RateLimit-*` headers until
versioned limits are documented here.

---

## Endpoint Summary

### Playbook CRUD

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `GET` | `/api/playbooks` | JWT | List user's playbooks |
| `POST` | `/api/playbooks` | JWT | Create playbook |
| `GET` | `/api/playbooks/:guid` | Public* | Get playbook (format param) |
| `PUT` | `/api/playbooks/:id` | JWT | Update playbook |
| `DELETE` | `/api/playbooks/:id` | JWT | Delete playbook |

### Personas, Skills, Memory

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `GET` | `/api/playbooks/:id/personas` | JWT/Public* | Get persona (singleton array) |
| `PUT` | `/api/playbooks/:id/personas/:pid` | JWT | Update embedded persona |
| `GET/POST` | `/api/playbooks/:id/skills` | JWT/Public* | List/Create skills |
| `PUT/DELETE` | `/api/playbooks/:id/skills/:sid` | JWT | Update/Delete skill |
| `GET` | `/api/playbooks/:guid/memory` | Public* | Read memory |
| `PUT` | `/api/playbooks/:guid/memory/:key` | API Key/JWT | Write memory |
| `DELETE` | `/api/playbooks/:guid/memory/:key` | API Key/JWT | Delete memory |

### API Keys

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `GET` | `/api/playbooks/:id/api-keys` | JWT | List playbook API keys |
| `POST` | `/api/playbooks/:id/api-keys` | JWT | Create playbook API key |
| `DELETE` | `/api/playbooks/:id/api-keys/:kid` | JWT | Delete playbook API key |
| `GET` | `/api/user/api-keys` | JWT | List user API keys |
| `POST` | `/api/user/api-keys` | JWT | Create user API key |
| `DELETE` | `/api/user/api-keys/:kid` | JWT | Delete user API key |

### Human Collaboration

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `GET` | `/api/playbooks/:id/collaborators` | Owner JWT | List editors and invitations |
| `POST` | `/api/playbooks/:id/collaborators` | Owner JWT | Create a one-time editor invite |
| `DELETE` | `/api/playbooks/:id/collaborators/:collaboratorId` | Owner JWT | Remove editor or revoke invite |
| `GET` | `/api/collaboration-invites/:token` | Invite token | Preview valid invite |
| `POST` | `/api/collaboration-invites/:token` | JWT + invite token | Accept invite |

### Management API (AI Automation)

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `GET` | `/api/manage/playbooks` | User Key | List all playbooks |
| `POST` | `/api/manage/playbooks` | User Key | Create playbook |
| `GET` | `/api/manage/playbooks/:id` | User Key | Get playbook with all contents |
| `PUT` | `/api/manage/playbooks/:id` | User Key | Update playbook (incl. persona) |
| `DELETE` | `/api/manage/playbooks/:id` | User Key | Delete playbook |
| `POST` | `/api/manage/playbooks/:id/skills` | User Key | Add skill |
| `PUT/DELETE` | `/api/manage/playbooks/:id/skills/:sid` | User Key | Update/Delete skill |
| `GET` | `/api/manage/playbooks/:id/memory` | User Key | List/search memories |
| `GET` | `/api/manage/playbooks/:id/memory/:key` | User Key | Get specific memory |
| `PUT` | `/api/manage/playbooks/:id/memory/:key` | User Key | Write memory with tags |
| `DELETE` | `/api/manage/playbooks/:id/memory/:key` | User Key | Delete memory |
| `GET` | `/api/manage/openapi.json` | None | OpenAPI specification |
| `GET/POST` | `/api/mcp/manage` | User Key | Management MCP Server |

### Skill Attachments

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `GET` | `/api/manage/skills/:sid/attachments` | Public*/User Key | List attachments |
| `GET` | `/api/manage/skills/:sid/attachments/:aid` | Public*/User Key | Get attachment |
| `POST` | `/api/manage/skills/:sid/attachments` | User Key | Upload attachment |
| `PUT` | `/api/manage/skills/:sid/attachments/:aid` | User Key | Update attachment |
| `DELETE` | `/api/manage/skills/:sid/attachments/:aid` | User Key | Delete attachment |

### Public Repository & MCP

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `GET` | `/api/public/skills` | None | Browse public skills |
| `GET` | `/api/public/mcp` | None | Browse public MCP servers |
| `GET` | `/api/public/playbooks` | None | Browse public playbooks |
| `GET/POST` | `/api/mcp/:guid` | Public* | Playbook MCP Server |
| `GET` | `/api/health` | None | Health check |

**Notes:**
- *Public endpoints work without auth for public playbooks, or with JWT for private playbooks you own.
- **User Key** = User API Key (created at /dashboard/settings)
- **API Key** = Playbook-specific API Key
