---
name: AgentDocs
description: Read, create, update, search, and share documentation on AgentDocs (agentdocs.eu or a self-hosted instance). Use when the user asks about their docs, wants to write or update pages, search across workspaces, or share documentation.
---

## Overview

AgentDocs is a collaborative documentation platform with a full REST API.
Base URL: `https://agentdocs.eu` (or your self-hosted instance)

You can create workspaces, organize pages in spaces, write Markdown content,
share pages via magic links, search across docs, and get notified of changes
via webhooks. Every feature available in the UI is also available via API.

## Prefer MCP? (recommended where available)

If your environment can run a local MCP server (Claude Code, Claude Desktop,
Cursor, Windsurf, Zed, Opencode, …), the official MCP server is the preferred integration —
native tools instead of hand-rolled curl calls. (On **Claude.ai web**, upload
this Skill instead — see Installation below; the stdio server can't be a remote
connector yet, though a hosted one is on the roadmap.)

```bash
claude mcp add agentdocs --env AGENTDOCS_TOKEN=<your-token> -- npx -y agentdocs-mcp
```

npm: https://www.npmjs.com/package/agentdocs-mcp · source: https://github.com/hoornet/agentdocs-mcp

**Updating:** the command is unpinned, so it always resolves the latest version —
just **restart your MCP client** to pick up a new release. If npx serves a stale
cached copy, run `npx -y agentdocs-mcp@latest` or `npm cache clean --force`.

This Skill file remains fully supported and is ideal where MCP isn't available
(plain HTTP scripts, CI, or skill-based setups).

## Installation

This file is a single Skill compatible with both Claude.ai and Claude Code.

### Claude.ai (web)
Open https://claude.ai → Skills → Upload Skill → drag in this file. Skill is then available in any conversation.

### Claude Code (CLI)
```bash
# Save this file as a user-level skill
mkdir -p ~/.claude/skills/agentdocs
curl -fsSL https://agentdocs.eu/agentdocs-skill.md > ~/.claude/skills/agentdocs/SKILL.md

# Configure credentials (one-time)
mkdir -p ~/.config/agentdocs

# Get your API token from agentdocs.eu → Profile → Regenerate API Token
# (the token is shown once at regen — save it directly into the file)
printf '%s' 'YOUR_API_TOKEN' > ~/.config/agentdocs/token
chmod 600 ~/.config/agentdocs/token

# Optional config: sets defaults so you don't have to specify them every time
cat > ~/.config/agentdocs/config.json <<'EOF'
{
  "instance_url": "https://agentdocs.eu",
  "workspace_slug": "your-workspace-slug",
  "space_slug": "your-default-space-slug"
}
EOF
chmod 600 ~/.config/agentdocs/config.json
```

In Claude Code, invoke with `/agentdocs`, or ask naturally: "list my workspaces", "search docs for X", "create a page", "write a session report".

## Authentication

Three methods (use one per request):
- **JWT**: `Authorization: Bearer <jwt_token>` (expires in 7 days, used for browser sessions)
- **Account API Token**: `Authorization: Token <api_token>` (long-lived, ideal for agents — full access to everything the user owns)
- **Space-scoped Token**: `Authorization: Token <space_token>` (editor-level access to exactly one space — same `Token` header; see "Space Tokens" below)

The account API token is **shown only once** — at user registration, at Nostr first-creation, and after explicit rotation via `POST /api/auth/regenerate-token`. **It is not returned by login.** If a user loses the token, they regenerate from the dashboard.

For Claude Code, the install instructions above keep the token in `~/.config/agentdocs/token`. Read it as:
```bash
AD_TOKEN=$(cat ~/.config/agentdocs/token)
```
Then use it in the Authorization header. Never echo the token into the conversation.

### Rotate the API token
```bash
curl -X POST https://agentdocs.eu/api/auth/regenerate-token \
  -H "Authorization: Bearer <jwt_token>"   # or current Token <api_token>
# Response: {"api_token": "new_token_here"}  ← shown once
```
The previous token is invalidated immediately. Save the new one into `~/.config/agentdocs/token`.

## Key Concepts

- **Workspace** — top-level container (like an org/team)
- **Space** — collection of related pages within a workspace
- **Page** — a Markdown document, can be nested (parent/child hierarchy)
- **Share Link** — public token-based URL to share a page without login
- **Space Token** — an API credential scoped to a single space (editor-level)
- **Webhook** — HTTP callback when pages are created/updated/deleted

## Common Workflows

### List workspaces
```bash
curl https://agentdocs.eu/api/workspaces \
  -H "Authorization: Token <api_token>"
```
Response: `{"workspaces": [{"id": "uuid", "name": "...", "slug": "..."}]}`

### List spaces in a workspace
```bash
curl https://agentdocs.eu/api/workspaces/{workspaceId}/spaces \
  -H "Authorization: Token <api_token>"
```
Response: `{"spaces": [{"id": "uuid", "name": "...", "slug": "..."}]}`

### List pages in a space
```bash
curl https://agentdocs.eu/api/spaces/{spaceId}/pages \
  -H "Authorization: Token <api_token>"
```
Add `?hierarchy=true` for nested tree structure.

### Get a page
```bash
curl https://agentdocs.eu/api/pages/{pageId} \
  -H "Authorization: Token <api_token>"
```
Response: `{"page": {"id": "uuid", "title": "...", "content": "# Markdown...", "version": 3}}`

### Create a page
```bash
curl -X POST https://agentdocs.eu/api/spaces/{spaceId}/pages \
  -H "Authorization: Token <api_token>" \
  -H "Content-Type: application/json" \
  -d '{"title": "My Page", "content": "# Hello\nMarkdown content here"}'
```
Optional fields: `slug` (auto-generated from the title when omitted — and deduped if it collides), `parent_page_id` (for nesting). Response 201: `{"page": {"id": "uuid", "title": "...", "slug": "...", "version": 1}}`.

### Update a page
```bash
curl -X PUT https://agentdocs.eu/api/pages/{pageId} \
  -H "Authorization: Token <api_token>" \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated Title", "content": "# New content"}'
```
Each update creates a new version automatically.

### Search across a workspace
```bash
curl "https://agentdocs.eu/api/workspaces/{workspaceId}/search?q=deployment" \
  -H "Authorization: Token <api_token>"
```
Response: `{"results": [{"id": "uuid", "title": "...", "slug": "...", "content_preview": "...with <<highlights>>...", "matched_in": ["title", "content"], "space_name": "...", "space_slug": "..."}]}`

Search-match highlights are delimited with `<<` `>>` markers (not HTML tags), and the snippet field is `content_preview`.

### Create a share link
```bash
curl -X POST https://agentdocs.eu/api/pages/{pageId}/share \
  -H "Authorization: Token <api_token>" \
  -H "Content-Type: application/json" \
  -d '{"expires_in_days": 30}'
```
Expiry is optional — pass `expires_in_days` (positive integer) **or** an explicit
ISO-8601 `expires_at` (e.g. `"2026-06-01T00:00:00Z"`); `expires_at` wins if both are
sent. Omit both for a non-expiring link.

Response 201: `{"share_link": {"id": "uuid", "token": "...", "url": "/shared/{token}", "expires_at": "ISO8601 or null", "created_at": "ISO8601"}}`. Note `url` is **relative** — prepend `https://agentdocs.eu`.

The shared page is then viewable at:
- JSON: `GET https://agentdocs.eu/api/shared/{token}`
- Raw Markdown: `GET https://agentdocs.eu/api/shared/{token}/raw` (no auth needed)

### List / revoke share links
```bash
curl https://agentdocs.eu/api/pages/{pageId}/shares \
  -H "Authorization: Token <api_token>"
```
Response: `{"share_links": [ ... ]}` (note the plural key, vs singular `share_link` on create). Revoke with `DELETE /api/pages/{pageId}/shares/{shareId}` → `{"message": "Share link deleted"}`.

### Read a shared page (no auth required)
```bash
curl https://agentdocs.eu/api/shared/{token}/raw
```
Returns clean Markdown with YAML frontmatter — ideal for agents.

### Delete a page
```bash
curl -X DELETE https://agentdocs.eu/api/pages/{pageId} \
  -H "Authorization: Token <api_token>"
```
Cascades to child pages.

### Bulk create pages (up to 500)
```bash
curl -X POST https://agentdocs.eu/api/spaces/{spaceId}/pages/bulk \
  -H "Authorization: Token <api_token>" \
  -H "Content-Type: application/json" \
  -d '{"pages": [{"title": "Page 1", "content": "..."}, {"title": "Page 2", "content": "..."}]}'
```
Response: `{"created": N, "pages": [...]}`.

### Bulk delete pages
```bash
curl -X DELETE https://agentdocs.eu/api/spaces/{spaceId}/pages/bulk \
  -H "Authorization: Token <api_token>" \
  -H "Content-Type: application/json" \
  -d '{"page_ids": ["uuid", "..."]}'
```
Response: `{"deleted": N, "page_ids": [...]}`.

## Complete API Endpoints

### Auth
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/auth/register` | Register (name, email, password). Response: `{"user": {"id", "email", "name", "api_token", "role", "nostr_pubkey"}, "token": "<jwt>"}` — the raw API token is nested at `user.api_token` (one-time reveal), and the top-level `token` is a JWT. |
| POST | `/api/auth/login` | Login. Returns JWT only — API token NOT returned. |
| POST | `/api/auth/regenerate-token` | Rotate the API token. Returns new raw token once; previous token invalidated. |
| POST | `/api/auth/password` | Change password. |
| POST | `/api/auth/password-reset/request` | Start a password reset (body `{"email"}`). No auth. Always returns `{"status":"ok"}` (no account enumeration); emails a 1-hour reset link if the account exists. |
| POST | `/api/auth/password-reset/confirm` | Complete reset (body `{"token","password"}`; `token` is the 64-hex value from the email). No auth. |
| POST | `/api/auth/email/verify` | Verify email (body `{"token"}`, 64-hex from email). No auth. Idempotent. Verification is a soft gate. |
| POST | `/api/auth/email/resend` | Resend the verification email. Auth required. Returns `{"status":"ok"}` or `{"status":"ok","already_verified":true}`. |
| GET | `/api/auth/me` | Self-describe. Accepts JWT, account token, **or** space-scoped token. Returns `{user, credential}` — `credential.type` is `"jwt"` / `"account"` / `"space"`. For space tokens, `credential` additionally contains `space_id`, `space_slug`, `space_name`, `workspace_id`, `workspace_slug`, `workspace_name`, `token_id`, `token_name`, `expires_at`. **Use this to discover your space when handed a raw space token.** |
| GET | `/api/auth/me/export` | GDPR data export — all the user's data as JSON. |
| DELETE | `/api/auth/me` | GDPR account deletion. Requires body `{"confirmation": "DELETE"}`. |

### Workspaces
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/workspaces` | Create workspace |
| GET | `/api/workspaces` | List user's workspaces |
| GET | `/api/workspaces/:id` | Get workspace |
| PUT | `/api/workspaces/:id` | Update workspace |
| DELETE | `/api/workspaces/:id` | Delete workspace (cascades) |
| GET | `/api/workspaces/:id/search?q=` | Full-text search |
| GET | `/api/workspaces/:id/search?q=&mode=semantic` | Semantic (embedding) search, natural-language queries — **Pro only**. Response `mode` field says `semantic` or `fulltext_fallback` |
| POST | `/api/workspaces/:id/invite` | Invite user by email to a space (body `{"email","space_id","role"?}`). User must already have an account; they're emailed a notification with a link to the space. |

### Spaces
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/workspaces/:wid/spaces` | Create space |
| GET | `/api/workspaces/:wid/spaces` | List spaces |
| GET | `/api/spaces/:id` | Get space |
| PUT | `/api/spaces/:id` | Update space |
| DELETE | `/api/spaces/:id` | Delete space |

### Pages
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/spaces/:sid/pages` | Create page |
| GET | `/api/spaces/:sid/pages` | List pages (?hierarchy=true) |
| POST | `/api/spaces/:sid/pages/bulk` | Bulk create (up to 500) |
| POST | `/api/spaces/:sid/import/markdown` | Import a markdown folder: `{"files": [{"path": "guides/setup.md", "content": "..."}]}`. Folders become parent pages (`index.md`/`README.md` supplies the folder page's content); titles from the first `# H1`, else the file name. Max 500/call. **Idempotent** — re-import reuses by source path (no `-2` dupes); optional `parent_page` (anchor) + `overwrite_existing` (re-sync). Returns `{created, reused, updated, pages}` |
| DELETE | `/api/spaces/:sid/pages/bulk` | Bulk delete |
| GET | `/api/pages/:id` | Get page. Add `?include=comments` for the comment thread and/or `?include=children` for immediate child pages (id/title/slug, no content); they compose: `?include=comments,children` |
| PUT | `/api/pages/:id` | Update page |
| PATCH | `/api/pages/:id/rename` | Rename title/slug |
| PUT | `/api/pages/:id/move` | Move to different parent/space |
| PUT | `/api/pages/:id/reorder` | Reorder a page among its siblings |
| POST | `/api/pages/:id/promote-to-space` | Promote a page (with its children) into a new space |
| DELETE | `/api/pages/:id` | Delete page (cascades) |
| GET | `/api/pages/:id/versions` | List versions. Each version records provenance: `edited_via` (`web` \| `api_token` \| `space_token`; `null` for pre-2026-06 versions) and `token_name` (space-token label, e.g. your agent's name) |
| POST | `/api/pages/:id/restore/:version` | Restore a page to an old version → `{"page": {...}, "restored_from_version": N}` |
| GET | `/api/pages/:id/ancestors` | Get parent chain |

### Share Links
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/pages/:pid/share` | Create share link (requires **editor** access) |
| GET | `/api/pages/:pid/shares` | List share links (read access only) |
| DELETE | `/api/pages/:pid/shares/:sid` | Revoke share link (requires **editor** access) |
| GET | `/api/shared/:token` | View shared page (JSON) |
| GET | `/api/shared/:token/raw` | View shared page (Markdown) |

Creating and revoking share links requires **editor** access — a viewer-role member gets `403`. Listing share links needs only read access.

### Comments
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/pages/:pid/comments` | Add comment |
| GET | `/api/pages/:pid/comments` | List threaded comments |
| PUT | `/api/comments/:id` | Update comment |
| DELETE | `/api/comments/:id` | Delete comment |

### Webhooks
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/workspaces/:id/webhooks` | Create webhook |
| GET | `/api/workspaces/:id/webhooks` | List webhooks |
| PATCH | `/api/workspaces/:id/webhooks/:wid` | Update webhook |
| DELETE | `/api/workspaces/:id/webhooks/:wid` | Delete webhook |
| GET | `/api/workspaces/:id/webhooks/:wid/logs` | Delivery logs for a webhook |
| POST | `/api/webhooks/test` | Test-deliver a one-off payload to a URL (body `{"url","secret"?}`) without saving a webhook. Auth required; URL is SSRF-checked and rate-limited. Returns `{"success", ...}`. |

The `url` must be a publicly reachable `http://` or `https://` URL — internal
addresses (loopback, RFC1918 private ranges, `169.254.169.254` cloud metadata)
are rejected with 400. HTTP redirects on delivery are not followed.

### Space Members
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/spaces/:sid/members` | Add member |
| GET | `/api/spaces/:sid/members` | List members |
| PUT | `/api/spaces/:sid/members/:mid` | Update role |
| DELETE | `/api/spaces/:sid/members/:mid` | Remove member |

### Space Tokens
A space-scoped token grants editor-level access to **one space only** — ideal
for handing an agent access to a single space while keeping others private.
Sent as `Authorization: Token <space_token>` (same header as an account token).

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/spaces/:sid/tokens` | Mint a token — body `{"name", "expires_at"?}`. Returns `{"token": {"id","name","space_id","token":"<raw — once>","expires_at","created_at"}}` |
| GET | `/api/spaces/:sid/tokens` | List tokens for the space (no hashes) |
| DELETE | `/api/spaces/:sid/tokens/:tid` | Revoke a token |

Token management requires a JWT or account token (workspace owner / space
admin / system admin) — a space token cannot mint or list tokens. A space
token can use page, comment and share-link routes **within its space** and read
that space's members; it gets **403** on every other space, on workspace /
account / admin routes (except `GET /api/auth/me`, which self-describes — see
Auth table above), on `promote-to-space`, on moving a page into or out of the
space, on deleting the space, and on member writes. Space tokens are REST-only
— Socket.io real-time editing rejects them.

### Other
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/templates` | List page templates |
| POST | `/api/uploads` | Upload file (multipart, form field `file`, max 5MB) → `201` `{"url": "..."}` |
| GET | `/api/workspaces/:id/stats` | Workspace stats — space/page/member/webhook counts + recent activity |

The `stats` response is `{"stats": {"spaces", "pages", "members", "webhooks", "active_webhooks", "pages_last_30d", "pages_last_7d", "edits_last_30d", "comments", "created_at"}}`.

## Tips for Agents

1. **Always start** by listing workspaces to find the right workspace ID
2. **Then list spaces** within that workspace to find the space ID
3. **Then list pages** or search to find specific content
4. **Use `?hierarchy=true`** on page listings to understand the page tree structure
5. **Page content is Markdown** — write content in proper Markdown format
6. **Each page update creates a version** — no need to worry about overwriting
7. **Share links with `/raw`** return clean Markdown with YAML frontmatter
8. **Bulk operations** are atomic — all succeed or all fail
