API Reference
All API endpoints require authentication unless noted otherwise. Unauthenticated requests receive a 401 Unauthorized response. Authenticate by including a valid session cookie (set during login via the web UI).
Some endpoints are admin only — non-admin users receive a 403 Forbidden response.
WebSocket (Chat)
Section titled “WebSocket (Chat)”Agent chat is handled over WebSocket at /api/ws, not via REST. The browser sends JSON messages and receives streaming responses. For details on the WebSocket protocol, message types, and session management, see the Architecture page.
Agents
Section titled “Agents”GET /api/agents
Section titled “GET /api/agents”List agents visible to the current user. Admins see all agents. Non-admin users see shared agents and their own personal agents.
Response:
[ { "id": "uuid", "name": "HR Policy Assistant", "model": "anthropic/claude-haiku-4-5-20251001", "templateId": "knowledge-base", "allowedTools": ["pinchy_ls", "pinchy_read"], "pluginConfig": { "allowed_paths": ["/data/hr-policies"] }, "isPersonal": false, "ownerId": "user-uuid", "createdAt": "2025-01-15T10:00:00.000Z", "updatedAt": "2025-01-15T10:00:00.000Z" }]POST /api/agents
Section titled “POST /api/agents”Create a new agent. The agent inherits default tool permissions from its template. Use PATCH /api/agents/:id to configure permissions after creation.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name for the agent |
templateId | string | Yes | Template to use (knowledge-base or custom) |
Example — Knowledge Base agent:
{ "name": "HR Policy Assistant", "templateId": "knowledge-base"}Example — Custom agent:
{ "name": "General Assistant", "templateId": "custom"}Response: 201 Created with the created agent object.
Errors:
400— missing or invalidnameortemplateId401— not authenticated
GET /api/agents/:id
Section titled “GET /api/agents/:id”Get a single agent by ID.
Response: The agent object, or 404 if not found.
PATCH /api/agents/:id
Section titled “PATCH /api/agents/:id”Update an agent’s settings. Any combination of fields can be included.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | New display name |
model | string | No | New model identifier |
allowedTools | string[] | No | List of tool IDs the agent can use (admin only) |
pluginConfig | object | No | Plugin configuration, e.g. { "allowed_paths": ["/data/hr"] } (admin only) |
Example — update permissions:
{ "allowedTools": ["pinchy_ls", "pinchy_read"], "pluginConfig": { "allowed_paths": ["/data/hr-policies"] }}Response: The updated agent object.
Errors:
400— cannot change permissions for personal agents401— not authenticated403— only admins can changeallowedToolsorpluginConfig404— agent not found
DELETE /api/agents/:id
Section titled “DELETE /api/agents/:id”Delete an agent. Admin only. Personal agents (auto-created Smithers) cannot be deleted.
Response: 200 OK
{ "success": true}Errors:
400— attempting to delete a personal agent401— not authenticated403— not an admin404— agent not found
Templates
Section titled “Templates”GET /api/templates
Section titled “GET /api/templates”List available agent templates.
Response:
{ "templates": [ { "id": "knowledge-base", "name": "Knowledge Base", "description": "Answer questions from your docs" }, { "id": "custom", "name": "Custom Agent", "description": "Start from scratch" } ]}Data Directories
Section titled “Data Directories”GET /api/data-directories
Section titled “GET /api/data-directories”List directories available under /data/ for agent configuration.
Response:
{ "directories": [ { "path": "/data/hr-policies", "name": "hr-policies" }, { "path": "/data/engineering-docs", "name": "engineering-docs" } ]}Returns an empty array if /data/ does not exist or contains no subdirectories. Hidden directories (starting with .) are excluded.
Settings
Section titled “Settings”GET /api/settings
Section titled “GET /api/settings”Get all application settings. Encrypted values (such as API keys) are masked in the response.
Response:
[ { "key": "default_provider", "value": "anthropic", "encrypted": false }, { "key": "anthropic_api_key", "value": "--------", "encrypted": true }]POST /api/settings
Section titled “POST /api/settings”Update a setting.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Setting key |
value | string | Yes | Setting value |
Settings with api_key in the key name are automatically encrypted at rest.
Response: { "success": true }
Organization Context (admin only)
Section titled “Organization Context (admin only)”GET /api/settings/context
Section titled “GET /api/settings/context”Get the organization context. Admin only.
Response:
{ "content": "Acme Corp is a SaaS company focused on..."}Returns { "content": "" } if no org context has been set.
PUT /api/settings/context
Section titled “PUT /api/settings/context”Update the organization context. This is synced to all shared agent workspaces and triggers a runtime restart.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Organization context (Markdown) |
Response: { "success": true }
Errors:
400— content is not a string401— not authenticated403— not an admin
Users (admin only)
Section titled “Users (admin only)”GET /api/users
Section titled “GET /api/users”List all users.
Response:
[ { "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "createdAt": "2025-01-15T10:00:00.000Z" }]DELETE /api/users/:id
Section titled “DELETE /api/users/:id”Delete a user and cascade-delete their personal agents.
Response: 200 OK
{ "success": true}Errors:
400— attempting to delete yourself401— not authenticated403— not an admin404— user not found
POST /api/users/:id/reset
Section titled “POST /api/users/:id/reset”Generate a password reset token for a user. The admin constructs the reset URL from the returned token: {origin}/invite/{token}.
Response: 201 Created
{ "token": "abc123..."}Errors:
401— not authenticated403— not an admin404— user not found
Invites (admin only)
Section titled “Invites (admin only)”POST /api/users/invite
Section titled “POST /api/users/invite”Create an invite for a new user. Returns the full invite object including a plaintext token (shown only once).
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
role | string | Yes | Role for the invited user (admin or user) |
email | string | No | Email address of the invited user (optional, for reference) |
Response: 201 Created
{ "id": "uuid", "token": "abc123...", "email": "bob@example.com", "role": "user", "type": "invite", "expiresAt": "2025-01-22T10:00:00.000Z", "createdAt": "2025-01-15T10:00:00.000Z"}The admin constructs the invite URL from the returned token: {origin}/invite/{token}.
Errors:
400— missing or invalid role401— not authenticated403— not an admin
GET /api/users/invites
Section titled “GET /api/users/invites”List all invites and their status.
Response:
[ { "id": "uuid", "email": "bob@example.com", "status": "pending", "expiresAt": "2025-01-22T10:00:00.000Z", "createdAt": "2025-01-15T10:00:00.000Z" }]DELETE /api/users/invites/:id
Section titled “DELETE /api/users/invites/:id”Revoke a pending invite.
Response: 200 OK
{ "success": true}Errors:
401— not authenticated403— not an admin404— invite not found
Invite Claim (public)
Section titled “Invite Claim (public)”POST /api/invite/claim
Section titled “POST /api/invite/claim”Claim an invite token to create a new account or reset a password. This endpoint does not require authentication.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | The invite/reset token from the URL |
name | string | Yes | Display name for the new user |
password | string | Yes | Password (min 8 characters) |
Response: 201 Created
{ "success": true}Errors:
400— missing fields, invalid token, or token expired
Audit Trail (admin only)
Section titled “Audit Trail (admin only)”GET /api/audit
Section titled “GET /api/audit”Retrieve a paginated, filterable audit log. Admin only.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 50 | Entries per page |
eventType | string | — | Filter by event type (e.g., auth.login, tool.execute) |
actorId | string | — | Filter by user ID |
from | string | — | Start date (ISO 8601) |
to | string | — | End date (ISO 8601) |
Response:
{ "entries": [ { "id": "uuid", "eventType": "auth.login", "actorId": "user-uuid", "actorEmail": "alice@example.com", "metadata": {}, "hmac": "sha256-hex-string", "createdAt": "2026-02-21T10:00:00.000Z" } ], "total": 142, "page": 1, "limit": 50}Errors:
401— not authenticated403— not an admin
GET /api/audit/verify
Section titled “GET /api/audit/verify”Verify the integrity of audit log entries by recomputing HMAC signatures. Admin only.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
fromId | string | — | Start verification from this entry ID (optional) |
toId | string | — | End verification at this entry ID (optional) |
Response:
{ "verified": true, "entriesChecked": 142, "tamperedEntries": []}If tampered entries are found, verified is false and tamperedEntries contains the IDs of entries with mismatched signatures.
Errors:
401— not authenticated403— not an admin
GET /api/audit/export
Section titled “GET /api/audit/export”Export the audit log as a CSV file. Supports the same filters as GET /api/audit. Admin only.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | string | — | Filter by event type |
actorId | string | — | Filter by user ID |
from | string | — | Start date (ISO 8601) |
to | string | — | End date (ISO 8601) |
Response: 200 OK with Content-Type: text/csv and Content-Disposition: attachment; filename="audit-log.csv".
Errors:
401— not authenticated403— not an admin
User Self-Service
Section titled “User Self-Service”PATCH /api/users/me
Section titled “PATCH /api/users/me”Update your own display name.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | New display name |
Response:
{ "success": true}Errors:
400— missing or empty name401— not authenticated
GET /api/users/me/context
Section titled “GET /api/users/me/context”Get your personal context.
Response:
{ "content": "I'm Alice, a product manager at Acme Corp..."}Returns { "content": "" } if no context has been set.
PUT /api/users/me/context
Section titled “PUT /api/users/me/context”Update your personal context. This is synced to your Smithers agent’s workspace and triggers a runtime restart.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Your personal context (Markdown) |
Response: { "success": true }
Errors:
400— content is not a string401— not authenticated
POST /api/users/me/password
Section titled “POST /api/users/me/password”Change your own password.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
currentPassword | string | Yes | Current password |
newPassword | string | Yes | New password (min 8 characters) |
Response: { "success": true }
Errors:
400— missing fields or new password too short401— not authenticated or current password incorrect