Skip to content

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.

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.

Service health check. Public — no authentication required. Suitable for load-balancer probes and uptime monitoring.

Response: 200 OK when Pinchy is running. 503 Service Unavailable when upstream dependencies (database, OpenClaw) are unreachable.

OpenClaw gateway reachability check. Public — no authentication required.

Useful for monitoring the WebSocket gateway separately from the web process.

Setup endpoints handle the initial installation and first-admin account. They are accessible only before setup has completed — once an admin exists, they return 409 Conflict.

Check whether setup is required.

Response:

{
"setupComplete": false,
"providerConfigured": false
}

Create the first admin account.

Request body:

FieldTypeRequiredDescription
namestringYesAdmin display name
emailstringYesAdmin email address
passwordstringYesAdmin password (min 8 chars)

Response: 201 Created. A session cookie is set — the admin is logged in immediately.

Configure the first LLM provider as part of the setup flow.

Request body:

FieldTypeRequiredDescription
providerstringYesanthropic, openai, google, ollama-cloud, ollama-local
apiKeystringCond.Required for cloud providers
baseUrlstringCond.Required for ollama-local

Response: { "success": true }

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"
}
]

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:

FieldTypeRequiredDescription
namestringYesDisplay name for the agent
templateIdstringYesTemplate 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 invalid name or templateId
  • 401 — not authenticated

Get a single agent by ID.

Response: The agent object, or 404 if not found.

Update an agent’s settings. Any combination of fields can be included.

Request body:

FieldTypeRequiredDescription
namestringNoNew display name
modelstringNoNew model identifier
allowedToolsstring[]NoList of tool IDs the agent can use (admin only)
pluginConfigobjectNoPlugin 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 agents
  • 401 — not authenticated
  • 403 — only admins can change allowedTools or pluginConfig
  • 404 — agent not found

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 agent
  • 401 — not authenticated
  • 403 — not an admin
  • 404 — agent not found

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"
}
]
}

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.

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 }
]

Update a setting.

Request body:

FieldTypeRequiredDescription
keystringYesSetting key
valuestringYesSetting value

Settings with api_key in the key name are automatically encrypted at rest.

Response: { "success": true }

List configured LLM providers. Credentials are masked.

Response:

[
{ "provider": "anthropic", "configured": true, "isDefault": true },
{ "provider": "openai", "configured": true, "isDefault": false }
]

Add or update a provider.

Request body:

FieldTypeRequiredDescription
providerstringYesProvider identifier
apiKeystringCond.API key (required for cloud providers)
baseUrlstringCond.URL (required for ollama-local)
isDefaultbooleanNoMark as the default provider for new agents

Pinchy validates the credentials by calling the provider’s /models endpoint before persisting. API keys are encrypted at rest with AES-256-GCM.

DELETE /api/settings/providers?provider=<id>

Section titled “DELETE /api/settings/providers?provider=<id>”

Remove a provider. Any agent still using a model from this provider will fail to start chats until reassigned.

List available models across all configured providers. Populates the model dropdown in agent settings.

Response:

{
"models": [
{
"id": "claude-sonnet-4-6",
"provider": "anthropic",
"displayName": "Claude Sonnet 4.6",
"toolUse": true
}
]
}

The response is cached for one hour for cloud providers. Local Ollama is always fetched live.

Get the current domain lock status.

Response:

{
"lockedDomain": "pinchy.example.com",
"insecureMode": false
}

Set or clear the locked domain. See Lock Pinchy to a Domain for the full flow and safety guarantees.

Request body:

FieldTypeRequiredDescription
lockedDomainstring|nullYesCanonical HTTPS hostname. Pass null to disable domain locking.

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.

Update the organization context. This is synced to all shared agent workspaces and triggers a runtime restart.

Request body:

FieldTypeRequiredDescription
contentstringYesOrganization context (Markdown)

Response: { "success": true }

Errors:

  • 400 — content is not a string
  • 401 — not authenticated
  • 403 — not an admin

List all users.

Response:

{
"users": [
{
"id": "uuid",
"name": "Alice",
"email": "alice@example.com",
"role": "admin",
"banned": false,
"groups": [{ "id": "group-uuid", "name": "Engineering" }]
}
]
}

Deactivate a user (soft delete — sets banned status). Their personal agents are removed from the shared view but the account record is retained and can be reactivated via POST /api/users/:id/reactivate.

Response: 200 OK

{
"success": true
}

Errors:

  • 400 — attempting to delete yourself
  • 401 — not authenticated
  • 403 — not an admin
  • 404 — user not found

Update a user’s role. Admin only.

Request body:

FieldTypeRequiredDescription
rolestringYesNew role (admin or member)

Response:

{
"success": true
}

Errors:

  • 400 — invalid role, attempting to change your own role, or demoting the last admin
  • 401 — not authenticated
  • 403 — not an admin
  • 404 — user not found

Reactivate a deactivated user. Admin only.

Response:

{
"success": true
}

Errors:

  • 401 — not authenticated
  • 403 — not an admin
  • 404 — user not found or user is not deactivated

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 authenticated
  • 403 — not an admin
  • 404 — user not found

Create an invite for a new user. Returns the full invite object including a plaintext token (shown only once).

Request body:

FieldTypeRequiredDescription
rolestringYesRole for the invited user (admin or member)
emailstringNoEmail address of the invited user (optional, for reference)
groupIdsstring[]NoGroup IDs to assign the user to on account creation

Response: 201 Created

{
"id": "uuid",
"token": "abc123...",
"email": "bob@example.com",
"role": "member",
"type": "invite",
"groups": [{ "id": "group-uuid", "name": "Engineering" }],
"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 role
  • 401 — not authenticated
  • 403 — not an admin

List all invites and their status.

Response:

[
{
"id": "uuid",
"email": "bob@example.com",
"role": "member",
"status": "pending",
"groups": [{ "id": "group-uuid", "name": "Engineering" }],
"expiresAt": "2025-01-22T10:00:00.000Z",
"createdAt": "2025-01-15T10:00:00.000Z"
}
]

Revoke a pending invite.

Response: 200 OK

{
"success": true
}

Errors:

  • 401 — not authenticated
  • 403 — not an admin
  • 404 — invite not found

Claim an invite token to create a new account or reset a password. This endpoint does not require authentication.

Request body:

FieldTypeRequiredDescription
tokenstringYesThe invite/reset token from the URL
namestringYesDisplay name for the new user
passwordstringYesPassword (min 8 characters)

Response: 201 Created

{
"success": true
}

Errors:

  • 400 — missing fields, invalid token, or token expired

Connect agents to Telegram bots so users can chat with them from their phone. See Set Up Telegram for the end-to-end flow.

GET /api/agents/:agentId/channels/telegram

Section titled “GET /api/agents/:agentId/channels/telegram”

Get the Telegram bot configuration for an agent. Admin only.

Response (not configured):

{ "configured": false, "mainBotConfigured": true }

Response (configured):

{ "configured": true, "hint": "8a2f", "mainBotConfigured": true }

hint is the last 4 characters of the bot token for visual verification. mainBotConfigured indicates whether the global Smithers/main bot is set up — a prerequisite before any other agent can connect to Telegram.

POST /api/agents/:agentId/channels/telegram

Section titled “POST /api/agents/:agentId/channels/telegram”

Connect an agent to a Telegram bot. Admin only. The main bot must be configured first (exception: the main bot itself).

Request body:

FieldTypeRequiredDescription
botTokenstringYesBot token from @BotFather

Response:

{ "botUsername": "support_pinchy_bot", "botId": 123456789 }

Errors:

  • 400 — invalid or non-working bot token
  • 409telegram_not_configured (main bot missing), or the bot token is already used by another agent

DELETE /api/agents/:agentId/channels/telegram

Section titled “DELETE /api/agents/:agentId/channels/telegram”

Disconnect a Telegram bot from an agent. Admin only. Personal (Smithers) agents cannot be disconnected individually — use the “Remove Telegram for everyone” action instead.

Get the current user’s Telegram link status. Available to any authenticated user.

Response:

{ "linked": true, "channelUserId": "123456789" }

Link the current user’s Telegram account using a pairing code received from the bot.

Request body:

FieldTypeRequiredDescription
codestringYesPairing code shown in Telegram after DM-ing bot

Response: { "linked": true, "telegramUserId": "..." }

Errors:

  • 400 — invalid or expired pairing code

Unlink the current user’s Telegram account.

List all Telegram bots connected across agents, with the agent each bot belongs to. Admin only.

Remove Telegram from every agent at once. Admin only. This is the backend for the “Remove Telegram for everyone” flow in Settings → Telegram.

External system integrations — currently Odoo. See Integrations for the conceptual overview and Connect Odoo for the setup guide.

List all integration connections with masked credentials.

Create a new integration connection.

Request body (Odoo):

{
"type": "odoo",
"name": "Production Odoo",
"description": "Main company instance",
"credentials": {
"url": "https://odoo.example.com",
"db": "production",
"login": "api-user@example.com",
"apiKey": "..."
}
}

Credentials are encrypted at rest with AES-256-GCM.

Response: 201 Created — the created connection with masked credentials.

Errors:

  • 400 — validation failed (bad URL, missing fields) or the URL resolves to a disallowed address (SSRF guard)

Get a single connection (credentials masked).

Update a connection’s name, description, or credentials.

Delete a connection. Agents that reference it lose access immediately.

Re-probe the external system: rediscover available models and recheck access rights. Permissions that no longer apply are removed.

Test credentials against the external system without persisting changes. Used by the setup wizard for live feedback.

Get an agent’s integration permissions: which connections are enabled, access level (read-only, read-write, full, custom), and which data models are allowed.

Update an agent’s integration permissions.

Token usage and estimated cost aggregates. See Usage & Costs Dashboard for the UI.

Per-agent usage totals within a time window.

Query parameters:

ParameterTypeDefaultDescription
daysnumber30Look-back window. 0 means all-time
agentIdstringFilter by a single agent

Response:

{
"agents": [
{
"agentId": "uuid",
"agentName": "Smithers",
"totalInputTokens": "15432",
"totalOutputTokens": "3210",
"totalCacheReadTokens": "120",
"totalCacheWriteTokens": "45",
"totalCost": "0.08",
"deleted": false
}
],
"totals": {
"chat": {
"inputTokens": "...",
"outputTokens": "...",
"cacheReadTokens": "...",
"cacheWriteTokens": "...",
"cost": "..."
},
"system": {
"inputTokens": "...",
"outputTokens": "...",
"cacheReadTokens": "...",
"cacheWriteTokens": "...",
"cost": "..."
},
"plugin": {
"inputTokens": "...",
"outputTokens": "...",
"cacheReadTokens": "...",
"cacheWriteTokens": "...",
"cost": "..."
}
}
}

Source buckets classify where the tokens were consumed:

  • chat — direct user ↔ agent conversation
  • system — background work such as Smithers onboarding or summarization
  • plugin — work driven by a plugin subagent call

Usage broken down by user. Enterprise license required.

Usage over time — used to render the dashboard charts. Query parameters: days, agentId, and interval (day or hour).

Export usage records as CSV. Enterprise license required.

All group endpoints require an enterprise license. Requests without a valid license receive a 403 Forbidden response.

List all groups with member counts. Admin only.

Response:

[
{
"id": "uuid",
"name": "Engineering",
"description": "Backend and frontend engineers",
"memberCount": 5
}
]

Create a new group. Admin only.

Request body:

FieldTypeRequiredDescription
namestringYesGroup name
descriptionstringNoGroup description

Response: 201 Created with the created group object.

Errors:

  • 400 — missing or invalid name
  • 401 — not authenticated
  • 403 — not an admin or no enterprise license

Update a group. Admin only.

Request body:

FieldTypeRequiredDescription
namestringNoNew group name
descriptionstringNoNew group description

Response: The updated group object.

Errors:

  • 400 — invalid fields
  • 401 — not authenticated
  • 403 — not an admin or no enterprise license
  • 404 — group not found

Delete a group. Admin only.

Response:

{
"success": true
}

Errors:

  • 401 — not authenticated
  • 403 — not an admin or no enterprise license
  • 404 — group not found

List members of a group. Admin only.

Response:

[
{
"userId": "user-uuid",
"groupId": "group-uuid"
}
]

Errors:

  • 401 — not authenticated
  • 403 — not an admin or no enterprise license
  • 404 — group not found

Replace the members of a group. Admin only.

Request body:

FieldTypeRequiredDescription
userIdsstring[]YesUser IDs to set as group members

Response:

{
"success": true
}

Errors:

  • 400 — invalid or missing userIds
  • 401 — not authenticated
  • 403 — not an admin or no enterprise license
  • 404 — group not found

Retrieve a paginated, filterable audit log. Admin only.

Query parameters:

ParameterTypeDefaultDescription
pagenumber1Page number
limitnumber50Entries per page
eventTypestringFilter by event type (e.g., auth.login, tool.shell)
actorIdstringFilter by user ID
fromstringStart date (ISO 8601)
tostringEnd date (ISO 8601)

Response:

{
"entries": [
{
"id": 1,
"timestamp": "2026-02-21T10:00:00.000Z",
"actorType": "user",
"actorId": "user-uuid",
"actorName": "Alice",
"actorDeleted": false,
"eventType": "auth.login",
"resource": "user:user-uuid",
"resourceName": "Alice",
"resourceDeleted": false,
"detail": {},
"rowHmac": "sha256-hex-string"
}
],
"total": 142,
"page": 1,
"limit": 50
}

Errors:

  • 401 — not authenticated
  • 403 — not an admin

Verify the integrity of audit log entries by recomputing HMAC signatures. Admin only.

Query parameters:

ParameterTypeDefaultDescription
fromIdstringStart verification from this entry ID (optional)
toIdstringEnd verification at this entry ID (optional)

Response:

{
"valid": true,
"totalChecked": 142,
"invalidIds": []
}

If tampered entries are found, valid is false and invalidIds contains the IDs of entries with mismatched signatures.

Errors:

  • 401 — not authenticated
  • 403 — not an admin

List the distinct event types present in the current audit log. Useful for populating filter dropdowns without enumerating the full schema. Admin only.

Response:

{
"eventTypes": [
"auth.login",
"auth.failed",
"agent.created",
"channel.created",
"tool.shell",
"tool.denied"
]
}

Export the audit log as a CSV file. Supports the same filters as GET /api/audit. Admin only.

Query parameters:

ParameterTypeDefaultDescription
eventTypestringFilter by event type
actorIdstringFilter by user ID
fromstringStart date (ISO 8601)
tostringEnd date (ISO 8601)

Response: 200 OK with Content-Type: text/csv and Content-Disposition: attachment; filename="audit-log.csv".

Errors:

  • 401 — not authenticated
  • 403 — not an admin

Update your own display name.

Request body:

FieldTypeRequiredDescription
namestringYesNew display name

Response:

{
"success": true
}

Errors:

  • 400 — missing or empty name
  • 401 — not authenticated

Get your personal context.

Response:

{
"content": "I'm Alice, a product manager at Acme Corp..."
}

Returns { "content": "" } if no context has been set.

Update your personal context. This is synced to your Smithers agent’s workspace and triggers a runtime restart.

Request body:

FieldTypeRequiredDescription
contentstringYesYour personal context (Markdown)

Response: { "success": true }

Errors:

  • 400 — content is not a string
  • 401 — not authenticated

Change your own password.

Request body:

FieldTypeRequiredDescription
currentPasswordstringYesCurrent password
newPasswordstringYesNew password (min 8 characters)

Response: { "success": true }

Errors:

  • 400 — missing fields or new password too short
  • 401 — not authenticated or current password incorrect