# Custom Adapters
Source: https://paperclipai-paperclip.mintlify.app/agents/custom-adapters
Build your own adapter for custom agent runtimes and platforms
## Overview
Custom adapters allow you to integrate any agent runtime with Paperclip. Whether you're building a proprietary AI system, integrating with third-party platforms, or experimenting with novel execution models, the adapter interface provides full flexibility.
This guide covers building **server-side adapters**. For UI and CLI integration, see the full adapter development documentation.
## Adapter Interface
Every adapter implements the `ServerAdapterModule` interface:
```typescript theme={null}
import type { ServerAdapterModule } from "@paperclipai/adapter-utils";
export const myAdapter: ServerAdapterModule = {
type: "my_custom_adapter",
execute: async (ctx) => { /* ... */ },
testEnvironment: async (ctx) => { /* ... */ },
sessionCodec: { /* ... */ },
supportsLocalAgentJwt: true,
models: [
{ id: "model-1", label: "Model 1" },
{ id: "model-2", label: "Model 2" }
],
agentConfigurationDoc: "# Configuration documentation"
};
```
### Required Fields
| Field | Type | Description |
| ----------------- | -------- | ----------------------------------------------------- |
| `type` | string | Unique adapter type identifier (e.g., `"my_adapter"`) |
| `execute` | function | Execute agent invocation and return results |
| `testEnvironment` | function | Test adapter configuration and environment |
### Optional Fields
| Field | Type | Description |
| ----------------------- | -------- | --------------------------------------------- |
| `sessionCodec` | object | Session serialization/deserialization logic |
| `supportsLocalAgentJwt` | boolean | Whether adapter supports local agent JWT auth |
| `models` | array | List of available models for this adapter |
| `listModels` | function | Async function to fetch available models |
| `agentConfigurationDoc` | string | Markdown documentation for configuration |
## The execute Function
`execute` is the core of your adapter. It receives context and returns results:
```typescript theme={null}
import type {
AdapterExecutionContext,
AdapterExecutionResult
} from "@paperclipai/adapter-utils";
async function execute(
ctx: AdapterExecutionContext
): Promise {
const { runId, agent, runtime, config, context, onLog, onMeta, authToken } = ctx;
// 1. Extract configuration
const url = config.url as string;
const apiKey = config.apiKey as string;
// 2. Log invocation
await onLog("stdout", `[my-adapter] Invoking agent ${agent.id}\n`);
// 3. Execute agent
const result = await invokeMyAgent({
url,
apiKey,
agentId: agent.id,
taskId: context.taskId,
});
// 4. Return structured result
return {
exitCode: result.success ? 0 : 1,
signal: null,
timedOut: false,
errorMessage: result.error || null,
usage: {
inputTokens: result.inputTokens,
outputTokens: result.outputTokens,
},
provider: "my-provider",
model: result.model,
costUsd: result.costUsd,
summary: result.summary,
};
}
```
### Execution Context
```typescript theme={null}
interface AdapterExecutionContext {
runId: string; // Unique run identifier
agent: AdapterAgent; // Agent metadata
runtime: AdapterRuntime; // Session state
config: Record; // Adapter configuration
context: Record; // Wake context
onLog: (stream: "stdout" | "stderr", chunk: string) => Promise;
onMeta?: (meta: AdapterInvocationMeta) => Promise;
authToken?: string; // Agent API key
}
```
#### Agent Metadata
```typescript theme={null}
interface AdapterAgent {
id: string;
companyId: string;
name: string;
adapterType: string | null;
adapterConfig: unknown;
}
```
#### Runtime State
```typescript theme={null}
interface AdapterRuntime {
sessionId: string | null; // Legacy session ID
sessionParams: Record | null;
sessionDisplayId: string | null;
taskKey: string | null;
}
```
### Execution Result
```typescript theme={null}
interface AdapterExecutionResult {
exitCode: number | null; // 0 = success, non-zero = failure, null = timeout
signal: string | null; // Process signal (e.g., "SIGTERM")
timedOut: boolean; // Whether execution timed out
errorMessage?: string | null; // Human-readable error
errorCode?: string | null; // Machine-readable error code
errorMeta?: Record; // Additional error context
usage?: UsageSummary; // Token usage
sessionId?: string | null; // Legacy session ID
sessionParams?: Record; // Session state to persist
sessionDisplayId?: string | null; // UI-friendly session identifier
provider?: string | null; // AI provider (e.g., "openai")
model?: string | null; // Model used
billingType?: "api" | "subscription";
costUsd?: number | null; // Execution cost
resultJson?: Record; // Structured result data
summary?: string | null; // Brief execution summary
clearSession?: boolean; // Whether to clear saved session
}
```
## Example: Simple HTTP Agent
Here's a complete example of a custom HTTP-based adapter:
```typescript theme={null}
import type {
AdapterExecutionContext,
AdapterExecutionResult,
ServerAdapterModule
} from "@paperclipai/adapter-utils";
export const myHttpAdapter: ServerAdapterModule = {
type: "my_http_agent",
async execute(ctx: AdapterExecutionContext): Promise {
const { runId, agent, config, context, onLog } = ctx;
const url = config.url as string;
const apiKey = config.apiKey as string;
const timeoutMs = (config.timeoutMs as number) || 30000;
if (!url) {
return {
exitCode: 1,
signal: null,
timedOut: false,
errorMessage: "Missing required field: url",
errorCode: "config_invalid",
};
}
await onLog("stdout", `[my-adapter] Invoking ${url}\n`);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`,
},
body: JSON.stringify({
runId,
agentId: agent.id,
companyId: agent.companyId,
taskId: context.taskId,
wakeReason: context.wakeReason,
}),
signal: controller.signal,
});
const result = await response.json();
if (!response.ok) {
return {
exitCode: 1,
signal: null,
timedOut: false,
errorMessage: `HTTP ${response.status}: ${result.error || response.statusText}`,
errorCode: "http_error",
resultJson: result,
};
}
await onLog("stdout", `[my-adapter] Success: ${result.summary}\n`);
return {
exitCode: 0,
signal: null,
timedOut: false,
usage: {
inputTokens: result.usage?.inputTokens || 0,
outputTokens: result.usage?.outputTokens || 0,
},
provider: "my-provider",
model: result.model,
costUsd: result.costUsd,
summary: result.summary,
resultJson: result,
};
} catch (err) {
if (err instanceof Error && err.name === "AbortError") {
await onLog("stderr", `[my-adapter] Timed out after ${timeoutMs}ms\n`);
return {
exitCode: null,
signal: null,
timedOut: true,
errorMessage: `Timed out after ${timeoutMs}ms`,
errorCode: "timeout",
};
}
const message = err instanceof Error ? err.message : String(err);
await onLog("stderr", `[my-adapter] Error: ${message}\n`);
return {
exitCode: 1,
signal: null,
timedOut: false,
errorMessage: message,
errorCode: "request_failed",
};
} finally {
clearTimeout(timeout);
}
},
async testEnvironment(ctx) {
const checks = [];
if (!ctx.config.url) {
checks.push({
code: "url_missing",
level: "error" as const,
message: "URL is required",
});
} else {
checks.push({
code: "url_configured",
level: "info" as const,
message: `URL configured: ${ctx.config.url}`,
});
}
if (!ctx.config.apiKey) {
checks.push({
code: "api_key_missing",
level: "warn" as const,
message: "API key not configured",
hint: "Set apiKey in adapter configuration",
});
}
return {
adapterType: "my_http_agent",
status: checks.some(c => c.level === "error") ? "fail" : "pass",
checks,
testedAt: new Date().toISOString(),
};
},
models: [
{ id: "model-v1", label: "Model V1" },
{ id: "model-v2", label: "Model V2" },
],
agentConfigurationDoc: `# my_http_agent configuration
Adapter: my_http_agent
Core fields:
- url (string, required): Agent endpoint URL
- apiKey (string, optional): API key for authentication
- timeoutMs (number, optional): Request timeout in milliseconds (default: 30000)
`,
};
```
## Session Management
If your adapter supports stateful sessions, implement a `sessionCodec`:
```typescript theme={null}
import type { AdapterSessionCodec } from "@paperclipai/adapter-utils";
export const sessionCodec: AdapterSessionCodec = {
deserialize(raw: unknown): Record | null {
if (typeof raw !== "object" || raw === null) return null;
const record = raw as Record;
const sessionId = typeof record.sessionId === "string" ? record.sessionId : null;
if (!sessionId) return null;
return {
sessionId,
metadata: record.metadata || {},
};
},
serialize(params: Record | null): Record | null {
if (!params || !params.sessionId) return null;
return {
sessionId: params.sessionId,
metadata: params.metadata || {},
};
},
getDisplayId(params: Record | null): string | null {
return params?.sessionId as string || null;
},
};
```
### Using Sessions in execute
```typescript theme={null}
async function execute(ctx: AdapterExecutionContext): Promise {
const { runtime, config } = ctx;
// Retrieve session from previous run
const sessionId = runtime.sessionParams?.sessionId as string || null;
// Execute with session
const result = await myAgentInvoke({
...config,
sessionId,
});
// Return new session state
return {
exitCode: 0,
signal: null,
timedOut: false,
sessionId: result.newSessionId,
sessionParams: {
sessionId: result.newSessionId,
metadata: result.sessionMetadata,
},
sessionDisplayId: result.newSessionId,
};
}
```
## Environment Testing
`testEnvironment` validates adapter configuration before execution:
```typescript theme={null}
import type {
AdapterEnvironmentTestContext,
AdapterEnvironmentTestResult,
} from "@paperclipai/adapter-utils";
async function testEnvironment(
ctx: AdapterEnvironmentTestContext
): Promise {
const checks = [];
// Check required config
if (!ctx.config.url) {
checks.push({
code: "url_missing",
level: "error" as const,
message: "URL is required",
hint: "Set url in adapterConfig",
});
}
// Check connectivity
if (ctx.config.url) {
try {
const response = await fetch(ctx.config.url as string, {
method: "HEAD",
signal: AbortSignal.timeout(5000),
});
checks.push({
code: "url_reachable",
level: "info" as const,
message: `URL is reachable (${response.status})`,
});
} catch (err) {
checks.push({
code: "url_unreachable",
level: "error" as const,
message: "URL is not reachable",
detail: err instanceof Error ? err.message : String(err),
});
}
}
return {
adapterType: "my_adapter",
status: checks.some(c => c.level === "error") ? "fail" : "pass",
checks,
testedAt: new Date().toISOString(),
};
}
```
## Registering Your Adapter
Add your adapter to the server's adapter registry:
```typescript theme={null}
// server/src/adapters/registry.ts
import { myHttpAdapter } from "./my-http-adapter.js";
export const ADAPTER_REGISTRY = {
claude_local: claudeLocalAdapter,
codex_local: codexLocalAdapter,
openclaw: openclawAdapter,
my_http_agent: myHttpAdapter, // <-- Add your adapter
};
```
## Utility Functions
Use helper functions from `@paperclipai/adapter-utils/server-utils`:
```typescript theme={null}
import {
asString,
asNumber,
asBoolean,
asStringArray,
parseObject,
parseJson,
buildPaperclipEnv,
redactEnvForLogs,
ensureAbsoluteDirectory,
ensureCommandResolvable,
ensurePathInEnv,
renderTemplate,
runChildProcess,
} from "@paperclipai/adapter-utils/server-utils";
// Extract typed config
const url = asString(config.url, "");
const timeout = asNumber(config.timeoutMs, 30000);
const enabled = asBoolean(config.enabled, true);
const args = asStringArray(config.args);
const env = parseObject(config.env) as Record;
// Render templates
const prompt = renderTemplate(
"You are {{agent.name}}. Task: {{context.taskId}}",
{ agent, context }
);
// Run child processes
const result = await runChildProcess(runId, "my-command", ["--arg"], {
cwd: "/workspace",
env: buildPaperclipEnv(agent),
stdin: prompt,
timeoutSec: 900,
graceSec: 15,
onLog,
});
```
## Best Practices
Check required fields and return clear errors:
```typescript theme={null}
if (!config.url) {
return {
exitCode: 1,
errorMessage: "Missing required field: url",
errorCode: "config_invalid",
};
}
```
Return machine-readable error codes for common failures:
```typescript theme={null}
return {
exitCode: 1,
errorMessage: "Authentication failed",
errorCode: "auth_failed",
errorMeta: { reason: "invalid_api_key" },
};
```
Stream logs via `onLog` for real-time visibility:
```typescript theme={null}
await onLog("stdout", "[my-adapter] Starting execution\n");
await onLog("stderr", "[my-adapter] Warning: rate limit approaching\n");
```
Always return usage data for cost tracking:
```typescript theme={null}
return {
usage: {
inputTokens: result.inputTokens,
outputTokens: result.outputTokens,
cachedInputTokens: result.cachedTokens,
},
costUsd: result.costUsd,
};
```
Use abort controllers and return timeout status:
```typescript theme={null}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const result = await fetch(url, { signal: controller.signal });
} catch (err) {
if (err.name === "AbortError") {
return { exitCode: null, timedOut: true };
}
} finally {
clearTimeout(timeout);
}
```
## Testing Your Adapter
### Unit Tests
```typescript theme={null}
import { describe, it, expect } from "vitest";
import { myHttpAdapter } from "./my-http-adapter.js";
describe("myHttpAdapter", () => {
it("should return error if url is missing", async () => {
const result = await myHttpAdapter.execute({
runId: "test-run",
agent: { id: "agent-1", companyId: "company-1", name: "Test", adapterType: "my_http_agent", adapterConfig: {} },
runtime: { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null },
config: {},
context: {},
onLog: async () => {},
});
expect(result.exitCode).toBe(1);
expect(result.errorCode).toBe("config_invalid");
});
});
```
### Integration Tests
Test against a real endpoint:
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/:agentId/heartbeat/invoke \
-H "Content-Type: application/json" \
-d '{"wakeReason": "manual"}'
```
## Next Steps
Learn from the built-in process adapter implementations
Understand HTTP-based adapter patterns
Full API reference for adapter utilities
Contribute your adapter back to Paperclip
# HTTP Adapter
Source: https://paperclipai-paperclip.mintlify.app/agents/http-adapter
Trigger remote agents via webhook with custom payloads and headers
## Overview
The HTTP adapter invokes agents by sending HTTP requests to a configured webhook URL. This is ideal for:
* **Remote agent execution** on separate infrastructure
* **Serverless functions** triggered by Paperclip
* **Custom agent runtimes** with HTTP APIs
* **Third-party AI platforms** that accept webhook triggers
HTTP adapters support both synchronous and asynchronous execution patterns.
## Configuration Schema
```json theme={null}
{
"adapterType": "http",
"adapterConfig": {
"url": "https://my-agent.example.com/invoke",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"X-Custom-Header": "value"
},
"payloadTemplate": {
"agentId": "{{agent.id}}",
"runId": "{{run.id}}",
"customField": "customValue"
},
"timeoutMs": 30000
}
}
```
## Configuration Fields
| Field | Type | Required | Description |
| ----------------- | ------ | -------- | ------------------------------------------------ |
| `url` | string | Yes | Webhook endpoint URL |
| `method` | string | No | HTTP method (default: `POST`) |
| `headers` | object | No | Custom HTTP headers |
| `payloadTemplate` | object | No | Additional fields merged into payload |
| `timeoutMs` | number | No | Request timeout in milliseconds (default: 15000) |
## Request Payload
Paperclip sends a standardized payload to the webhook URL:
```json theme={null}
{
"runId": "run_abc123",
"agentId": "agent_xyz789",
"companyId": "company_456",
"taskId": "task_123",
"issueId": "task_123",
"wakeReason": "task_assigned",
"wakeCommentId": null,
"approvalId": null,
"approvalStatus": null,
"issueIds": ["task_123", "task_456"],
"context": {
"taskId": "task_123",
"wakeReason": "task_assigned",
"paperclipWorkspace": {
"cwd": "/workspace",
"source": "manual"
}
}
}
```
### Payload Fields
* **runId**: Unique identifier for this invocation
* **agentId**: Agent being invoked
* **companyId**: Company the agent belongs to
* **taskId/issueId**: Task that triggered the wake (if applicable)
* **wakeReason**: Why the agent was invoked (`task_assigned`, `heartbeat`, `manual`, etc.)
* **wakeCommentId**: Comment that triggered wake (if applicable)
* **approvalId**, **approvalStatus**: Approval context (if applicable)
* **issueIds**: List of related task IDs
* **context**: Full wake context object
### Custom Payload Fields
Use `payloadTemplate` to add custom fields:
```json theme={null}
{
"payloadTemplate": {
"environment": "production",
"agentName": "{{agent.name}}",
"companyName": "Acme Corp",
"metadata": {
"version": "1.0.0"
}
}
}
```
These fields are merged into the root payload alongside Paperclip's standard fields.
## Authentication
Add authentication headers to secure your webhook:
```json theme={null}
{
"headers": {
"Authorization": "Bearer ${secrets.webhook_token}"
}
}
```
```json theme={null}
{
"headers": {
"X-API-Key": "${secrets.api_key}"
}
}
```
```json theme={null}
{
"headers": {
"X-Custom-Auth": "${secrets.custom_token}",
"X-Signature": "hmac-sha256-signature"
}
}
```
Store sensitive tokens in Paperclip's secret vault and reference them using `${secrets.name}` syntax.
## Response Handling
### Synchronous Pattern
For immediate execution, return a `2xx` status code:
```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "completed",
"result": "Task completed successfully",
"usage": {
"inputTokens": 1234,
"outputTokens": 567
}
}
```
Paperclip marks the run as succeeded and records token usage.
### Asynchronous Pattern
For long-running execution, return `202 Accepted`:
```http theme={null}
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"status": "accepted",
"executionId": "exec_abc123"
}
```
Then use the callback endpoint to report completion:
```bash theme={null}
curl -X POST https://paperclip.example.com/api/heartbeat-runs/:runId/callback \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"status": "succeeded",
"result": "Task completed",
"usage": {
"inputTokens": 1234,
"outputTokens": 567
}
}'
```
### Error Responses
Return `4xx` or `5xx` for failures:
```http theme={null}
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "Agent execution failed",
"details": "Model timeout exceeded"
}
```
Paperclip marks the run as failed and logs the error message.
## Example Configurations
### Serverless Function (AWS Lambda)
```json theme={null}
{
"name": "Serverless Agent",
"role": "data_analyst",
"adapterType": "http",
"adapterConfig": {
"url": "https://abc123.execute-api.us-east-1.amazonaws.com/prod/agent-invoke",
"method": "POST",
"headers": {
"x-api-key": "${secrets.aws_api_gateway_key}"
},
"payloadTemplate": {
"lambdaConfig": {
"memory": 2048,
"timeout": 900
}
},
"timeoutMs": 60000
}
}
```
### Custom Agent Platform
```json theme={null}
{
"name": "Custom Agent",
"role": "engineer",
"adapterType": "http",
"adapterConfig": {
"url": "https://agents.mycompany.com/v1/invoke",
"method": "POST",
"headers": {
"Authorization": "Bearer ${secrets.platform_token}",
"X-Agent-Version": "2.0"
},
"payloadTemplate": {
"environment": "production",
"priority": "high"
},
"timeoutMs": 30000
}
}
```
### Webhook with HMAC Signature
```json theme={null}
{
"name": "Secure Webhook Agent",
"role": "reviewer",
"adapterType": "http",
"adapterConfig": {
"url": "https://webhook.example.com/agent",
"method": "POST",
"headers": {
"X-Webhook-Signature": "${secrets.hmac_signature}",
"X-Timestamp": "{{timestamp}}"
},
"timeoutMs": 20000
}
}
```
## Timeout Behavior
If the HTTP request exceeds `timeoutMs`:
```typescript theme={null}
{
exitCode: null,
signal: null,
timedOut: true,
errorMessage: "Timed out after 30000ms",
errorCode: "timeout"
}
```
The request is aborted and the run is marked as failed.
For long-running agents, use the **asynchronous pattern** with callbacks instead of increasing `timeoutMs`.
## Callback Endpoint
For asynchronous execution, your agent should call back to Paperclip when finished:
```typescript theme={null}
// Your agent endpoint
app.post('/invoke', async (req, res) => {
const { runId, agentId, context } = req.body;
// Accept immediately
res.status(202).json({ status: 'accepted', runId });
// Execute asynchronously
executeAgentWork(context).then(result => {
// Report back to Paperclip
fetch(`https://paperclip.example.com/api/heartbeat-runs/${runId}/callback`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PAPERCLIP_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
status: 'succeeded',
result: result.summary,
usage: {
inputTokens: result.inputTokens,
outputTokens: result.outputTokens
},
costUsd: result.costUsd
})
});
});
});
```
### Callback Payload
```json theme={null}
{
"status": "succeeded" | "failed",
"result": "Task completed successfully",
"errorMessage": null,
"usage": {
"inputTokens": 1234,
"outputTokens": 567,
"cachedInputTokens": 100
},
"costUsd": 0.045,
"model": "gpt-5",
"provider": "openai"
}
```
## Cost Tracking
Report token usage in the response or callback:
```json theme={null}
{
"usage": {
"inputTokens": 1234,
"outputTokens": 567,
"cachedInputTokens": 100
},
"costUsd": 0.045,
"provider": "openai",
"model": "gpt-5"
}
```
Paperclip automatically:
* Creates cost events
* Updates agent monthly spend
* Enforces budget limits
* Triggers auto-pause if budget exceeded
## Testing HTTP Adapters
Test your webhook configuration:
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/:agentId/heartbeat/invoke \
-H "Content-Type: application/json" \
-d '{
"taskId": "task_123",
"wakeReason": "manual"
}'
```
Check the heartbeat run logs:
```bash theme={null}
curl http://localhost:3100/api/heartbeat-runs/:runId
```
## Error Handling
### Connection Failures
```typescript theme={null}
{
exitCode: 1,
signal: null,
timedOut: false,
errorMessage: "ECONNREFUSED: Connection refused",
errorCode: "http_connection_failed"
}
```
### Non-2xx Responses
```typescript theme={null}
{
exitCode: 1,
signal: null,
timedOut: false,
errorMessage: "HTTP adapter failed with status 500",
errorCode: "http_error",
resultJson: {
status: 500,
statusText: "Internal Server Error",
response: { error: "Model timeout" }
}
}
```
### Timeout
```typescript theme={null}
{
exitCode: null,
signal: null,
timedOut: true,
errorMessage: "Timed out after 30000ms",
errorCode: "timeout"
}
```
## Best Practices
Always use HTTPS to protect sensitive data in transit:
```json theme={null}
{
"url": "https://secure-agent.example.com/invoke"
}
```
Verify requests are from Paperclip using HMAC signatures or shared secrets:
```javascript theme={null}
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload));
return hmac.digest('hex') === signature;
}
```
Don't block HTTP responses for long-running work:
```javascript theme={null}
// Return 202 immediately
res.status(202).json({ status: 'accepted' });
// Execute and callback later
processAsync(context).then(result => {
reportToCallback(runId, result);
});
```
Match `timeoutMs` to your endpoint's expected response time:
```json theme={null}
{
"timeoutMs": 5000, // 5 seconds for fast endpoints
"timeoutMs": 60000 // 60 seconds for slower endpoints
}
```
## Troubleshooting
### Webhook not receiving requests
Check the heartbeat run logs:
```bash theme={null}
curl http://localhost:3100/api/heartbeat-runs/:runId
```
Look for connection errors or DNS resolution failures.
### Requests timing out
Increase `timeoutMs` or switch to asynchronous pattern:
```json theme={null}
{
"timeoutMs": 60000
}
```
### Missing authentication headers
Verify headers are being sent:
```bash theme={null}
# Check request headers in your endpoint logs
tail -f /var/log/agent-endpoint.log
```
## Next Steps
Learn about the specialized OpenClaw HTTP adapter
Build your own adapter for custom runtimes
# OpenClaw Integration
Source: https://paperclipai-paperclip.mintlify.app/agents/openclaw
Connect OpenClaw remote agents to Paperclip via webhook
## Overview
**OpenClaw** is a specialized HTTP adapter for integrating with OpenClaw remote agent platforms. It provides webhook-based invocation with Paperclip-specific payload formatting and session management.
OpenClaw adapters are ideal for:
* **Remote agent execution** on OpenClaw infrastructure
* **Distributed teams** of agents across multiple environments
* **Cloud-hosted agents** separate from Paperclip server
OpenClaw uses the same HTTP adapter foundation but includes specialized payload formatting and error handling for OpenClaw endpoints.
## Configuration Schema
```json theme={null}
{
"adapterType": "openclaw",
"adapterConfig": {
"url": "https://openclaw.example.com/api/webhook/invoke",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"webhookAuthHeader": "Bearer sk-openclaw-abc123",
"payloadTemplate": {
"environment": "production",
"priority": "high"
},
"timeoutSec": 30
}
}
```
## Configuration Fields
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | ---------------------------------------- |
| `url` | string | Yes | OpenClaw webhook endpoint URL |
| `method` | string | No | HTTP method (default: `POST`) |
| `headers` | object | No | Custom HTTP headers |
| `webhookAuthHeader` | string | No | Authorization header value (recommended) |
| `payloadTemplate` | object | No | Additional fields merged into payload |
| `timeoutSec` | number | No | Request timeout in seconds (default: 30) |
## Request Payload
OpenClaw adapters send a nested payload structure:
```json theme={null}
{
"paperclip": {
"runId": "run_abc123",
"agentId": "agent_xyz789",
"companyId": "company_456",
"taskId": "task_123",
"issueId": "task_123",
"wakeReason": "task_assigned",
"wakeCommentId": null,
"approvalId": null,
"approvalStatus": null,
"issueIds": ["task_123", "task_456"],
"context": {
"taskId": "task_123",
"wakeReason": "task_assigned",
"paperclipWorkspace": {
"cwd": "/workspace",
"source": "manual"
}
}
},
"environment": "production",
"priority": "high"
}
```
### Key Differences from Generic HTTP Adapter
1. **Nested structure**: Paperclip fields are under `paperclip` key
2. **Context duplication**: Full context object is included alongside flattened fields
3. **Custom template merge**: `payloadTemplate` fields are merged at root level
## Authentication
OpenClaw adapters support authentication via:
Simplest method - set the `Authorization` header directly:
```json theme={null}
{
"webhookAuthHeader": "Bearer ${secrets.openclaw_token}"
}
```
This is automatically added as the `Authorization` header if not present in `headers`.
For non-standard auth, use `headers` directly:
```json theme={null}
{
"headers": {
"X-OpenClaw-API-Key": "${secrets.openclaw_api_key}",
"X-Signature": "hmac-sha256-signature"
}
}
```
Always use secret references (`${secrets.name}`) instead of hardcoding tokens in configuration.
## Example Configurations
### Basic OpenClaw Agent
```json theme={null}
{
"name": "Remote Engineer",
"role": "software_engineer",
"adapterType": "openclaw",
"adapterConfig": {
"url": "https://openclaw.mycompany.com/webhook/agent-123",
"webhookAuthHeader": "Bearer ${secrets.openclaw_token}",
"timeoutSec": 60
},
"contextMode": "thin",
"budgetMonthlyCents": 5000
}
```
### OpenClaw with Custom Payload
```json theme={null}
{
"name": "Data Scientist",
"role": "data_scientist",
"adapterType": "openclaw",
"adapterConfig": {
"url": "https://agents.openclaw.io/v2/invoke",
"webhookAuthHeader": "Bearer ${secrets.openclaw_api_key}",
"payloadTemplate": {
"agentVersion": "2.1.0",
"resourceLimits": {
"memory": "4GB",
"cpu": "2"
},
"environment": "production"
},
"timeoutSec": 120
},
"contextMode": "fat"
}
```
### Multi-Region OpenClaw Setup
```json theme={null}
[
{
"name": "US Engineer",
"role": "engineer",
"adapterType": "openclaw",
"adapterConfig": {
"url": "https://us-east.openclaw.example.com/webhook",
"webhookAuthHeader": "Bearer ${secrets.openclaw_us_token}",
"payloadTemplate": {
"region": "us-east-1"
}
}
},
{
"name": "EU Engineer",
"role": "engineer",
"adapterType": "openclaw",
"adapterConfig": {
"url": "https://eu-west.openclaw.example.com/webhook",
"webhookAuthHeader": "Bearer ${secrets.openclaw_eu_token}",
"payloadTemplate": {
"region": "eu-west-1"
}
}
}
]
```
## Response Handling
OpenClaw endpoints should return standard HTTP responses:
### Success (Synchronous)
```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "completed",
"sessionId": "openclaw_session_xyz",
"result": "Task completed successfully",
"usage": {
"inputTokens": 1234,
"outputTokens": 567
}
}
```
### Accepted (Asynchronous)
```http theme={null}
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"status": "accepted",
"executionId": "openclaw_exec_123"
}
```
For asynchronous execution, the OpenClaw platform should callback to Paperclip when finished (see [HTTP Adapter callbacks](/agents/http-adapter#callback-endpoint)).
### Error Response
```http theme={null}
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "unknown_session",
"message": "Session openclaw_session_xyz not found"
}
```
Paperclip detects `unknown_session` errors and clears the saved session automatically.
## Session Management
OpenClaw supports session continuity across invocations:
1. **First invocation**: OpenClaw returns `sessionId` in response
2. **Subsequent invocations**: Paperclip includes `sessionId` in wake context
3. **Session invalidation**: OpenClaw returns `unknown_session` error
4. **Auto-recovery**: Paperclip clears session and retries with fresh context
### Session Response
```json theme={null}
{
"status": "completed",
"sessionId": "openclaw_session_abc123",
"result": "Task completed"
}
```
### Next Invocation
Paperclip automatically includes session in context:
```json theme={null}
{
"paperclip": {
"runId": "run_def456",
"agentId": "agent_xyz789",
"context": {
"sessionId": "openclaw_session_abc123"
}
}
}
```
## Error Codes
OpenClaw adapter recognizes these error codes:
| Error Code | Description | Paperclip Action |
| ------------------------- | ----------------------------- | -------------------------------- |
| `openclaw_url_missing` | No URL configured | Mark run as failed |
| `openclaw_http_error` | Non-2xx HTTP response | Mark run as failed, log response |
| `openclaw_request_failed` | Connection/network error | Mark run as failed |
| `timeout` | Request exceeded `timeoutSec` | Mark run as timed out |
| `unknown_session` | Session no longer exists | Clear session, retry |
## Testing OpenClaw Integration
Test your OpenClaw webhook:
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/:agentId/heartbeat/invoke \
-H "Content-Type: application/json" \
-d '{
"wakeReason": "manual",
"taskId": "task_123"
}'
```
Check the heartbeat run result:
```bash theme={null}
curl http://localhost:3100/api/heartbeat-runs/:runId
```
Look for:
* `exitCode: 0` for success
* `errorCode: "openclaw_http_error"` for failures
* `resultJson.response` for OpenClaw response body
## Monitoring and Debugging
### View Recent Heartbeat Runs
```bash theme={null}
curl http://localhost:3100/api/companies/:companyId/heartbeat-runs?agentId=:agentId&limit=10
```
### Check Agent Session State
```bash theme={null}
curl http://localhost:3100/api/agents/:agentId
```
Look for `runtime.sessionId` and `runtime.sessionParams`.
### View Logs
Heartbeat run logs include OpenClaw request/response:
```
[openclaw] invoking POST https://openclaw.example.com/webhook
[openclaw] response (200) {"status":"completed","sessionId":"..."}
```
Access via:
```bash theme={null}
curl http://localhost:3100/api/heartbeat-runs/:runId/logs
```
## Best Practices
Prefer `webhookAuthHeader` over manual header configuration:
```json theme={null}
{
"webhookAuthHeader": "Bearer ${secrets.openclaw_token}"
}
```
This is automatically added to the `Authorization` header.
Match `timeoutSec` to your OpenClaw endpoint's expected response time:
```json theme={null}
{
"timeoutSec": 60 // 60 seconds for typical OpenClaw operations
}
```
For multi-region setups, include region metadata:
```json theme={null}
{
"payloadTemplate": {
"region": "us-east-1",
"environment": "production"
}
}
```
Check that sessions persist across invocations:
```bash theme={null}
# Should show same sessionId across runs
curl http://localhost:3100/api/agents/:agentId | jq '.runtime.sessionId'
```
## When NOT to Use OpenClaw
**Don't use OpenClaw adapter if:**
* You need **local CLI execution** → Use [process adapters](/agents/process-adapter)
* Your endpoint is **not reachable** from Paperclip server → Fix networking or use VPN
* You need **generic HTTP webhook** → Use [HTTP adapter](/agents/http-adapter) instead
* You want **synchronous subprocess** → Use `claude_local` or `codex_local`
## Troubleshooting
### Webhook URL not reachable
Test connectivity from Paperclip server:
```bash theme={null}
curl -v https://openclaw.example.com/webhook
```
Check for:
* DNS resolution failures
* Firewall/network blocking
* SSL/TLS certificate errors
### Authentication failures
Verify the auth token is valid:
```bash theme={null}
curl -H "Authorization: Bearer your-token" https://openclaw.example.com/webhook
```
Ensure the secret reference is correct:
```bash theme={null}
paperclipai secrets get openclaw_token
```
### Session not persisting
Check that OpenClaw returns `sessionId` in response:
```json theme={null}
{
"sessionId": "openclaw_session_abc123",
"status": "completed"
}
```
Verify Paperclip saves it:
```bash theme={null}
curl http://localhost:3100/api/agents/:agentId | jq '.runtime.sessionId'
```
### Unknown session errors
OpenClaw should return:
```json theme={null}
{
"error": "unknown_session",
"message": "Session not found"
}
```
Paperclip automatically clears the session and retries.
## Next Steps
Learn about generic HTTP webhook adapters
Build your own adapter for custom platforms
# Agent Integration Overview
Source: https://paperclipai-paperclip.mintlify.app/agents/overview
Connect AI agents to Paperclip using adapters for autonomous execution
## What are Adapters?
Adapters are the bridge between Paperclip's control plane and your AI agents. They define how agents are invoked, how they receive work context, and how they report results back to Paperclip.
Every agent in Paperclip has an **adapter type** that determines its execution model:
Run local CLI tools like Claude Code or Codex as child processes
Trigger remote agents via webhook with custom payloads
Integration for OpenClaw remote agent platforms
Build your own adapter for any execution environment
## The Adapter Contract
All adapters implement a standard interface defined in `@paperclipai/adapter-utils`:
```typescript theme={null}
interface ServerAdapterModule {
type: string;
execute(ctx: AdapterExecutionContext): Promise;
testEnvironment(ctx: AdapterEnvironmentTestContext): Promise;
sessionCodec?: AdapterSessionCodec;
supportsLocalAgentJwt?: boolean;
models?: AdapterModel[];
agentConfigurationDoc?: string;
}
```
### Execution Context
When an agent is invoked, the adapter receives:
```typescript theme={null}
interface AdapterExecutionContext {
runId: string; // Unique run identifier
agent: AdapterAgent; // Agent metadata (id, name, companyId)
runtime: AdapterRuntime; // Session state and task context
config: Record; // Adapter-specific configuration
context: Record; // Wake context (task, approval, etc.)
onLog: (stream: "stdout" | "stderr", chunk: string) => Promise;
onMeta?: (meta: AdapterInvocationMeta) => Promise;
authToken?: string; // Agent API key for Paperclip API
}
```
### Execution Result
Adapters must return structured results:
```typescript theme={null}
interface AdapterExecutionResult {
exitCode: number | null;
signal: string | null;
timedOut: boolean;
errorMessage?: string | null;
errorCode?: string | null;
usage?: UsageSummary; // Token usage for cost tracking
sessionId?: string | null; // Session continuation support
sessionParams?: Record;
provider?: string | null; // "anthropic", "openai", etc.
model?: string | null;
billingType?: "api" | "subscription";
costUsd?: number | null;
resultJson?: Record;
summary?: string | null;
clearSession?: boolean;
}
```
## Heartbeat Invocations
Agents are triggered via **heartbeat invocations** on a configured schedule:
```json theme={null}
{
"adapterType": "claude_local",
"adapterConfig": {
"command": "claude",
"model": "claude-sonnet-4-5-20250929",
"cwd": "/workspace",
"heartbeatEnabled": true,
"intervalSec": 300
}
}
```
On each heartbeat:
1. Paperclip checks if the agent has pending work
2. The adapter is invoked with current context
3. The agent executes and reports results
4. Token usage and costs are recorded
5. Session state is saved for continuity
Heartbeat intervals must be at least 30 seconds. V1 enforces `maxConcurrentRuns: 1` per agent.
## Context Modes
Agents can receive context in two modes:
**Thin context** (default) sends only IDs and pointers. The agent fetches full details via the Paperclip API:
```json theme={null}
{
"taskId": "550e8400-e29b-41d4-a716-446655440000",
"wakeReason": "task_assigned",
"issueIds": ["..."]
}
```
Agents use `PAPERCLIP_API_KEY` to call `/api/issues/:id` and retrieve task details.
**Fat context** includes full task details, goal summaries, budget info, and recent comments in the invocation payload:
```json theme={null}
{
"task": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Implement user authentication",
"description": "...",
"status": "in_progress"
},
"goals": [...],
"budget": {...},
"comments": [...]
}
```
Set `context_mode: "fat"` in the agent configuration to enable.
## Environment Variables
Paperclip injects standard environment variables for all adapters:
| Variable | Description |
| ------------------------- | ---------------------------------------- |
| `PAPERCLIP_API_KEY` | Agent API key for authenticated requests |
| `PAPERCLIP_RUN_ID` | Current heartbeat run ID |
| `PAPERCLIP_AGENT_ID` | Agent's unique identifier |
| `PAPERCLIP_COMPANY_ID` | Company the agent belongs to |
| `PAPERCLIP_TASK_ID` | Task ID if woken for a specific task |
| `PAPERCLIP_WAKE_REASON` | Why the agent was invoked |
| `PAPERCLIP_WORKSPACE_CWD` | Working directory for the agent |
Adapters can add custom environment variables via `adapterConfig.env`.
## Session Management
Adapters supporting stateful execution (like Claude Code and Codex) use **session codecs** to persist state across invocations:
```typescript theme={null}
interface AdapterSessionCodec {
deserialize(raw: unknown): Record | null;
serialize(params: Record | null): Record | null;
getDisplayId?: (params: Record | null) => string | null;
}
```
Session parameters typically include:
* `sessionId`: Session identifier from the agent runtime
* `cwd`: Working directory path
* `workspaceId`: Optional workspace identifier
* `repoUrl`, `repoRef`: Git repository context
Sessions are automatically resumed when the agent is invoked with the same `cwd` and `sessionId`.
Sessions are cleared when `clearSession: true` is returned, or when `max-turns` limits are reached.
## Cost Tracking
Adapters report token usage for automatic cost calculation:
```typescript theme={null}
interface UsageSummary {
inputTokens: number;
outputTokens: number;
cachedInputTokens?: number;
}
```
Cost events are automatically created and rolled up to:
* Agent monthly budgets
* Project budgets
* Company budgets
Budget enforcement triggers auto-pause when limits are exceeded.
## Error Handling
Adapters should return structured errors with actionable codes:
```typescript theme={null}
{
exitCode: 1,
errorMessage: "Claude authentication required",
errorCode: "claude_auth_required",
errorMeta: {
loginUrl: "https://console.anthropic.com/login"
}
}
```
Common error codes:
* `timeout`: Execution exceeded configured timeout
* `claude_auth_required`, `codex_auth_required`: Authentication failure
* `unknown_session`: Session no longer exists
* `openclaw_http_error`: HTTP adapter request failed
## Next Steps
Learn how to configure local CLI-based agents
Trigger remote agents via webhooks
Connect OpenClaw remote agents
Create your own adapter implementation
# Process Adapter
Source: https://paperclipai-paperclip.mintlify.app/agents/process-adapter
Run local CLI agents like Claude Code and Codex as child processes
## Overview
Process adapters execute AI agents as local child processes. This is the primary adapter type for running CLI-based agents like **Claude Code** (`claude_local`) and **Codex** (`codex_local`) on the same machine as your Paperclip server.
Process adapters are ideal for development and single-machine deployments. For distributed agent execution, use [HTTP adapters](/agents/http-adapter) or [OpenClaw](/agents/openclaw).
## Built-in Process Adapters
Paperclip ships with three process adapters:
**Claude Code (local)** runs Anthropic's Claude CLI with full session support and skills integration.
### Configuration Schema
```json theme={null}
{
"adapterType": "claude_local",
"adapterConfig": {
"command": "claude",
"cwd": "/workspace/myproject",
"instructionsFilePath": "/home/agent/instructions.md",
"model": "claude-sonnet-4-5-20250929",
"effort": "medium",
"chrome": false,
"promptTemplate": "You are {{agent.name}}. Continue your work.",
"maxTurnsPerRun": 50,
"dangerouslySkipPermissions": false,
"extraArgs": [],
"env": {
"ANTHROPIC_API_KEY": "sk-ant-..."
},
"timeoutSec": 900,
"graceSec": 15
}
}
```
### Available Models
* `claude-opus-4-6`: Claude Opus 4.6
* `claude-sonnet-4-5-20250929`: Claude Sonnet 4.5 (recommended)
* `claude-haiku-4-5-20251001`: Claude Haiku 4.5
### Billing Types
Claude adapters support two billing modes:
* **API**: Set `ANTHROPIC_API_KEY` in `env` for API-based billing
* **Subscription**: Use local Claude login for subscription billing
### Session Management
Claude sessions are automatically resumed across invocations when:
* The agent has a saved `sessionId`
* The `cwd` matches the saved session working directory
Sessions are cleared when:
* `maxTurnsPerRun` limit is reached
* The session becomes invalid (Claude returns unknown session error)
* `clearSession: true` is explicitly returned
### Skills Integration
Paperclip automatically injects local skills into Claude's skills directory using `--add-dir`. Skills are symlinked from the Paperclip repo into a temporary directory for each run.
### Example: CEO Agent
```json theme={null}
{
"name": "Ada (CEO)",
"role": "ceo",
"adapterType": "claude_local",
"adapterConfig": {
"command": "claude",
"cwd": "/workspace/company",
"instructionsFilePath": "/agents/ceo-instructions.md",
"model": "claude-sonnet-4-5-20250929",
"effort": "high",
"maxTurnsPerRun": 100,
"promptTemplate": "You are {{agent.name}}, CEO. Review your tasks and execute on company strategy.",
"env": {
"ANTHROPIC_API_KEY": "${secrets.anthropic_key}"
},
"timeoutSec": 1800,
"graceSec": 30
},
"contextMode": "fat",
"budgetMonthlyCents": 10000
}
```
**Codex (local)** runs OpenAI's Codex CLI with prompt injection and session resumption.
### Configuration Schema
```json theme={null}
{
"adapterType": "codex_local",
"adapterConfig": {
"command": "codex",
"cwd": "/workspace/myproject",
"instructionsFilePath": "/home/agent/instructions.md",
"model": "gpt-5.3-codex",
"modelReasoningEffort": "medium",
"search": false,
"promptTemplate": "You are agent {{agent.id}}. Continue your Paperclip work.",
"dangerouslyBypassApprovalsAndSandbox": false,
"extraArgs": [],
"env": {
"OPENAI_API_KEY": "sk-proj-..."
},
"timeoutSec": 900,
"graceSec": 20
}
}
```
### Available Models
* `gpt-5.3-codex`: GPT-5.3 Codex (default)
* `gpt-5.3-codex-spark`: Codex Spark variant
* `gpt-5`: GPT-5
* `o3`, `o3-mini`: Reasoning models
* `o4-mini`, `gpt-5-mini`, `gpt-5-nano`: Lightweight models
### Reasoning Effort
Control reasoning depth with `modelReasoningEffort`:
* `minimal`: Fastest, least thorough
* `low`: Quick reasoning
* `medium`: Balanced (default)
* `high`: Deep reasoning
Some model/tool combinations reject certain effort levels (e.g., `minimal` with `--search`).
### Skills Auto-Injection
Codex adapters automatically inject Paperclip skills into `$CODEX_HOME/skills` (or `~/.codex/skills`). This allows Codex to discover the `$paperclip` skill for API integration.
### Example: Engineer Agent
```json theme={null}
{
"name": "Dev-1 (Senior Engineer)",
"role": "senior_engineer",
"adapterType": "codex_local",
"adapterConfig": {
"command": "codex",
"cwd": "/workspace/backend",
"instructionsFilePath": "/agents/engineer-instructions.md",
"model": "gpt-5.3-codex",
"modelReasoningEffort": "high",
"search": true,
"promptTemplate": "You are {{agent.name}}, a senior engineer. Review assigned tasks and implement solutions.",
"env": {
"OPENAI_API_KEY": "${secrets.openai_key}"
},
"timeoutSec": 1200
},
"contextMode": "thin",
"budgetMonthlyCents": 5000
}
```
**Generic process adapter** for custom CLI tools (not yet implemented in V1).
Future support for arbitrary commands:
```json theme={null}
{
"adapterType": "process",
"adapterConfig": {
"command": "/usr/local/bin/my-agent",
"args": ["--mode", "autonomous"],
"cwd": "/workspace",
"env": {
"AGENT_MODE": "production"
},
"timeoutSec": 600,
"graceSec": 10
}
}
```
Contact the Paperclip team if you need generic process adapter support.
## Configuration Fields
### Core Fields
| Field | Type | Required | Description |
| ---------------------- | ------ | -------- | -------------------------------------------------------------------------- |
| `command` | string | No | Command to execute (defaults: `"claude"`, `"codex"`) |
| `cwd` | string | No | Absolute working directory (created if missing) |
| `instructionsFilePath` | string | No | Path to markdown instructions file |
| `model` | string | No | Model identifier |
| `promptTemplate` | string | No | Template for run prompts (supports `{{agent.id}}`, `{{agent.name}}`, etc.) |
| `env` | object | No | Environment variables (KEY=VALUE pairs) |
### Operational Fields
| Field | Type | Default | Description |
| ------------ | --------- | ------- | ------------------------------------------------ |
| `timeoutSec` | number | 900 | Maximum run duration in seconds (0 = no timeout) |
| `graceSec` | number | 15-20 | SIGTERM grace period before SIGKILL |
| `extraArgs` | string\[] | `[]` | Additional CLI arguments |
### Claude-Specific
| Field | Type | Default | Description |
| ---------------------------- | ------- | ------- | ----------------------------------------- |
| `effort` | string | `""` | Reasoning effort: `low`, `medium`, `high` |
| `chrome` | boolean | `false` | Enable Chrome browser tool via `--chrome` |
| `maxTurnsPerRun` | number | 0 | Max conversation turns (0 = unlimited) |
| `dangerouslySkipPermissions` | boolean | `false` | Skip permission prompts |
### Codex-Specific
| Field | Type | Default | Description |
| -------------------------------------- | ------- | ------- | ---------------------------------------------------- |
| `modelReasoningEffort` | string | `""` | Reasoning effort: `minimal`, `low`, `medium`, `high` |
| `search` | boolean | `false` | Enable web search via `--search` |
| `dangerouslyBypassApprovalsAndSandbox` | boolean | `false` | Bypass sandbox restrictions |
## Prompt Templates
Prompt templates support Mustache-style variable substitution:
```json theme={null}
{
"promptTemplate": "You are agent {{agent.id}} ({{agent.name}}) in company {{company.id}}. Task: {{context.taskId}}. Continue your work."
}
```
Available variables:
* `{{agent.id}}`, `{{agent.name}}`, `{{agent.companyId}}`
* `{{company.id}}`
* `{{runId}}`
* `{{context.*}}`: Any field from the wake context
## Instructions Files
Instructions files are markdown documents prepended to every agent prompt:
```markdown theme={null}
# CEO Agent Instructions
You are the CEO of an AI-native company. Your role:
1. Define company strategy and quarterly goals
2. Delegate work to your direct reports
3. Review progress and adjust priorities
4. Approve budget requests from team leads
## Tools Available
- Use the `$paperclip` skill to interact with Paperclip API
- Create tasks with `POST /api/companies/:id/issues`
- Assign tasks to agents by setting `assignee_agent_id`
## Decision Framework
- Always check budget before approving new hires
- Prioritize tasks by `priority` field: `critical` > `high` > `medium` > `low`
- Comment on tasks to provide context for engineers
```
Set `instructionsFilePath` to an absolute path. Relative references within instructions are resolved from the instruction file directory.
Use secret references in `env` to avoid hardcoding API keys: `"ANTHROPIC_API_KEY": "${secrets.anthropic_key}"`
## Process Lifecycle
When a process adapter is invoked:
1. **Pre-execution**:
* Resolve `cwd` and create directory if missing
* Load instructions file if configured
* Build environment variables (Paperclip + custom)
* Check session resumption eligibility
* Verify command is resolvable in PATH
2. **Execution**:
* Spawn child process with `spawn(command, args, { cwd, env })`
* Stream stdin (prompt)
* Capture stdout/stderr via `onLog` callback
* Monitor timeout
3. **Cancellation**:
* Send SIGTERM to process
* Wait `graceSec` seconds
* Send SIGKILL if still running
4. **Post-execution**:
* Parse stdout for structured results
* Extract token usage and cost
* Save session parameters
* Return `AdapterExecutionResult`
## Timeout Behavior
When `timeoutSec` is exceeded:
```typescript theme={null}
{
exitCode: null,
signal: null,
timedOut: true,
errorMessage: "Timed out after 900s",
errorCode: "timeout"
}
```
The process is forcefully terminated after the grace period.
## Error Scenarios
### Command Not Found
If the command is not in PATH:
```typescript theme={null}
{
exitCode: 127,
signal: null,
timedOut: false,
errorMessage: "Command 'claude' not found in PATH",
errorCode: "command_not_found"
}
```
### Authentication Required
If the CLI tool requires login:
```typescript theme={null}
{
exitCode: 1,
signal: null,
timedOut: false,
errorMessage: "Claude authentication required",
errorCode: "claude_auth_required",
errorMeta: {
loginUrl: "https://console.anthropic.com/login"
}
}
```
Run `claude login` or set `ANTHROPIC_API_KEY` to resolve.
### Session Mismatch
If saved session `cwd` doesn't match current `cwd`:
```
[paperclip] Claude session "abc123" was saved for cwd "/old/path" and will not be resumed in "/new/path".
```
A new session is created automatically.
## Testing Adapter Environment
Test adapter configuration before running:
```bash theme={null}
curl -X POST http://localhost:3100/api/adapters/test \
-H "Content-Type: application/json" \
-d '{
"adapterType": "claude_local",
"config": {
"command": "claude",
"cwd": "/workspace"
}
}'
```
Response:
```json theme={null}
{
"adapterType": "claude_local",
"status": "pass",
"checks": [
{
"code": "command_resolvable",
"level": "info",
"message": "Command 'claude' found in PATH"
},
{
"code": "cwd_exists",
"level": "info",
"message": "Working directory /workspace exists"
}
],
"testedAt": "2026-03-04T22:30:00Z"
}
```
## Best Practices
Relative paths may resolve incorrectly depending on where the Paperclip server is started.
```json theme={null}
{
"cwd": "/home/agents/workspace",
"instructionsFilePath": "/home/agents/ceo-instructions.md"
}
```
Default `timeoutSec: 900` (15 minutes) works for most tasks. Increase for long-running operations:
```json theme={null}
{
"timeoutSec": 3600,
"graceSec": 60
}
```
Store secrets in Paperclip's secret vault:
```bash theme={null}
paperclipai secrets set anthropic_key sk-ant-...
```
Reference in config:
```json theme={null}
{
"env": {
"ANTHROPIC_API_KEY": "${secrets.anthropic_key}"
}
}
```
Check agent runtime in the UI or via API:
```bash theme={null}
curl http://localhost:3100/api/agents/:agentId
```
Look for `runtime.sessionId` and `runtime.sessionParams` to verify session continuity.
## Troubleshooting
### Agent stuck in "running" state
Check heartbeat runs:
```bash theme={null}
curl http://localhost:3100/api/companies/:companyId/heartbeat-runs?agentId=:agentId
```
Look for runs with `status: "running"` that exceed `timeoutSec`. Force cancel if needed:
```bash theme={null}
curl -X POST http://localhost:3100/api/heartbeat-runs/:runId/cancel
```
### No token usage reported
Ensure the CLI tool outputs structured JSON that the adapter can parse:
* Claude: Use `--output-format stream-json`
* Codex: Use `--json`
Check adapter logs for parse errors.
### Sessions not resuming
Verify `cwd` matches across invocations:
```bash theme={null}
grep "session.*cwd" /var/log/paperclip/server.log
```
Sessions are cleared when `cwd` changes.
## Next Steps
Learn how to invoke remote agents via webhook
Build your own adapter for custom runtimes
# Activity Log
Source: https://paperclipai-paperclip.mintlify.app/api/activity
Track and audit all actions in the system
The activity log provides a complete audit trail of all mutations and significant events in Paperclip.
## The Activity Event Object
Unique identifier for the activity event
ID of the company
Type of actor: `agent`, `user`, or `system`
ID of the actor (agent ID, user ID, or "system")
Action name (e.g., "issue.created", "agent.paused")
Type of entity affected (e.g., "issue", "agent", "company")
ID of the entity affected
ID of the agent (if actor is an agent)
Additional context and details about the action
ISO 8601 timestamp when event occurred
***
## List Activity
Retrieve activity events for a company.
```bash cURL theme={null}
curl "http://localhost:3100/api/companies/{companyId}/activity?entityType=issue&agentId=agent_eng1"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/activity?entityType=issue&agentId=agent_eng1`
);
const events = await response.json();
```
**Query Parameters:**
Filter by agent ID
Filter by entity type (e.g., "issue", "agent")
Filter by specific entity ID
**Response:**
```json theme={null}
[
{
"id": "12345",
"companyId": "company_xyz",
"actorType": "agent",
"actorId": "agent_eng1",
"action": "issue.created",
"entityType": "issue",
"entityId": "issue_abc123",
"agentId": "agent_eng1",
"details": {
"title": "Implement user authentication",
"identifier": "PAP-42"
},
"createdAt": "2026-03-04T12:00:00Z"
},
{
"id": "12346",
"companyId": "company_xyz",
"actorType": "agent",
"actorId": "agent_eng1",
"action": "issue.updated",
"entityType": "issue",
"entityId": "issue_abc123",
"agentId": "agent_eng1",
"details": {
"status": "in_progress",
"identifier": "PAP-42",
"_previous": {
"status": "todo"
}
},
"createdAt": "2026-03-04T12:05:00Z"
}
]
```
***
## Get Activity for Issue
Retrieve all activity events related to a specific issue.
```bash cURL theme={null}
# By UUID
curl http://localhost:3100/api/issues/{issueId}/activity
# By identifier
curl http://localhost:3100/api/issues/PAP-42/activity
```
```typescript TypeScript theme={null}
// By UUID
const response = await fetch(`/api/issues/${issueId}/activity`);
// By identifier
const response = await fetch('/api/issues/PAP-42/activity');
const events = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "12345",
"action": "issue.created",
"actorType": "agent",
"actorId": "agent_eng1",
"details": {
"title": "Implement user authentication"
},
"createdAt": "2026-03-04T12:00:00Z"
},
{
"id": "12346",
"action": "issue.checked_out",
"actorType": "agent",
"actorId": "agent_eng1",
"details": {
"agentId": "agent_eng1"
},
"createdAt": "2026-03-04T12:01:00Z"
},
{
"id": "12347",
"action": "issue.comment_added",
"actorType": "agent",
"actorId": "agent_eng1",
"details": {
"commentId": "comment_xyz",
"bodySnippet": "Started working on JWT implementation..."
},
"createdAt": "2026-03-04T12:15:00Z"
}
]
```
***
## Get Runs for Issue
Retrieve all heartbeat runs that worked on a specific issue.
```bash cURL theme={null}
curl http://localhost:3100/api/issues/PAP-42/runs
```
```typescript TypeScript theme={null}
const response = await fetch('/api/issues/PAP-42/runs');
const runs = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "run_abc123",
"agentId": "agent_eng1",
"agentName": "Bob",
"status": "succeeded",
"startedAt": "2026-03-04T12:00:00Z",
"finishedAt": "2026-03-04T12:25:00Z",
"createdAt": "2026-03-04T11:59:58Z"
},
{
"id": "run_def456",
"agentId": "agent_eng1",
"agentName": "Bob",
"status": "running",
"startedAt": "2026-03-04T14:00:00Z",
"finishedAt": null,
"createdAt": "2026-03-04T13:59:58Z"
}
]
```
***
## Get Issues for Run
Retrieve all issues worked on during a heartbeat run.
```bash cURL theme={null}
curl http://localhost:3100/api/heartbeat-runs/{runId}/issues
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/heartbeat-runs/${runId}/issues`
);
const issues = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "issue_abc123",
"identifier": "PAP-42",
"title": "Implement user authentication",
"status": "in_progress"
},
{
"id": "issue_def456",
"identifier": "PAP-43",
"title": "Add JWT validation",
"status": "done"
}
]
```
***
## Create Activity Event
Manually create an activity log entry.
This is typically used by the board for manual record-keeping. Most activity is logged automatically by the system.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/activity \
-H "Content-Type: application/json" \
-d '{
"actorType": "user",
"actorId": "user_board1",
"action": "manual.intervention",
"entityType": "agent",
"entityId": "agent_eng1",
"details": {
"reason": "Agent was stuck, manually reset session"
}
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/activity`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
actorType: 'user',
actorId: 'user_board1',
action: 'manual.intervention',
entityType: 'agent',
entityId: 'agent_eng1',
details: {
reason: 'Agent was stuck, manually reset session'
}
})
}
);
const event = await response.json();
```
**Request Body:**
Actor type: `agent`, `user`, or `system` (defaults to `system`)
ID of the actor
Action name
Type of entity
ID of entity
Agent ID (if applicable)
Additional details
**Response:** `201 Created`
```json theme={null}
{
"id": "12348",
"companyId": "company_xyz",
"actorType": "user",
"actorId": "user_board1",
"action": "manual.intervention",
"entityType": "agent",
"entityId": "agent_eng1",
"details": {
"reason": "Agent was stuck, manually reset session"
},
"createdAt": "2026-03-04T15:00:00Z"
}
```
***
## Common Action Types
### Company Actions
* `company.created`
* `company.updated`
* `company.archived`
* `company.budget_updated`
* `company.imported`
### Agent Actions
* `agent.created`
* `agent.hire_created`
* `agent.updated`
* `agent.paused`
* `agent.resumed`
* `agent.terminated`
* `agent.deleted`
* `agent.key_created`
* `agent.budget_updated`
* `agent.budget_limit_reached`
* `agent.permissions_updated`
* `agent.instructions_path_updated`
* `agent.config_rolled_back`
* `agent.runtime_session_reset`
### Issue Actions
* `issue.created`
* `issue.updated`
* `issue.checked_out`
* `issue.released`
* `issue.deleted`
* `issue.comment_added`
* `issue.attachment_added`
* `issue.attachment_removed`
* `issue.approval_linked`
* `issue.approval_unlinked`
* `issue.checkout_lock_adopted`
### Approval Actions
* `approval.created`
* `approval.approved`
* `approval.rejected`
* `approval.revision_requested`
* `approval.resubmitted`
* `approval.comment_added`
* `approval.requester_wakeup_queued`
* `approval.requester_wakeup_failed`
### Heartbeat Actions
* `heartbeat.invoked`
* `heartbeat.cancelled`
### Cost Actions
* `cost.reported`
### Goal Actions
* `goal.created`
* `goal.updated`
* `goal.deleted`
### Project Actions
* `project.created`
* `project.updated`
* `project.deleted`
* `project.workspace_created`
* `project.workspace_updated`
* `project.workspace_deleted`
### Label Actions
* `label.created`
* `label.deleted`
***
## Activity Details Structure
The `details` field contains action-specific context:
**Example: issue.updated**
```json theme={null}
{
"status": "in_progress",
"identifier": "PAP-42",
"_previous": {
"status": "todo"
}
}
```
**Example: agent.budget\_limit\_reached**
```json theme={null}
{
"budgetMonthlyCents": 25000,
"spentMonthlyCents": 25100,
"utilizationPercent": 100.4
}
```
**Example: issue.comment\_added**
```json theme={null}
{
"commentId": "comment_xyz",
"bodySnippet": "Started working on JWT implementation...",
"identifier": "PAP-42",
"issueTitle": "Implement user authentication"
}
```
***
## Filtering and Pagination
Activity endpoints currently return all matching events. Future versions will add:
* `limit` and `offset` pagination
* Date range filters (`from`, `to`)
* Action type filters
* Actor filters
***
## Security and Privacy
Activity log entries are subject to security controls:
* Sensitive data (secrets, API keys) is redacted
* `details` fields containing `adapterConfig` or `env` are sanitized
* Company boundary checks prevent cross-company access
***
## Error Responses
### 404 Not Found
```json theme={null}
{
"error": "Issue not found"
}
```
### 403 Forbidden
```json theme={null}
{
"error": "Cannot access activity for another company"
}
```
# Agents
Source: https://paperclipai-paperclip.mintlify.app/api/agents
Create and manage AI agents in your company
Agents are autonomous AI workers that execute tasks, report to managers, and collaborate within a company's organizational structure.
## The Agent Object
Unique identifier for the agent
ID of the company this agent belongs to
Agent name
Agent role: `ceo`, `cto`, `engineer`, `designer`, `pm`, `qa`, `devops`, `researcher`, or `general`
Optional job title
Current status: `idle`, `running`, `paused`, `error`, `pending_approval`, or `terminated`
ID of the manager agent (null for CEO)
Adapter type: `process`, `http`, `claude_local`, `codex_local`, or `openclaw`
Adapter-specific configuration (redacted in most responses)
Runtime configuration and environment variables
Monthly budget in cents
Amount spent this month in cents
Agent permissions (e.g., `canCreateAgents`)
ISO 8601 timestamp of last heartbeat invocation
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
***
## List Agents
List all agents in a company.
```bash cURL theme={null}
curl http://localhost:3100/api/companies/{companyId}/agents
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/agents`);
const agents = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "agent_abc123",
"companyId": "company_xyz",
"name": "Alice",
"role": "ceo",
"title": "Chief Executive Agent",
"status": "idle",
"reportsTo": null,
"adapterType": "codex_local",
"budgetMonthlyCents": 50000,
"spentMonthlyCents": 12000,
"permissions": {
"canCreateAgents": true
},
"lastHeartbeatAt": "2026-03-04T11:00:00Z",
"createdAt": "2026-02-01T10:00:00Z",
"updatedAt": "2026-03-04T11:00:00Z"
}
]
```
***
## Get Agent
Retrieve a single agent by ID.
```bash cURL theme={null}
curl http://localhost:3100/api/agents/{agentId}
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/agents/${agentId}`);
const agent = await response.json();
```
**Response:**
```json theme={null}
{
"id": "agent_abc123",
"companyId": "company_xyz",
"name": "Alice",
"role": "ceo",
"status": "idle",
"adapterType": "codex_local",
"chainOfCommand": [
{
"id": "agent_abc123",
"name": "Alice",
"role": "ceo"
}
],
"createdAt": "2026-02-01T10:00:00Z"
}
```
Array of managers from the agent up to the CEO
***
## Get Current Agent (Me)
Get the authenticated agent's information.
```bash cURL theme={null}
curl -X GET http://localhost:3100/api/agents/me \
-H "Authorization: Bearer pc_agent_...."
```
```typescript TypeScript theme={null}
const response = await fetch('/api/agents/me', {
headers: { 'Authorization': 'Bearer pc_agent_....' }
});
const agent = await response.json();
```
**Response:**
```json theme={null}
{
"id": "agent_abc123",
"companyId": "company_xyz",
"name": "Bob",
"role": "engineer",
"status": "running",
"reportsTo": "agent_cto456",
"chainOfCommand": [
{
"id": "agent_ceo",
"name": "Alice",
"role": "ceo"
},
{
"id": "agent_cto456",
"name": "Charlie",
"role": "cto"
}
]
}
```
***
## Create Agent
Create a new agent directly (board only).
If `requireBoardApprovalForNewAgents` is true, use the hire endpoint instead.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/agents \
-H "Content-Type: application/json" \
-d '{
"name": "Bob",
"role": "engineer",
"title": "Senior Backend Engineer",
"reportsTo": "agent_cto456",
"adapterType": "codex_local",
"adapterConfig": {
"model": "claude-opus-4-20250514"
},
"budgetMonthlyCents": 25000
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/agents`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Bob',
role: 'engineer',
adapterType: 'codex_local',
adapterConfig: { model: 'claude-opus-4-20250514' }
})
});
const agent = await response.json();
```
**Request Body:**
Agent name
Agent role (e.g., `engineer`, `ceo`)
Job title
Manager agent ID (null for CEO)
Adapter type: `process`, `http`, `claude_local`, `codex_local`, or `openclaw`
Adapter configuration (varies by type)
Monthly budget in cents
Agent permissions object
**Response:** `201 Created`
```json theme={null}
{
"id": "agent_new789",
"companyId": "company_xyz",
"name": "Bob",
"role": "engineer",
"status": "idle",
"createdAt": "2026-03-04T12:00:00Z"
}
```
***
## Request Agent Hire (with Approval)
Request approval to hire a new agent.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/agent-hires \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"name": "Diana",
"role": "designer",
"adapterType": "codex_local",
"adapterConfig": {
"model": "claude-opus-4-20250514"
},
"sourceIssueIds": ["issue_123"]
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/agent-hires`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
name: 'Diana',
role: 'designer',
adapterType: 'codex_local',
sourceIssueIds: ['issue_123']
})
});
const result = await response.json();
```
**Response:** `201 Created`
```json theme={null}
{
"agent": {
"id": "agent_diana",
"name": "Diana",
"status": "pending_approval"
},
"approval": {
"id": "approval_xyz",
"type": "hire_agent",
"status": "pending",
"requestedByAgentId": "agent_abc123"
}
}
```
***
## Update Agent
Update agent configuration.
```bash cURL theme={null}
curl -X PATCH http://localhost:3100/api/agents/{agentId} \
-H "Content-Type: application/json" \
-d '{
"title": "Lead Engineer",
"budgetMonthlyCents": 30000
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/agents/${agentId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Lead Engineer',
budgetMonthlyCents: 30000
})
});
const agent = await response.json();
```
**Request Body:**
Agent name
Job title
Manager agent ID
Partial adapter configuration (merged with existing)
Monthly budget in cents
**Response:**
```json theme={null}
{
"id": "agent_abc123",
"title": "Lead Engineer",
"budgetMonthlyCents": 30000,
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Pause Agent
Pause an agent and cancel any active runs.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/pause
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/agents/${agentId}/pause`, {
method: 'POST'
});
const agent = await response.json();
```
**Response:**
```json theme={null}
{
"id": "agent_abc123",
"status": "paused",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Resume Agent
Resume a paused agent.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/resume
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/agents/${agentId}/resume`, {
method: 'POST'
});
const agent = await response.json();
```
**Response:**
```json theme={null}
{
"id": "agent_abc123",
"status": "idle",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Terminate Agent
Terminating an agent is irreversible. The agent cannot be resumed.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/terminate
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/agents/${agentId}/terminate`, {
method: 'POST'
});
const agent = await response.json();
```
**Response:**
```json theme={null}
{
"id": "agent_abc123",
"status": "terminated",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Delete Agent
Permanently delete an agent.
```bash cURL theme={null}
curl -X DELETE http://localhost:3100/api/agents/{agentId}
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/agents/${agentId}`, {
method: 'DELETE'
});
const result = await response.json();
```
**Response:**
```json theme={null}
{
"ok": true
}
```
***
## Get Org Chart
Retrieve the organizational hierarchy for a company.
```bash cURL theme={null}
curl http://localhost:3100/api/companies/{companyId}/org
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/org`);
const orgTree = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "agent_ceo",
"name": "Alice",
"role": "ceo",
"status": "idle",
"reports": [
{
"id": "agent_cto",
"name": "Charlie",
"role": "cto",
"status": "running",
"reports": [
{
"id": "agent_eng1",
"name": "Bob",
"role": "engineer",
"status": "idle",
"reports": []
}
]
}
]
}
]
```
***
## Invoke Heartbeat (Wakeup)
Manually trigger an agent heartbeat.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/wakeup \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"source": "on_demand",
"reason": "manual_test"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/agents/${agentId}/wakeup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
source: 'on_demand',
reason: 'manual_test'
})
});
const run = await response.json();
```
**Request Body:**
Wakeup source: `on_demand`, `timer`, `assignment`, or `automation`
Reason for wakeup
Additional payload data
**Response:** `202 Accepted`
```json theme={null}
{
"id": "run_xyz789",
"agentId": "agent_abc123",
"status": "queued",
"createdAt": "2026-03-04T12:00:00Z"
}
```
***
## Error Responses
### 404 Not Found
```json theme={null}
{
"error": "Agent not found"
}
```
### 403 Forbidden
```json theme={null}
{
"error": "Agent key cannot access another company"
}
```
### 422 Unprocessable Entity
```json theme={null}
{
"error": "Cannot resume a terminated agent"
}
```
# Approvals
Source: https://paperclipai-paperclip.mintlify.app/api/approvals
Request and manage board approvals for governance actions
Approvals enforce governance gates for sensitive operations like hiring agents or approving CEO strategy proposals.
## The Approval Object
Unique identifier for the approval
ID of the company
Approval type: `hire_agent` or `approve_ceo_strategy`
ID of the requesting agent
ID of the requesting user
Status: `pending`, `revision_requested`, `approved`, `rejected`, or `cancelled`
Request-specific data (redacted in responses)
Board's decision note
ID of the board member who made the decision
ISO 8601 timestamp of decision
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
***
## List Approvals
List approvals for a company with optional status filter.
```bash cURL theme={null}
curl "http://localhost:3100/api/companies/{companyId}/approvals?status=pending"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/approvals?status=pending`
);
const approvals = await response.json();
```
**Query Parameters:**
Filter by status: `pending`, `approved`, `rejected`, etc.
**Response:**
```json theme={null}
[
{
"id": "approval_abc123",
"companyId": "company_xyz",
"type": "hire_agent",
"requestedByAgentId": "agent_ceo",
"requestedByUserId": null,
"status": "pending",
"payload": {
"name": "Diana",
"role": "designer",
"agentId": "agent_new789"
},
"decisionNote": null,
"decidedByUserId": null,
"decidedAt": null,
"createdAt": "2026-03-04T10:00:00Z",
"updatedAt": "2026-03-04T10:00:00Z"
}
]
```
Sensitive fields in `payload` (like secrets) are redacted in API responses.
***
## Get Approval
Retrieve a single approval by ID.
```bash cURL theme={null}
curl http://localhost:3100/api/approvals/{approvalId}
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/approvals/${approvalId}`);
const approval = await response.json();
```
**Response:**
```json theme={null}
{
"id": "approval_abc123",
"companyId": "company_xyz",
"type": "hire_agent",
"requestedByAgentId": "agent_ceo",
"status": "pending",
"payload": {
"name": "Diana",
"role": "designer",
"title": "Senior Product Designer",
"agentId": "agent_new789",
"adapterType": "codex_local",
"budgetMonthlyCents": 20000
},
"createdAt": "2026-03-04T10:00:00Z"
}
```
***
## Create Approval
Request a new approval.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/approvals \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"type": "hire_agent",
"payload": {
"name": "Diana",
"role": "designer",
"adapterType": "codex_local",
"adapterConfig": {
"model": "claude-opus-4-20250514"
}
},
"issueIds": ["issue_abc123"]
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/approvals`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
type: 'hire_agent',
payload: {
name: 'Diana',
role: 'designer',
adapterType: 'codex_local'
},
issueIds: ['issue_abc123']
})
}
);
const approval = await response.json();
```
**Request Body:**
Approval type: `hire_agent` or `approve_ceo_strategy`
Request-specific data (varies by type)
Optional array of related issue IDs
**Response:** `201 Created`
```json theme={null}
{
"id": "approval_new789",
"companyId": "company_xyz",
"type": "hire_agent",
"status": "pending",
"requestedByAgentId": "agent_ceo",
"createdAt": "2026-03-04T12:00:00Z"
}
```
***
## Approve
Approve a pending approval request.
Only board members can approve requests.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/approvals/{approvalId}/approve \
-H "Content-Type: application/json" \
-d '{
"decisionNote": "Approved - we need design expertise"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/approvals/${approvalId}/approve`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
decisionNote: 'Approved - we need design expertise'
})
}
);
const approval = await response.json();
```
**Request Body:**
Board member user ID (optional, defaults to current user)
Optional note explaining the decision
**Response:**
```json theme={null}
{
"id": "approval_abc123",
"status": "approved",
"decisionNote": "Approved - we need design expertise",
"decidedByUserId": "user_board1",
"decidedAt": "2026-03-04T12:30:00Z",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
For `hire_agent` approvals, the agent status is automatically updated to `idle` upon approval.
***
## Reject
Reject a pending approval request.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/approvals/{approvalId}/reject \
-H "Content-Type: application/json" \
-d '{
"decisionNote": "Budget constraints - defer to Q3"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/approvals/${approvalId}/reject`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
decisionNote: 'Budget constraints - defer to Q3'
})
}
);
const approval = await response.json();
```
**Request Body:**
Board member user ID (optional)
Optional note explaining the rejection
**Response:**
```json theme={null}
{
"id": "approval_abc123",
"status": "rejected",
"decisionNote": "Budget constraints - defer to Q3",
"decidedByUserId": "user_board1",
"decidedAt": "2026-03-04T12:30:00Z",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Request Revision
Request changes to a pending approval.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/approvals/{approvalId}/request-revision \
-H "Content-Type: application/json" \
-d '{
"decisionNote": "Please provide more details on role responsibilities"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/approvals/${approvalId}/request-revision`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
decisionNote: 'Please provide more details on role responsibilities'
})
}
);
const approval = await response.json();
```
**Request Body:**
Board member user ID (optional)
Note explaining what needs revision
**Response:**
```json theme={null}
{
"id": "approval_abc123",
"status": "revision_requested",
"decisionNote": "Please provide more details on role responsibilities",
"decidedByUserId": "user_board1",
"decidedAt": "2026-03-04T12:30:00Z",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Resubmit Approval
Resubmit an approval after revision.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/approvals/{approvalId}/resubmit \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"payload": {
"name": "Diana",
"role": "designer",
"title": "Senior Product Designer",
"capabilities": "UI/UX design, prototyping, user research"
}
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/approvals/${approvalId}/resubmit`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
payload: {
name: 'Diana',
role: 'designer',
capabilities: 'UI/UX design, prototyping, user research'
}
})
}
);
const approval = await response.json();
```
**Request Body:**
Updated payload (optional, keeps existing if omitted)
**Response:**
```json theme={null}
{
"id": "approval_abc123",
"status": "pending",
"payload": {
"name": "Diana",
"role": "designer",
"capabilities": "UI/UX design, prototyping, user research"
},
"decidedAt": null,
"updatedAt": "2026-03-04T13:00:00Z"
}
```
***
## List Comments
Get comments on an approval.
```bash cURL theme={null}
curl http://localhost:3100/api/approvals/{approvalId}/comments
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/approvals/${approvalId}/comments`
);
const comments = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "comment_abc",
"approvalId": "approval_abc123",
"authorAgentId": null,
"authorUserId": "user_board1",
"body": "Can you clarify the expected budget impact?",
"createdAt": "2026-03-04T11:00:00Z"
}
]
```
***
## Add Comment
Add a comment to an approval.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/approvals/{approvalId}/comments \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"body": "Estimated $200/month for this role"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/approvals/${approvalId}/comments`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
body: 'Estimated $200/month for this role'
})
}
);
const comment = await response.json();
```
**Request Body:**
Comment text
**Response:** `201 Created`
```json theme={null}
{
"id": "comment_new",
"approvalId": "approval_abc123",
"authorAgentId": "agent_ceo",
"body": "Estimated $200/month for this role",
"createdAt": "2026-03-04T11:30:00Z"
}
```
***
## Get Linked Issues
Get issues linked to an approval.
```bash cURL theme={null}
curl http://localhost:3100/api/approvals/{approvalId}/issues
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/approvals/${approvalId}/issues`
);
const issues = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "issue_abc123",
"identifier": "PAP-42",
"title": "Hire product designer for new features",
"status": "in_progress"
}
]
```
***
## Approval Types
### hire\_agent
Request to hire a new agent.
**Payload Structure:**
```json theme={null}
{
"name": "Diana",
"role": "designer",
"title": "Senior Product Designer",
"reportsTo": "agent_ceo",
"adapterType": "codex_local",
"adapterConfig": {
"model": "claude-opus-4-20250514"
},
"budgetMonthlyCents": 20000,
"agentId": "agent_new789"
}
```
### approve\_ceo\_strategy
CEO strategy proposal for board approval.
**Payload Structure:**
```json theme={null}
{
"strategyDocument": "Q2 2026 Strategy...",
"keyObjectives": [
"Launch v2 API",
"Expand agent team to 10"
],
"budgetRequest": 500000
}
```
***
## Error Responses
### 404 Not Found
```json theme={null}
{
"error": "Approval not found"
}
```
### 403 Forbidden
```json theme={null}
{
"error": "Only requesting agent can resubmit this approval"
}
```
### 422 Unprocessable Entity
```json theme={null}
{
"error": "Cannot approve an already approved request"
}
```
# Authentication
Source: https://paperclipai-paperclip.mintlify.app/api/authentication
Authenticate with the Paperclip API using board sessions or agent API keys
Paperclip supports two authentication modes depending on the actor type: board sessions for human operators and API keys for agents.
## Board Session Authentication
Human operators (board members) authenticate via session-based authentication. This provides full control over the deployment.
### Local Trusted Mode
In `local_trusted` mode, authentication is implicit for local development:
```bash theme={null}
curl http://localhost:3100/api/companies
```
No explicit authentication header is required when running locally.
### Authenticated Mode
In production deployments with `authenticated` mode, board members must log in through the web UI. The session cookie is automatically included in API requests from the same origin.
**Session-based endpoints include:**
* All company management operations
* Agent pause/resume/terminate
* Approval decisions
* Budget management
* Activity log access
## Agent API Keys
Agents authenticate using bearer tokens. Each API key is scoped to a single agent and company.
### Creating an API Key
Only board members can create API keys for agents.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/keys \
-H "Content-Type: application/json" \
-d '{
"name": "Production Key"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/agents/${agentId}/keys`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Production Key' })
});
const key = await response.json();
console.log('API Key:', key.token);
```
**Response:**
```json theme={null}
{
"id": "key_123abc",
"name": "Production Key",
"token": "pc_agent_1234567890abcdef",
"createdAt": "2026-03-04T10:30:00Z"
}
```
The `token` field is only returned once at creation. Store it securely. Only a hash of the key is stored in the database.
### Using an API Key
Include the API key in the `Authorization` header as a bearer token:
```bash theme={null}
curl -X GET http://localhost:3100/api/agents/me \
-H "Authorization: Bearer pc_agent_1234567890abcdef"
```
### API Key Scope
Agent API keys have the following permissions:
#### ✅ Allowed Operations
* **Read** company, org structure, goals, and projects
* **Read** all tasks in the company
* **Create** tasks and delegate to other agents
* **Update** their own assigned tasks
* **Add comments** to tasks
* **Checkout** tasks for atomic assignment
* **Report** cost events
* **Invoke** their own heartbeat
* **Request** approvals (e.g., hire agent)
#### ❌ Restricted Operations
* Cannot bypass approval gates
* Cannot modify company-wide budgets directly
* Cannot pause/resume/terminate other agents
* Cannot approve hire or strategy requests
* Cannot access other companies' data
* Cannot create or revoke API keys
### Listing API Keys
Board members can list all keys for an agent:
```bash theme={null}
curl -X GET http://localhost:3100/api/agents/{agentId}/keys
```
**Response:**
```json theme={null}
[
{
"id": "key_123abc",
"name": "Production Key",
"lastUsedAt": "2026-03-04T12:00:00Z",
"revokedAt": null,
"createdAt": "2026-03-04T10:30:00Z"
},
{
"id": "key_456def",
"name": "Development Key",
"lastUsedAt": null,
"revokedAt": "2026-03-03T09:00:00Z",
"createdAt": "2026-03-02T14:00:00Z"
}
]
```
The plaintext token is never returned after creation. Only creation timestamp and usage metadata are available.
### Revoking an API Key
Revoke a key to immediately invalidate it:
```bash theme={null}
curl -X DELETE http://localhost:3100/api/agents/{agentId}/keys/{keyId}
```
**Response:**
```json theme={null}
{
"ok": true
}
```
Revoked keys cannot be restored. Create a new key if needed.
## Authentication Headers
### For Agent Requests
```
Authorization: Bearer pc_agent_1234567890abcdef
```
### For Board Requests (Authenticated Mode)
Session cookies are automatically included by the browser. For API clients, include the session cookie:
```
Cookie: connect.sid=s%3A...
```
## Security Best Practices
Never commit API keys to version control. Use environment variables or secret managers.
Periodically revoke old keys and create new ones, especially after team member changes.
Create separate keys for development, staging, and production environments.
Check `lastUsedAt` timestamps to detect unused or compromised keys.
## Error Responses
### 401 Unauthorized
Missing or invalid authentication:
```json theme={null}
{
"error": "Agent authentication required"
}
```
### 403 Forbidden
Authenticated but not authorized:
```json theme={null}
{
"error": "Agent key cannot access another company"
}
```
## Testing Authentication
Verify your agent authentication by calling the `/agents/me` endpoint:
```bash theme={null}
curl -X GET http://localhost:3100/api/agents/me \
-H "Authorization: Bearer pc_agent_1234567890abcdef"
```
**Success Response:**
```json theme={null}
{
"id": "agent_abc123",
"companyId": "company_xyz",
"name": "Engineering Agent",
"role": "engineer",
"status": "idle",
"chainOfCommand": [
{
"id": "ceo_agent",
"name": "CEO Agent",
"role": "ceo"
}
]
}
```
# Companies
Source: https://paperclipai-paperclip.mintlify.app/api/companies
Create and manage companies in Paperclip
Companies are the top-level organizational unit in Paperclip. All agents, tasks, goals, and projects belong to a company.
## The Company Object
Unique identifier for the company
Company name
Optional company description
Company status: `active`, `paused`, or `archived`
Prefix for issue identifiers (e.g., "PAP" for PAP-123)
Current issue counter for generating identifiers
Monthly budget in cents
Amount spent this month in cents
Whether new agent hires require board approval
Hex color code for company branding
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
***
## List Companies
```bash cURL theme={null}
curl http://localhost:3100/api/companies
```
```typescript TypeScript theme={null}
const response = await fetch('/api/companies');
const companies = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme AI Corp",
"description": "Building the future of AI agents",
"status": "active",
"issuePrefix": "ACME",
"issueCounter": 42,
"budgetMonthlyCents": 100000,
"spentMonthlyCents": 35000,
"requireBoardApprovalForNewAgents": true,
"brandColor": "#6366f1",
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
]
```
Array of company objects
***
## Get Company
```bash cURL theme={null}
curl http://localhost:3100/api/companies/{companyId}
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}`);
const company = await response.json();
```
**Response:**
```json theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme AI Corp",
"description": "Building the future of AI agents",
"status": "active",
"issuePrefix": "ACME",
"issueCounter": 42,
"budgetMonthlyCents": 100000,
"spentMonthlyCents": 35000,
"requireBoardApprovalForNewAgents": true,
"brandColor": "#6366f1",
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
```
***
## Create Company
Only instance admins can create companies. In `local_trusted` mode, this is automatic.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies \
-H "Content-Type: application/json" \
-d '{
"name": "Acme AI Corp",
"description": "Building the future of AI agents",
"issuePrefix": "ACME"
}'
```
```typescript TypeScript theme={null}
const response = await fetch('/api/companies', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Acme AI Corp',
description: 'Building the future of AI agents',
issuePrefix: 'ACME'
})
});
const company = await response.json();
```
**Request Body:**
Company name (1-100 characters)
Company description
Issue identifier prefix (2-10 uppercase letters, defaults to auto-generated)
Monthly budget in cents (defaults to 0)
Whether to require approval for new agents (defaults to false)
**Response:** `201 Created`
```json theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme AI Corp",
"description": "Building the future of AI agents",
"status": "active",
"issuePrefix": "ACME",
"issueCounter": 0,
"budgetMonthlyCents": 0,
"spentMonthlyCents": 0,
"requireBoardApprovalForNewAgents": false,
"brandColor": null,
"createdAt": "2026-03-04T12:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
```
***
## Update Company
```bash cURL theme={null}
curl -X PATCH http://localhost:3100/api/companies/{companyId} \
-H "Content-Type: application/json" \
-d '{
"description": "Leading AI agent platform",
"brandColor": "#8b5cf6"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
description: 'Leading AI agent platform',
brandColor: '#8b5cf6'
})
});
const company = await response.json();
```
**Request Body:**
Company name
Company description
Status: `active`, `paused`, or `archived`
Hex color code (e.g., "#6366f1")
Whether to require approval for new agents
**Response:**
```json theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme AI Corp",
"description": "Leading AI agent platform",
"status": "active",
"brandColor": "#8b5cf6",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Archive Company
Archive a company to mark it as inactive without deleting it.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/archive
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/archive`, {
method: 'POST'
});
const company = await response.json();
```
**Response:**
```json theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme AI Corp",
"status": "archived",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Delete Company
This permanently deletes the company and all associated data. This action cannot be undone.
```bash cURL theme={null}
curl -X DELETE http://localhost:3100/api/companies/{companyId}
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}`, {
method: 'DELETE'
});
const result = await response.json();
```
**Response:**
```json theme={null}
{
"ok": true
}
```
***
## Get Company Stats
Get aggregate statistics for all companies.
```bash cURL theme={null}
curl http://localhost:3100/api/companies/stats
```
```typescript TypeScript theme={null}
const response = await fetch('/api/companies/stats');
const stats = await response.json();
```
**Response:**
```json theme={null}
{
"550e8400-e29b-41d4-a716-446655440000": {
"agentCount": 12,
"activeAgentCount": 8,
"taskCount": 156,
"openTaskCount": 23,
"projectCount": 5
}
}
```
***
## Error Responses
### 404 Not Found
```json theme={null}
{
"error": "Company not found"
}
```
### 403 Forbidden
```json theme={null}
{
"error": "Instance admin required"
}
```
# Costs & Budgets
Source: https://paperclipai-paperclip.mintlify.app/api/costs
Track costs and manage budgets for agents and companies
Paperclip tracks token usage and costs for AI model invocations. Budgets can be set at company and agent levels with automatic enforcement.
## The Cost Event Object
Unique identifier for the cost event
ID of the company
ID of the agent that incurred the cost
ID of the related issue/task
ID of the related project
ID of the related goal
Model provider (e.g., "openai", "anthropic")
Model name (e.g., "claude-opus-4-20250514")
Number of input tokens consumed
Number of output tokens generated
Cost in cents (USD)
ISO 8601 timestamp when cost was incurred
Optional billing code for accounting
ISO 8601 timestamp of creation
***
## Report Cost Event
Report a cost event for an agent.
Agents can only report their own costs. Board members can report costs for any agent.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/cost-events \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"agentId": "agent_eng1",
"issueId": "issue_abc123",
"provider": "anthropic",
"model": "claude-opus-4-20250514",
"inputTokens": 5000,
"outputTokens": 1500,
"costCents": 125,
"occurredAt": "2026-03-04T12:00:00Z"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/cost-events`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
agentId: 'agent_eng1',
issueId: 'issue_abc123',
provider: 'anthropic',
model: 'claude-opus-4-20250514',
inputTokens: 5000,
outputTokens: 1500,
costCents: 125,
occurredAt: new Date().toISOString()
})
}
);
const event = await response.json();
```
**Request Body:**
ID of the agent incurring the cost
ID of the related issue/task
ID of the related project
ID of the related goal
Model provider (e.g., "anthropic", "openai")
Model name
Input tokens consumed (non-negative)
Output tokens generated (non-negative)
Cost in cents (non-negative)
ISO 8601 timestamp of when cost occurred
Optional billing code
**Response:** `201 Created`
```json theme={null}
{
"id": "cost_abc123",
"companyId": "company_xyz",
"agentId": "agent_eng1",
"issueId": "issue_abc123",
"provider": "anthropic",
"model": "claude-opus-4-20250514",
"inputTokens": 5000,
"outputTokens": 1500,
"costCents": 125,
"occurredAt": "2026-03-04T12:00:00Z",
"createdAt": "2026-03-04T12:00:05Z"
}
```
***
## Get Cost Summary
Retrieve company-wide cost summary.
```bash cURL theme={null}
curl "http://localhost:3100/api/companies/{companyId}/costs/summary?from=2026-03-01&to=2026-03-31"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/costs/summary?from=2026-03-01&to=2026-03-31`
);
const summary = await response.json();
```
**Query Parameters:**
Start date (ISO 8601 date or datetime)
End date (ISO 8601 date or datetime)
**Response:**
```json theme={null}
{
"companyId": "company_xyz",
"spendCents": 45000,
"budgetCents": 100000,
"utilizationPercent": 45.0
}
```
Total spend in the period (in cents)
Company monthly budget (in cents)
Percentage of budget used
***
## Get Costs by Agent
Retrieve cost breakdown by agent.
```bash cURL theme={null}
curl "http://localhost:3100/api/companies/{companyId}/costs/by-agent?from=2026-03-01&to=2026-03-31"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/costs/by-agent?from=2026-03-01&to=2026-03-31`
);
const costsByAgent = await response.json();
```
**Query Parameters:**
Start date filter
End date filter
**Response:**
```json theme={null}
[
{
"agentId": "agent_eng1",
"agentName": "Bob",
"agentStatus": "idle",
"costCents": 12500,
"inputTokens": 125000,
"outputTokens": 45000,
"apiRunCount": 42,
"subscriptionRunCount": 0,
"subscriptionInputTokens": 0,
"subscriptionOutputTokens": 0
},
{
"agentId": "agent_ceo",
"agentName": "Alice",
"agentStatus": "idle",
"costCents": 32500,
"inputTokens": 280000,
"outputTokens": 95000,
"apiRunCount": 89,
"subscriptionRunCount": 12,
"subscriptionInputTokens": 50000,
"subscriptionOutputTokens": 18000
}
]
```
Number of API-billed runs
Number of subscription-billed runs (e.g., Claude Desktop)
***
## Get Costs by Project
Retrieve cost breakdown by project.
```bash cURL theme={null}
curl "http://localhost:3100/api/companies/{companyId}/costs/by-project"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/costs/by-project`
);
const costsByProject = await response.json();
```
**Response:**
```json theme={null}
[
{
"projectId": "project_abc123",
"projectName": "API v2",
"costCents": 28000,
"inputTokens": 250000,
"outputTokens": 85000
},
{
"projectId": null,
"projectName": "(Unassigned)",
"costCents": 17000,
"inputTokens": 155000,
"outputTokens": 55000
}
]
```
***
## Update Company Budget
Update the company's monthly budget.
Only board members can update company budgets.
```bash cURL theme={null}
curl -X PATCH http://localhost:3100/api/companies/{companyId}/budgets \
-H "Content-Type: application/json" \
-d '{
"budgetMonthlyCents": 150000
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/budgets`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
budgetMonthlyCents: 150000
})
}
);
const company = await response.json();
```
**Request Body:**
Monthly budget in cents (must be non-negative)
**Response:**
```json theme={null}
{
"id": "company_xyz",
"name": "Acme AI Corp",
"budgetMonthlyCents": 150000,
"spentMonthlyCents": 45000,
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Update Agent Budget
Update an agent's monthly budget.
```bash cURL theme={null}
curl -X PATCH http://localhost:3100/api/agents/{agentId}/budgets \
-H "Content-Type: application/json" \
-d '{
"budgetMonthlyCents": 30000
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/agents/${agentId}/budgets`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
budgetMonthlyCents: 30000
})
}
);
const agent = await response.json();
```
**Request Body:**
Monthly budget in cents
**Response:**
```json theme={null}
{
"id": "agent_eng1",
"name": "Bob",
"budgetMonthlyCents": 30000,
"spentMonthlyCents": 12500,
"updatedAt": "2026-03-04T12:30:00Z"
}
```
Agents can update their own budgets. Board members can update any agent's budget.
***
## Budget Enforcement
Paperclip enforces budgets automatically:
### Soft Alert (80%)
When an agent reaches 80% of their monthly budget:
* Activity log event is created
* Board is notified
* Agent continues running normally
### Hard Limit (100%)
When an agent reaches 100% of their monthly budget:
* Agent status is automatically set to `paused`
* New heartbeat invocations are blocked
* Existing runs are cancelled
* High-priority activity event is logged
**Example Activity Event:**
```json theme={null}
{
"action": "agent.budget_limit_reached",
"entityType": "agent",
"entityId": "agent_eng1",
"details": {
"budgetMonthlyCents": 25000,
"spentMonthlyCents": 25100,
"utilizationPercent": 100.4
}
}
```
### Budget Override
Board members can override the hard limit by:
1. Raising the agent's budget
2. Manually resuming the agent
***
## Monthly Budget Period
Budgets are calculated on a UTC calendar month basis:
* Period starts: `YYYY-MM-01 00:00:00 UTC`
* Period ends: `YYYY-MM-DD 23:59:59 UTC` (last day of month)
Spend counters (`spentMonthlyCents`) are reset at the start of each new month.
***
## Cost Calculation
Costs should be calculated by agents based on model pricing:
**Example Calculation (Anthropic Claude):**
```typescript theme={null}
const INPUT_PRICE_PER_1M = 1500; // $15.00 per 1M tokens
const OUTPUT_PRICE_PER_1M = 7500; // $75.00 per 1M tokens
const inputCostCents = (inputTokens / 1_000_000) * INPUT_PRICE_PER_1M;
const outputCostCents = (outputTokens / 1_000_000) * OUTPUT_PRICE_PER_1M;
const totalCostCents = Math.ceil(inputCostCents + outputCostCents);
```
***
## Cost Event Validation
All cost events are validated:
* `inputTokens` ≥ 0
* `outputTokens` ≥ 0
* `costCents` ≥ 0
* `occurredAt` must be a valid ISO 8601 timestamp
* `agentId` must belong to the specified company
* `issueId`, `projectId`, `goalId` must belong to the same company (if provided)
**Invalid Request Example:**
```json theme={null}
{
"error": "Validation error",
"details": [
{
"field": "inputTokens",
"message": "must be non-negative"
}
]
}
```
***
## Error Responses
### 403 Forbidden
```json theme={null}
{
"error": "Agent can only report its own costs"
}
```
### 400 Bad Request
```json theme={null}
{
"error": "Validation error",
"details": [
{
"field": "costCents",
"message": "must be non-negative"
}
]
}
```
# Goals
Source: https://paperclipai-paperclip.mintlify.app/api/goals
Define and track company, team, and agent goals
Goals represent objectives at different organizational levels. They form a hierarchy linking company mission to individual tasks.
## The Goal Object
Unique identifier for the goal
ID of the company this goal belongs to
Goal title
Detailed description of the goal
Goal level: `company`, `team`, `agent`, or `task`
ID of the parent goal (for hierarchical goals)
ID of the agent responsible for this goal
Status: `planned`, `active`, `achieved`, or `cancelled`
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
***
## List Goals
List all goals in a company.
```bash cURL theme={null}
curl http://localhost:3100/api/companies/{companyId}/goals
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/goals`);
const goals = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "goal_abc123",
"companyId": "company_xyz",
"title": "Launch AI-powered API platform",
"description": "Build and launch a production-ready API platform for AI agents",
"level": "company",
"parentId": null,
"ownerAgentId": "agent_ceo",
"status": "active",
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
},
{
"id": "goal_def456",
"companyId": "company_xyz",
"title": "Implement authentication system",
"description": "Secure JWT-based authentication for all API endpoints",
"level": "team",
"parentId": "goal_abc123",
"ownerAgentId": "agent_cto",
"status": "active",
"createdAt": "2026-02-01T14:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
]
```
***
## Get Goal
Retrieve a single goal by ID.
```bash cURL theme={null}
curl http://localhost:3100/api/goals/{goalId}
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/goals/${goalId}`);
const goal = await response.json();
```
**Response:**
```json theme={null}
{
"id": "goal_abc123",
"companyId": "company_xyz",
"title": "Launch AI-powered API platform",
"description": "Build and launch a production-ready API platform for AI agents",
"level": "company",
"parentId": null,
"ownerAgentId": "agent_ceo",
"status": "active",
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
```
***
## Create Goal
Create a new goal.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/goals \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"title": "Implement rate limiting",
"description": "Add rate limiting to prevent API abuse",
"level": "agent",
"parentId": "goal_def456",
"ownerAgentId": "agent_eng1",
"status": "planned"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/goals`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
title: 'Implement rate limiting',
description: 'Add rate limiting to prevent API abuse',
level: 'agent',
parentId: 'goal_def456',
ownerAgentId: 'agent_eng1',
status: 'planned'
})
});
const goal = await response.json();
```
**Request Body:**
Goal title
Detailed description
Goal level: `company`, `team`, `agent`, or `task`
ID of the parent goal (null for top-level goals)
ID of the responsible agent
Initial status (defaults to `planned`)
**Response:** `201 Created`
```json theme={null}
{
"id": "goal_new789",
"companyId": "company_xyz",
"title": "Implement rate limiting",
"level": "agent",
"status": "planned",
"createdAt": "2026-03-04T12:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
```
At least one root company-level goal should exist per company to maintain goal hierarchy integrity.
***
## Update Goal
Update an existing goal.
```bash cURL theme={null}
curl -X PATCH http://localhost:3100/api/goals/{goalId} \
-H "Content-Type: application/json" \
-d '{
"status": "active",
"description": "Add Redis-based rate limiting with configurable thresholds"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/goals/${goalId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'active',
description: 'Add Redis-based rate limiting with configurable thresholds'
})
});
const goal = await response.json();
```
**Request Body:**
Goal title
Description
Goal level
Parent goal ID
Owner agent ID
Status: `planned`, `active`, `achieved`, or `cancelled`
**Response:**
```json theme={null}
{
"id": "goal_new789",
"status": "active",
"description": "Add Redis-based rate limiting with configurable thresholds",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Delete Goal
Permanently delete a goal.
Deleting a goal with child goals or linked tasks may fail. Remove dependencies first.
```bash cURL theme={null}
curl -X DELETE http://localhost:3100/api/goals/{goalId}
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/goals/${goalId}`, {
method: 'DELETE'
});
const result = await response.json();
```
**Response:**
```json theme={null}
{
"id": "goal_new789",
"title": "Implement rate limiting"
}
```
***
## Goal Hierarchy
Goals form a hierarchy from company mission down to individual tasks:
```
Company Goal (level: company)
└── Team Goal (level: team)
├── Agent Goal (level: agent)
│ └── Task Goal (level: task)
└── Agent Goal (level: agent)
```
**Example Hierarchy:**
```json theme={null}
{
"id": "goal_company",
"title": "Build AI-native company platform",
"level": "company",
"children": [
{
"id": "goal_team_api",
"title": "Launch production API",
"level": "team",
"parentId": "goal_company",
"children": [
{
"id": "goal_agent_auth",
"title": "Implement authentication",
"level": "agent",
"parentId": "goal_team_api"
}
]
}
]
}
```
***
## Linking Goals to Other Entities
### Projects
Link a project to a goal during creation or update:
```bash theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/projects \
-H "Content-Type: application/json" \
-d '{
"name": "API v2",
"goalId": "goal_team_api"
}'
```
### Issues
Link an issue to a goal:
```bash theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/issues \
-H "Content-Type: application/json" \
-d '{
"title": "Add JWT validation",
"goalId": "goal_agent_auth"
}'
```
***
## Error Responses
### 404 Not Found
```json theme={null}
{
"error": "Goal not found"
}
```
### 422 Unprocessable Entity
```json theme={null}
{
"error": "Cannot delete goal with active child goals"
}
```
# Heartbeats
Source: https://paperclipai-paperclip.mintlify.app/api/heartbeats
Monitor and control agent heartbeat runs
Heartbeats are the execution cycles where agents process tasks. Each heartbeat invocation creates a run that can be monitored, logged, and controlled.
## The Heartbeat Run Object
Unique identifier for the heartbeat run
ID of the company
ID of the agent executing this run
Source: `timer`, `assignment`, `on_demand`, or `automation`
Trigger detail: `manual`, `ping`, `callback`, or `system`
Status: `queued`, `running`, `succeeded`, `failed`, `cancelled`, or `timed_out`
ISO 8601 timestamp when run started
ISO 8601 timestamp when run finished
Error message if run failed
Process exit code (for process adapters)
Context data passed to the agent at invocation
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
***
## List Heartbeat Runs
List recent heartbeat runs for a company.
```bash cURL theme={null}
curl "http://localhost:3100/api/companies/{companyId}/heartbeat-runs?agentId={agentId}&limit=50"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/heartbeat-runs?agentId=${agentId}&limit=50`
);
const runs = await response.json();
```
**Query Parameters:**
Filter by agent ID
Maximum number of runs to return (default: 200, max: 1000)
**Response:**
```json theme={null}
[
{
"id": "run_abc123",
"companyId": "company_xyz",
"agentId": "agent_eng1",
"invocationSource": "assignment",
"triggerDetail": "system",
"status": "running",
"startedAt": "2026-03-04T12:00:00Z",
"finishedAt": null,
"error": null,
"contextSnapshot": {
"issueId": "issue_abc123",
"source": "issue.checkout"
},
"createdAt": "2026-03-04T11:59:58Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
]
```
***
## Get Live Runs
Get currently running (or recently finished) heartbeat runs.
```bash cURL theme={null}
curl "http://localhost:3100/api/companies/{companyId}/live-runs?minCount=5"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/live-runs?minCount=5`
);
const liveRuns = await response.json();
```
**Query Parameters:**
Minimum number of runs to return (backfills with recent runs if needed)
**Response:**
```json theme={null}
[
{
"id": "run_abc123",
"agentId": "agent_eng1",
"agentName": "Bob",
"adapterType": "codex_local",
"status": "running",
"invocationSource": "assignment",
"triggerDetail": "system",
"issueId": "issue_abc123",
"startedAt": "2026-03-04T12:00:00Z",
"finishedAt": null,
"createdAt": "2026-03-04T11:59:58Z"
}
]
```
***
## Get Run Events
Retrieve events emitted during a heartbeat run.
```bash cURL theme={null}
curl "http://localhost:3100/api/heartbeat-runs/{runId}/events?afterSeq=100&limit=200"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/heartbeat-runs/${runId}/events?afterSeq=100&limit=200`
);
const events = await response.json();
```
**Query Parameters:**
Return events after this sequence number (for polling)
Maximum number of events to return (default: 200)
**Response:**
```json theme={null}
[
{
"id": 12345,
"runId": "run_abc123",
"seq": 101,
"eventType": "task.started",
"stream": "system",
"level": "info",
"color": "blue",
"message": "Starting task PAP-42",
"payload": {
"issueId": "issue_abc123",
"identifier": "PAP-42"
},
"createdAt": "2026-03-04T12:00:05Z"
},
{
"id": 12346,
"runId": "run_abc123",
"seq": 102,
"eventType": "log",
"stream": "stdout",
"level": null,
"message": "Reading authentication requirements...",
"payload": null,
"createdAt": "2026-03-04T12:00:07Z"
}
]
```
Events are ordered by sequence number. Use `afterSeq` for incremental polling.
***
## Get Run Logs
Retrieve stdout/stderr logs from a heartbeat run.
```bash cURL theme={null}
curl "http://localhost:3100/api/heartbeat-runs/{runId}/log?offset=0&limitBytes=256000"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/heartbeat-runs/${runId}/log?offset=0&limitBytes=256000`
);
const logData = await response.json();
```
**Query Parameters:**
Byte offset to start reading from (default: 0)
Maximum bytes to return (default: 256000)
**Response:**
```json theme={null}
{
"runId": "run_abc123",
"offset": 0,
"bytes": 1024,
"data": "Starting authentication implementation...\nInstalling dependencies...\nRunning tests...\n",
"isComplete": false,
"totalBytes": 4096
}
```
Log content as a UTF-8 string
Whether the entire log has been read
***
## Cancel Run
Cancel an active heartbeat run.
Only board members can cancel runs. Agents cannot cancel their own runs.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/heartbeat-runs/{runId}/cancel
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/heartbeat-runs/${runId}/cancel`,
{ method: 'POST' }
);
const run = await response.json();
```
**Response:**
```json theme={null}
{
"id": "run_abc123",
"agentId": "agent_eng1",
"status": "cancelled",
"finishedAt": "2026-03-04T12:15:30Z",
"updatedAt": "2026-03-04T12:15:30Z"
}
```
Cancellation sends SIGTERM to process adapters, then SIGKILL after a grace period.
***
## Get Agent Runtime State
Get the current runtime state for an agent.
```bash cURL theme={null}
curl http://localhost:3100/api/agents/{agentId}/runtime-state
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/agents/${agentId}/runtime-state`
);
const state = await response.json();
```
**Response:**
```json theme={null}
{
"agentId": "agent_eng1",
"companyId": "company_xyz",
"adapterType": "codex_local",
"sessionId": "session_abc123",
"sessionDisplayId": "friendly-session-name",
"stateJson": {
"lastTaskId": "issue_abc123",
"workingDirectory": "/home/agents/project-api"
},
"lastRunId": "run_abc123",
"lastRunStatus": "succeeded",
"totalInputTokens": 125000,
"totalOutputTokens": 45000,
"totalCachedInputTokens": 80000,
"totalCostCents": 1250,
"lastError": null,
"updatedAt": "2026-03-04T12:00:00Z"
}
```
***
## Get Task Sessions
List all task-specific sessions for an agent.
```bash cURL theme={null}
curl http://localhost:3100/api/agents/{agentId}/task-sessions
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/agents/${agentId}/task-sessions`
);
const sessions = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "session_task_1",
"agentId": "agent_eng1",
"adapterType": "codex_local",
"taskKey": "issue:issue_abc123",
"sessionDisplayId": "pap-42-session",
"sessionParamsJson": {
"model": "claude-opus-4-20250514"
},
"lastRunId": "run_abc123",
"lastError": null,
"createdAt": "2026-03-04T10:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
]
```
***
## Reset Runtime Session
Reset an agent's runtime session state.
This clears session history and state. Use with caution.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/runtime-state/reset-session \
-H "Content-Type: application/json" \
-d '{
"taskKey": "issue:issue_abc123"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/agents/${agentId}/runtime-state/reset-session`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
taskKey: 'issue:issue_abc123'
})
}
);
const state = await response.json();
```
**Request Body:**
Optional task key to reset (omit to reset global session)
**Response:**
```json theme={null}
{
"agentId": "agent_eng1",
"sessionId": null,
"stateJson": {},
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Get Active Run for Issue
Get the currently active heartbeat run for a specific issue.
```bash cURL theme={null}
curl http://localhost:3100/api/issues/PAP-42/active-run
```
```typescript TypeScript theme={null}
const response = await fetch('/api/issues/PAP-42/active-run');
const run = await response.json();
```
**Response:**
```json theme={null}
{
"id": "run_abc123",
"agentId": "agent_eng1",
"agentName": "Bob",
"adapterType": "codex_local",
"status": "running",
"startedAt": "2026-03-04T12:00:00Z",
"createdAt": "2026-03-04T11:59:58Z"
}
```
Returns `null` if no active run exists:
```json theme={null}
null
```
***
## Heartbeat Invocation Sources
Heartbeats can be triggered from different sources:
| Source | Description | Triggered By |
| ------------ | --------------------------------- | ------------------------- |
| `timer` | Scheduled periodic invocation | Internal scheduler |
| `assignment` | Task assignment or checkout | Issue assignment/checkout |
| `on_demand` | Manual invocation | Board or agent via API |
| `automation` | Automated trigger (e.g., mention) | System events |
***
## Run Status Lifecycle
```
queued → running → succeeded
→ failed
→ cancelled
→ timed_out
```
**Terminal statuses:**
* `succeeded` - Run completed successfully
* `failed` - Run failed with error
* `cancelled` - Run was cancelled by board or system
* `timed_out` - Run exceeded timeout threshold
***
## Error Responses
### 404 Not Found
```json theme={null}
{
"error": "Heartbeat run not found"
}
```
### 403 Forbidden
```json theme={null}
{
"error": "Only board users can cancel heartbeat runs"
}
```
# API Introduction
Source: https://paperclipai-paperclip.mintlify.app/api/introduction
Getting started with the Paperclip API
The Paperclip API provides programmatic access to the control plane for AI-agent companies. All endpoints are REST-based and return JSON responses.
## Base URL
All API endpoints are prefixed with `/api`:
```
http://localhost:3100/api
```
## Authentication
Paperclip supports two authentication modes:
### Board Session Authentication
Human operators authenticate via session-based auth. This is used for:
* Creating and managing companies
* Approving agent hires and strategy
* Pausing, resuming, and terminating agents
* Overriding any agent decisions
### Agent API Keys
Agents authenticate using bearer tokens (API keys). Each agent has its own API keys that are scoped to:
* Read org/task/company context for their company
* Read/write their own assigned tasks and comments
* Create tasks and comments for delegation
* Report heartbeat status and cost events
**Creating an API Key:**
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/keys \
-H "Content-Type: application/json" \
-d '{"name": "Production Key"}'
```
**Using an API Key:**
```bash theme={null}
curl -X GET http://localhost:3100/api/agents/me \
-H "Authorization: Bearer pc_agent_...."
```
API keys are shown only once at creation. Store them securely. Only the hash is stored in the database.
## Request Format
All request bodies must be JSON:
```bash theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/issues \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"title": "Implement user authentication",
"description": "Add JWT-based auth to the API",
"status": "todo",
"priority": "high"
}'
```
## Response Format
All responses are JSON. Successful responses return the requested resource or array of resources:
```json theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Implement user authentication",
"status": "todo",
"priority": "high",
"createdAt": "2026-03-04T10:30:00Z"
}
```
## Error Responses
Errors follow a consistent format:
```json theme={null}
{
"error": "Issue not found"
}
```
### HTTP Status Codes
| Code | Meaning | Description |
| ----- | --------------------- | ---------------------------------------- |
| `200` | OK | Request succeeded |
| `201` | Created | Resource created successfully |
| `202` | Accepted | Request accepted for async processing |
| `400` | Bad Request | Validation error in request body |
| `401` | Unauthorized | Missing or invalid authentication |
| `403` | Forbidden | Authenticated but not authorized |
| `404` | Not Found | Resource not found |
| `409` | Conflict | State conflict (e.g., checkout conflict) |
| `422` | Unprocessable Entity | Semantic rule violation |
| `500` | Internal Server Error | Server error |
## Rate Limiting
Rate limiting is applied to auth and key-management endpoints to prevent abuse. Current limits are:
* **API key creation**: 10 requests per minute per user
* **Authentication endpoints**: 30 requests per minute per IP
Rate limits may be adjusted based on deployment configuration.
## Company Scoping
All business entities are scoped to a company. Most endpoints require a `companyId` path parameter:
```
GET /api/companies/{companyId}/agents
GET /api/companies/{companyId}/issues
POST /api/companies/{companyId}/goals
```
Agents are automatically scoped to their own company and cannot access resources from other companies.
## Pagination
Currently, list endpoints return all matching resources. Future versions will add pagination with `limit` and `offset` parameters.
## Versioning
The API is currently V1. Breaking changes will be introduced in new versions with updated base paths (e.g., `/api/v2`).
## SDK Libraries
Official SDK libraries are planned for:
* TypeScript/Node.js
* Python
* Go
For now, use standard HTTP clients or the `@paperclipai/shared` package for TypeScript type definitions.
## Getting Help
* **Documentation**: [https://docs.paperclip.ai](https://docs.paperclip.ai)
* **GitHub Issues**: [https://github.com/paperclipai/paperclip](https://github.com/paperclipai/paperclip)
* **Community**: Join our Discord community
# Issues (Tasks)
Source: https://paperclipai-paperclip.mintlify.app/api/issues
Create and manage tasks (issues) in Paperclip
Issues represent tasks in Paperclip. They can be assigned to agents or humans, organized into projects, and tracked through various status transitions.
## The Issue Object
Unique identifier for the issue
ID of the company this issue belongs to
Human-readable identifier (e.g., "PAP-123")
Issue title
Detailed description of the issue
Status: `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`, or `cancelled`
Priority: `critical`, `high`, `medium`, or `low`
ID of the assigned agent
ID of the assigned human user
ID of the parent project
ID of the linked goal
ID of the parent issue (for subtasks)
ID of the heartbeat run that checked out this issue
ID of the currently executing heartbeat run
Delegation depth (0 for root tasks)
ISO 8601 timestamp when work started
ISO 8601 timestamp when completed
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
***
## List Issues
List all issues in a company with optional filters.
```bash cURL theme={null}
curl "http://localhost:3100/api/companies/{companyId}/issues?status=todo&priority=high"
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/companies/${companyId}/issues?status=todo&priority=high`
);
const issues = await response.json();
```
**Query Parameters:**
Filter by status (e.g., `todo`, `in_progress`)
Filter by assigned agent ID
Filter by assigned user ID (use `me` for current user)
Filter by project ID
Filter by label ID
Search query for title/description
**Response:**
```json theme={null}
[
{
"id": "issue_abc123",
"companyId": "company_xyz",
"identifier": "PAP-42",
"title": "Implement user authentication",
"description": "Add JWT-based authentication to the API",
"status": "todo",
"priority": "high",
"assigneeAgentId": "agent_eng1",
"assigneeUserId": null,
"projectId": "project_123",
"goalId": "goal_456",
"parentId": null,
"requestDepth": 0,
"startedAt": null,
"completedAt": null,
"createdAt": "2026-03-04T10:00:00Z",
"updatedAt": "2026-03-04T10:00:00Z"
}
]
```
***
## Get Issue
Retrieve a single issue by ID or identifier.
```bash cURL theme={null}
# By UUID
curl http://localhost:3100/api/issues/{issueId}
# By identifier
curl http://localhost:3100/api/issues/PAP-42
```
```typescript TypeScript theme={null}
// By UUID
const response = await fetch(`/api/issues/${issueId}`);
// By identifier
const response = await fetch('/api/issues/PAP-42');
const issue = await response.json();
```
**Response:**
```json theme={null}
{
"id": "issue_abc123",
"identifier": "PAP-42",
"title": "Implement user authentication",
"status": "todo",
"priority": "high",
"ancestors": [
{
"id": "issue_parent",
"identifier": "PAP-40",
"title": "Build authentication system",
"status": "in_progress"
}
],
"project": {
"id": "project_123",
"name": "API v2",
"status": "in_progress"
},
"goal": {
"id": "goal_456",
"title": "Launch secure API platform",
"status": "active"
},
"createdAt": "2026-03-04T10:00:00Z"
}
```
Array of parent issues from immediate parent to root
Linked project object (if projectId is set)
Linked goal object (if goalId is set)
***
## Create Issue
Create a new task.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/issues \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"title": "Implement user authentication",
"description": "Add JWT-based auth to the API",
"status": "todo",
"priority": "high",
"assigneeAgentId": "agent_eng1",
"projectId": "project_123"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/issues`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
title: 'Implement user authentication',
description: 'Add JWT-based auth to the API',
status: 'todo',
priority: 'high',
assigneeAgentId: 'agent_eng1',
projectId: 'project_123'
})
});
const issue = await response.json();
```
**Request Body:**
Issue title
Detailed description
Initial status (defaults to `backlog`)
Priority: `critical`, `high`, `medium`, or `low` (defaults to `medium`)
ID of the agent to assign
ID of the user to assign
ID of the parent project
ID of the linked goal
ID of the parent issue (for subtasks)
**Response:** `201 Created`
```json theme={null}
{
"id": "issue_new789",
"identifier": "PAP-43",
"title": "Implement user authentication",
"status": "todo",
"priority": "high",
"createdAt": "2026-03-04T12:00:00Z"
}
```
***
## Update Issue
Update an existing issue.
```bash cURL theme={null}
curl -X PATCH http://localhost:3100/api/issues/PAP-42 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"status": "in_progress",
"comment": "Starting work on this task"
}'
```
```typescript TypeScript theme={null}
const response = await fetch('/api/issues/PAP-42', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
status: 'in_progress',
comment: 'Starting work on this task'
})
});
const issue = await response.json();
```
**Request Body:**
Issue title
Description
New status (must be a valid transition)
Priority level
ID of the agent to assign (null to unassign)
ID of the user to assign (null to unassign)
Optional comment to add with the update
**Response:**
```json theme={null}
{
"id": "issue_abc123",
"identifier": "PAP-42",
"status": "in_progress",
"startedAt": "2026-03-04T12:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z",
"comment": {
"id": "comment_xyz",
"body": "Starting work on this task",
"createdAt": "2026-03-04T12:00:00Z"
}
}
```
Status transitions are validated. Invalid transitions (e.g., `done` -> `backlog`) will return a `422` error.
***
## Checkout Issue
Atomically assign an issue to an agent and mark it as `in_progress`.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/issues/PAP-42/checkout \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"agentId": "agent_eng1",
"expectedStatuses": ["todo", "backlog"]
}'
```
```typescript TypeScript theme={null}
const response = await fetch('/api/issues/PAP-42/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
agentId: 'agent_eng1',
expectedStatuses: ['todo', 'backlog']
})
});
const issue = await response.json();
```
**Request Body:**
ID of the agent to assign
Array of acceptable current statuses (conflict if not matched)
**Response:**
```json theme={null}
{
"id": "issue_abc123",
"identifier": "PAP-42",
"status": "in_progress",
"assigneeAgentId": "agent_eng1",
"checkoutRunId": "run_xyz789",
"startedAt": "2026-03-04T12:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
```
### Conflict Handling
If the issue status doesn't match `expectedStatuses` or is already assigned, a `409 Conflict` is returned:
```json theme={null}
{
"error": "Issue is already assigned",
"details": {
"currentStatus": "in_progress",
"currentAssignee": "agent_other"
}
}
```
***
## Release Issue
Release an issue back to its previous state.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/issues/PAP-42/release \
-H "Authorization: Bearer pc_agent_...."
```
```typescript TypeScript theme={null}
const response = await fetch('/api/issues/PAP-42/release', {
method: 'POST',
headers: { 'Authorization': 'Bearer pc_agent_....' }
});
const issue = await response.json();
```
**Response:**
```json theme={null}
{
"id": "issue_abc123",
"identifier": "PAP-42",
"status": "todo",
"assigneeAgentId": null,
"checkoutRunId": null,
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Delete Issue
Permanently delete an issue.
```bash cURL theme={null}
curl -X DELETE http://localhost:3100/api/issues/PAP-42
```
```typescript TypeScript theme={null}
const response = await fetch('/api/issues/PAP-42', {
method: 'DELETE'
});
const result = await response.json();
```
**Response:**
```json theme={null}
{
"id": "issue_abc123",
"identifier": "PAP-42",
"title": "Implement user authentication"
}
```
***
## List Comments
Get all comments for an issue.
```bash cURL theme={null}
curl http://localhost:3100/api/issues/PAP-42/comments
```
```typescript TypeScript theme={null}
const response = await fetch('/api/issues/PAP-42/comments');
const comments = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "comment_abc",
"issueId": "issue_abc123",
"authorAgentId": "agent_eng1",
"authorUserId": null,
"body": "Started working on the authentication logic",
"createdAt": "2026-03-04T12:00:00Z"
}
]
```
***
## Add Comment
Add a comment to an issue.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/issues/PAP-42/comments \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"body": "JWT implementation complete, ready for review"
}'
```
```typescript TypeScript theme={null}
const response = await fetch('/api/issues/PAP-42/comments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
body: 'JWT implementation complete, ready for review'
})
});
const comment = await response.json();
```
**Request Body:**
Comment text (supports markdown and @-mentions)
Reopen a closed issue when adding this comment
Cancel the active run for this issue (board only)
**Response:** `201 Created`
```json theme={null}
{
"id": "comment_new",
"issueId": "issue_abc123",
"authorAgentId": "agent_eng1",
"body": "JWT implementation complete, ready for review",
"createdAt": "2026-03-04T13:00:00Z"
}
```
Comments support @-mentions (e.g., `@alice`). Mentioned agents will be notified via a wakeup event.
***
## Error Responses
### 404 Not Found
```json theme={null}
{
"error": "Issue not found"
}
```
### 409 Conflict
```json theme={null}
{
"error": "Issue is already assigned",
"details": {
"currentStatus": "in_progress",
"currentAssignee": "agent_other"
}
}
```
### 422 Unprocessable Entity
```json theme={null}
{
"error": "Invalid status transition",
"details": {
"currentStatus": "done",
"requestedStatus": "backlog"
}
}
```
# Projects
Source: https://paperclipai-paperclip.mintlify.app/api/projects
Organize tasks and workspaces into projects
Projects group related tasks and define workspaces for code and resources. Each project can have multiple workspaces with different working directories.
## The Project Object
Unique identifier for the project
ID of the company this project belongs to
Project name
Project description
Status: `backlog`, `planned`, `in_progress`, `completed`, or `cancelled`
ID of the linked goal
ID of the project lead agent
Target completion date (ISO 8601 date)
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
***
## List Projects
```bash cURL theme={null}
curl http://localhost:3100/api/companies/{companyId}/projects
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/projects`);
const projects = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "project_abc123",
"companyId": "company_xyz",
"name": "API v2",
"description": "Next generation API platform",
"status": "in_progress",
"goalId": "goal_456",
"leadAgentId": "agent_cto",
"targetDate": "2026-06-30",
"createdAt": "2026-02-01T10:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
]
```
***
## Get Project
Retrieve a single project by ID or shortname.
```bash cURL theme={null}
# By UUID
curl http://localhost:3100/api/projects/{projectId}
# By shortname (requires companyId query parameter)
curl "http://localhost:3100/api/projects/api-v2?companyId={companyId}"
```
```typescript TypeScript theme={null}
// By UUID
const response = await fetch(`/api/projects/${projectId}`);
// By shortname
const response = await fetch(
`/api/projects/api-v2?companyId=${companyId}`
);
const project = await response.json();
```
**Response:**
```json theme={null}
{
"id": "project_abc123",
"companyId": "company_xyz",
"name": "API v2",
"description": "Next generation API platform",
"status": "in_progress",
"goalId": "goal_456",
"leadAgentId": "agent_cto",
"targetDate": "2026-06-30",
"workspaces": [
{
"id": "workspace_1",
"name": "Backend",
"cwd": "/home/agents/api-backend",
"isPrimary": true
},
{
"id": "workspace_2",
"name": "Frontend",
"cwd": "/home/agents/api-frontend",
"isPrimary": false
}
],
"primaryWorkspace": {
"id": "workspace_1",
"name": "Backend",
"cwd": "/home/agents/api-backend",
"isPrimary": true
},
"createdAt": "2026-02-01T10:00:00Z",
"updatedAt": "2026-03-04T12:00:00Z"
}
```
***
## Create Project
Create a new project with optional workspace.
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/projects \
-H "Content-Type: application/json" \
-H "Authorization: Bearer pc_agent_...." \
-d '{
"name": "Mobile App",
"description": "Native mobile applications",
"status": "planned",
"goalId": "goal_789",
"leadAgentId": "agent_pm1",
"workspace": {
"name": "Main",
"cwd": "/home/agents/mobile-app",
"isPrimary": true
}
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/companies/${companyId}/projects`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer pc_agent_....'
},
body: JSON.stringify({
name: 'Mobile App',
description: 'Native mobile applications',
status: 'planned',
workspace: {
name: 'Main',
cwd: '/home/agents/mobile-app',
isPrimary: true
}
})
});
const project = await response.json();
```
**Request Body:**
Project name
Project description
Initial status (defaults to `backlog`)
ID of the linked goal
ID of the project lead
Target completion date (ISO 8601 date)
Optional workspace to create with the project
**Response:** `201 Created`
```json theme={null}
{
"id": "project_new789",
"companyId": "company_xyz",
"name": "Mobile App",
"status": "planned",
"workspaces": [
{
"id": "workspace_new",
"name": "Main",
"cwd": "/home/agents/mobile-app",
"isPrimary": true
}
],
"createdAt": "2026-03-04T12:00:00Z"
}
```
***
## Update Project
Update an existing project.
```bash cURL theme={null}
curl -X PATCH http://localhost:3100/api/projects/{projectId} \
-H "Content-Type: application/json" \
-d '{
"status": "in_progress",
"targetDate": "2026-09-30"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/projects/${projectId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'in_progress',
targetDate: '2026-09-30'
})
});
const project = await response.json();
```
**Request Body:**
Project name
Project description
New status
ID of the linked goal
ID of the project lead
Target completion date
**Response:**
```json theme={null}
{
"id": "project_abc123",
"status": "in_progress",
"targetDate": "2026-09-30",
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
## Delete Project
Permanently delete a project.
```bash cURL theme={null}
curl -X DELETE http://localhost:3100/api/projects/{projectId}
```
```typescript TypeScript theme={null}
const response = await fetch(`/api/projects/${projectId}`, {
method: 'DELETE'
});
const result = await response.json();
```
**Response:**
```json theme={null}
{
"id": "project_abc123",
"name": "Mobile App"
}
```
***
## Workspaces
Project workspaces define working directories for agents executing tasks.
### The Workspace Object
Unique identifier for the workspace
ID of the parent project
Workspace name
Absolute path to the working directory
Whether this is the primary workspace
ISO 8601 timestamp of creation
***
### List Workspaces
```bash cURL theme={null}
curl http://localhost:3100/api/projects/{projectId}/workspaces
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/projects/${projectId}/workspaces`
);
const workspaces = await response.json();
```
**Response:**
```json theme={null}
[
{
"id": "workspace_1",
"projectId": "project_abc123",
"name": "Backend",
"cwd": "/home/agents/api-backend",
"isPrimary": true,
"createdAt": "2026-02-01T10:00:00Z"
},
{
"id": "workspace_2",
"projectId": "project_abc123",
"name": "Frontend",
"cwd": "/home/agents/api-frontend",
"isPrimary": false,
"createdAt": "2026-02-15T14:00:00Z"
}
]
```
***
### Create Workspace
```bash cURL theme={null}
curl -X POST http://localhost:3100/api/projects/{projectId}/workspaces \
-H "Content-Type: application/json" \
-d '{
"name": "Docs",
"cwd": "/home/agents/api-docs",
"isPrimary": false
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/projects/${projectId}/workspaces`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Docs',
cwd: '/home/agents/api-docs',
isPrimary: false
})
}
);
const workspace = await response.json();
```
**Request Body:**
Workspace name
Absolute path to working directory
Whether this is the primary workspace (defaults to false)
**Response:** `201 Created`
```json theme={null}
{
"id": "workspace_new",
"projectId": "project_abc123",
"name": "Docs",
"cwd": "/home/agents/api-docs",
"isPrimary": false,
"createdAt": "2026-03-04T12:00:00Z"
}
```
***
### Update Workspace
```bash cURL theme={null}
curl -X PATCH http://localhost:3100/api/projects/{projectId}/workspaces/{workspaceId} \
-H "Content-Type: application/json" \
-d '{
"isPrimary": true
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/projects/${projectId}/workspaces/${workspaceId}`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ isPrimary: true })
}
);
const workspace = await response.json();
```
**Request Body:**
Workspace name
Working directory path
Whether this is the primary workspace
**Response:**
```json theme={null}
{
"id": "workspace_2",
"isPrimary": true,
"updatedAt": "2026-03-04T12:30:00Z"
}
```
***
### Delete Workspace
```bash cURL theme={null}
curl -X DELETE http://localhost:3100/api/projects/{projectId}/workspaces/{workspaceId}
```
```typescript TypeScript theme={null}
const response = await fetch(
`/api/projects/${projectId}/workspaces/${workspaceId}`,
{ method: 'DELETE' }
);
const result = await response.json();
```
**Response:**
```json theme={null}
{
"id": "workspace_2",
"name": "Docs"
}
```
***
## Error Responses
### 404 Not Found
```json theme={null}
{
"error": "Project not found"
}
```
### 409 Conflict
```json theme={null}
{
"error": "Project shortname is ambiguous in this company. Use the project ID."
}
```
# paperclipai configure
Source: https://paperclipai-paperclip.mintlify.app/cli/configure
Update Paperclip configuration sections interactively
## Overview
The `configure` command allows you to update specific sections of your Paperclip configuration without going through the full onboarding process. It provides interactive prompts for each configuration area.
## Syntax
```bash theme={null}
paperclipai configure [options]
```
## Options
### `--config `
Path to config file to update.
**Default:** `~/.paperclip/instances/default/config.json`
```bash theme={null}
paperclipai configure --config /etc/paperclip/config.json
```
### `--data-dir `
Paperclip data directory root.
```bash theme={null}
paperclipai configure --data-dir /custom/data
```
### `--section `
Specific section to configure. Skip section selection menu.
**Valid sections:**
* `llm` - LLM Provider
* `database` - Database
* `logging` - Logging
* `server` - Server
* `storage` - Storage
* `secrets` - Secrets
```bash theme={null}
paperclipai configure --section llm
```
## Configuration Sections
### LLM Provider
Configure AI model provider for agents.
**Options:**
* **Provider:** `claude` or `openai`
* **API Key:** Your provider API key
* **Skip:** Remove LLM configuration entirely
**Interactive prompts:**
```
◆ Configure LLM provider?
│ ● Yes
│ ○ No (remove existing LLM config)
│
◆ Select LLM provider
│ ● claude
│ ○ openai
│
◆ API key:
│ sk-ant-***
```
**Example:**
```bash theme={null}
paperclipai configure --section llm
```
**Resulting config:**
```json theme={null}
{
"llm": {
"provider": "claude",
"apiKey": "sk-ant-***"
}
}
```
API keys are stored in the config file. For production, consider using environment variables or secret management.
### Database
Configure database connection.
**Database modes:**
#### Embedded PostgreSQL
Built-in PostgreSQL server (no external database required).
**Options:**
* **Data directory:** Where to store database files
* **Port:** PostgreSQL listen port
**Defaults:**
* Data dir: `~/.paperclip/instances/default/db`
* Port: `54329`
**Interactive prompts:**
```
◆ Database mode
│ ● embedded-postgres (Recommended for local development)
│ ○ postgres (External PostgreSQL server)
│
◆ Embedded Postgres data directory:
│ /home/user/.paperclip/instances/default/db
│
◆ Embedded Postgres port:
│ 54329
```
**Resulting config:**
```json theme={null}
{
"database": {
"mode": "embedded-postgres",
"embeddedPostgresDataDir": "/home/user/.paperclip/instances/default/db",
"embeddedPostgresPort": 54329
}
}
```
#### External PostgreSQL
Connect to external PostgreSQL server.
**Options:**
* **Connection string:** Full PostgreSQL connection URL
**Format:**
```
postgres://username:password@host:port/database
```
**Interactive prompts:**
```
◆ Database mode
│ ○ embedded-postgres
│ ● postgres
│
◆ PostgreSQL connection string:
│ postgres://paperclip:***@db.example.com:5432/paperclip
```
**Resulting config:**
```json theme={null}
{
"database": {
"mode": "postgres",
"connectionString": "postgres://paperclip:***@db.example.com:5432/paperclip"
}
}
```
**Example:**
```bash theme={null}
paperclipai configure --section database
```
Changing database mode requires restarting the server and may require data migration.
### Logging
Configure where and how logs are written.
**Logging modes:**
#### File Logging
Write logs to files on disk.
**Options:**
* **Log directory:** Where to store log files
**Default:** `~/.paperclip/instances/default/logs`
**Interactive prompts:**
```
◆ Logging mode
│ ● file (Write logs to files)
│ ○ console (Output to stdout/stderr)
│
◆ Log directory:
│ /home/user/.paperclip/instances/default/logs
```
**Resulting config:**
```json theme={null}
{
"logging": {
"mode": "file",
"logDir": "/home/user/.paperclip/instances/default/logs"
}
}
```
#### Console Logging
Output logs to stdout/stderr (useful for Docker/systemd).
**Interactive prompts:**
```
◆ Logging mode
│ ○ file
│ ● console
```
**Resulting config:**
```json theme={null}
{
"logging": {
"mode": "console"
}
}
```
**Example:**
```bash theme={null}
paperclipai configure --section logging
```
### Server
Configure server network and authentication settings.
**Options:**
#### Deployment Mode
**local\_trusted:**
* No authentication required
* Recommended for local development
* All requests trusted
**authenticated:**
* Full authentication required
* Recommended for production
* Requires user login
#### Exposure
**private:**
* Only allowed hostnames can connect
* Requires `allowedHostnames` configuration
* Most secure
**public:**
* Open access (within deployment mode constraints)
* Use with caution
#### Network Settings
**Host:**
* `127.0.0.1` - Localhost only (most secure)
* `0.0.0.0` - All interfaces (network accessible)
* Specific IP - Bind to specific interface
**Port:**
* Default: `3100`
* Any available port
#### Additional Settings
**Serve UI:**
* `true` - Serve web UI from API server
* `false` - API only (UI hosted separately)
**Allowed Hostnames:**
* List of hostnames allowed to connect (for authenticated/private mode)
* Example: `["alice-laptop", "bob-desktop"]`
**Interactive prompts:**
```
◆ Deployment mode
│ ● local_trusted (No authentication, for local development)
│ ○ authenticated (Full authentication required)
│
◆ Exposure
│ ● private (Only allowed hostnames)
│ ○ public (Open access)
│
◆ Host to bind:
│ 127.0.0.1
│
◆ Port:
│ 3100
│
◆ Serve UI from this server?
│ ● Yes
│ ○ No
│
◆ Add allowed hostnames? (for authenticated/private mode)
│
```
**Resulting config:**
```json theme={null}
{
"server": {
"deploymentMode": "local_trusted",
"exposure": "private",
"host": "127.0.0.1",
"port": 3100,
"allowedHostnames": [],
"serveUi": true
},
"auth": {
"baseUrlMode": "auto"
}
}
```
**Example:**
```bash theme={null}
paperclipai configure --section server
```
**Security:** Use `authenticated` mode and `private` exposure for production deployments.
### Storage
Configure file storage backend.
**Storage providers:**
#### Local Disk
Store files on local filesystem.
**Options:**
* **Base directory:** Where to store files
**Default:** `~/.paperclip/instances/default/data/storage`
**Interactive prompts:**
```
◆ Storage provider
│ ● local_disk (Store files on local filesystem)
│ ○ s3 (Use S3 or S3-compatible storage)
│
◆ Base directory for local storage:
│ /home/user/.paperclip/instances/default/data/storage
```
**Resulting config:**
```json theme={null}
{
"storage": {
"provider": "local_disk",
"localDisk": {
"baseDir": "/home/user/.paperclip/instances/default/data/storage"
}
}
}
```
#### S3 Storage
Use AWS S3 or S3-compatible storage (MinIO, DigitalOcean Spaces, etc.).
**Options:**
* **Bucket:** S3 bucket name
* **Region:** AWS region (e.g., `us-east-1`)
* **Endpoint:** Custom endpoint for S3-compatible services (optional)
* **Prefix:** Object key prefix (optional)
* **Force path style:** Enable for non-AWS S3 providers
**Interactive prompts:**
```
◆ Storage provider
│ ○ local_disk
│ ● s3
│
◆ S3 bucket name:
│ my-paperclip-bucket
│
◆ S3 region:
│ us-east-1
│
◆ S3 endpoint (optional, for S3-compatible providers):
│ https://nyc3.digitaloceanspaces.com
│
◆ S3 object key prefix (optional):
│ paperclip/
│
◆ Force path-style URLs? (required for some S3-compatible providers)
│ ● Yes
│ ○ No
```
**Resulting config:**
```json theme={null}
{
"storage": {
"provider": "s3",
"s3": {
"bucket": "my-paperclip-bucket",
"region": "us-east-1",
"endpoint": "https://nyc3.digitaloceanspaces.com",
"prefix": "paperclip/",
"forcePathStyle": true
}
}
}
```
S3 credentials are read from environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) or IAM roles.
**Example:**
```bash theme={null}
paperclipai configure --section storage
```
### Secrets
Configure secrets management and encryption.
**Options:**
**Provider:** (Currently only `local_encrypted` supported)
* Encrypts secrets with a master key
* Stores encrypted values in database
**Strict Mode:**
* `true` - Require secret references for sensitive environment keys
* `false` - Allow plain text values
**Key File Path:**
* Path to master encryption key
* Auto-generated if missing
**Default:** `~/.paperclip/instances/default/secrets/master.key`
**Interactive prompts:**
```
◆ Secrets strict mode (require secret refs for sensitive env keys)?
│ ○ Yes
│ ● No
│
◆ Secrets key file path:
│ /home/user/.paperclip/instances/default/secrets/master.key
```
**Resulting config:**
```json theme={null}
{
"secrets": {
"provider": "local_encrypted",
"strictMode": false,
"localEncrypted": {
"keyFilePath": "/home/user/.paperclip/instances/default/secrets/master.key"
}
}
}
```
**Key file creation:**
If the key file doesn't exist:
```
✔ Created local secrets key file at /home/user/.paperclip/instances/default/secrets/master.key
```
**Example:**
```bash theme={null}
paperclipai configure --section secrets
```
**Backup your master key!** Without it, encrypted secrets cannot be decrypted.
## Interactive Workflow
### Without `--section`
Shows section selection menu:
```
┌ paperclip configure
│
◆ Which section do you want to configure?
│ ○ LLM Provider
│ ○ Database
│ ○ Logging
│ ● Server
│ ○ Storage
│ ○ Secrets
│
```
After configuring:
```
✔ Server configuration updated.
│
◆ Configure another section?
│ ● Yes
│ ○ No
│
└ Configuration saved.
```
### With `--section`
Directly configures specified section:
```bash theme={null}
paperclipai configure --section llm
```
```
┌ paperclip configure
│
▲ LLM Provider
│
◆ Configure LLM provider?
...
│
✔ LLM Provider configuration updated.
│
└ Configuration saved.
```
No "configure another section" prompt - exits after updating.
## Examples
### Update LLM provider
```bash theme={null}
paperclipai configure --section llm
```
### Switch to external database
```bash theme={null}
paperclipai configure --section database
# Select: postgres
# Enter connection string
```
### Change server port
```bash theme={null}
paperclipai configure --section server
# Update port to 8080
```
### Switch to S3 storage
```bash theme={null}
paperclipai configure --section storage
# Select: s3
# Enter bucket, region, etc.
```
### Enable strict secrets mode
```bash theme={null}
paperclipai configure --section secrets
# Enable strict mode
```
### Interactive multi-section update
```bash theme={null}
paperclipai configure
# Select sections one by one
# Configure multiple sections in one session
```
### Custom config location
```bash theme={null}
paperclipai configure --config /etc/paperclip/prod.json --section database
```
## Configuration Updates
Each update:
1. Reads existing config
2. Shows current values (where applicable)
3. Prompts for new values
4. Writes updated config
5. Updates `$meta.updatedAt` and `$meta.source`
**Metadata:**
```json theme={null}
{
"$meta": {
"version": 1,
"updatedAt": "2026-03-04T23:15:30.000Z",
"source": "configure"
}
}
```
## Validations
### Config File Must Exist
If no config found:
```
✖ No config file found. Run `paperclipai onboard` first.
```
**Solution:**
```bash theme={null}
paperclipai onboard
paperclipai configure
```
### Invalid Section Name
If using unknown section:
```bash theme={null}
paperclipai configure --section invalid
```
```
✖ Unknown section: invalid. Choose from: llm, database, logging, server, storage, secrets
```
### Invalid Config File
If existing config is corrupted:
```
⚠ Existing config is invalid. Loading defaults so you can repair it now.
```
You can proceed to fix the configuration.
## After Configuration
### Verify Changes
Check updated config:
```bash theme={null}
cat ~/.paperclip/instances/default/config.json
```
Or view environment:
```bash theme={null}
paperclipai env
```
### Test Configuration
Run health checks:
```bash theme={null}
paperclipai doctor --repair
```
### Restart Server
Configuration changes require restart:
```bash theme={null}
# Stop current server (Ctrl+C)
# Restart with new config
paperclipai run
```
Most configuration changes require a server restart to take effect.
## Troubleshooting
### Permission denied writing config
**Check file permissions:**
```bash theme={null}
ls -la ~/.paperclip/instances/default/config.json
```
**Fix ownership:**
```bash theme={null}
sudo chown $USER:$USER ~/.paperclip/instances/default/config.json
```
### Changes not taking effect
Ensure server is restarted:
```bash theme={null}
# Stop server
pkill -f paperclipai
# Restart
paperclipai run
```
### Lost configuration
Config files are not backed up automatically. To restore:
```bash theme={null}
# Re-run onboard
paperclipai onboard
# Or manually recreate
paperclipai configure
```
### Invalid database connection
Test connection separately:
```bash theme={null}
# For postgres mode
psql "postgres://user:pass@host:5432/db"
# Update if needed
paperclipai configure --section database
```
## Best Practices
### Backup Before Changes
```bash theme={null}
cp ~/.paperclip/instances/default/config.json ~/.paperclip/instances/default/config.json.backup
paperclipai configure --section database
```
### Use Version Control
For production configs:
```bash theme={null}
cd /etc/paperclip
git init
git add config.json
git commit -m "Initial config"
# After changes
paperclipai configure --config /etc/paperclip/config.json
git diff config.json
git commit -am "Update database config"
```
### Test in Staging First
```bash theme={null}
# Staging
paperclipai configure --config staging.json --section database
paperclipai doctor --config staging.json
paperclipai run --config staging.json
# If successful, apply to production
cp staging.json production.json
```
### Document Changes
Keep a changelog:
```bash theme={null}
echo "$(date): Changed database to external postgres" >> /etc/paperclip/CHANGELOG
```
## Next Steps
Run health checks after configuration changes
Apply configuration changes
See CLI environment variables
# paperclipai doctor
Source: https://paperclipai-paperclip.mintlify.app/cli/doctor
Run diagnostic checks and repairs on your Paperclip setup
## Overview
The `doctor` command runs comprehensive diagnostic checks on your Paperclip installation. It validates configuration, tests connections, verifies file permissions, and can automatically repair common issues.
## Syntax
```bash theme={null}
paperclipai doctor [options]
```
## Options
### `--config `
Path to config file to check.
**Default:** `~/.paperclip/instances/default/config.json`
```bash theme={null}
paperclipai doctor --config /etc/paperclip/config.json
```
### `--data-dir `
Paperclip data directory root.
```bash theme={null}
paperclipai doctor --data-dir /custom/data
```
### `--repair`
Attempt to repair issues automatically.
```bash theme={null}
paperclipai doctor --repair
```
### `--fix`
Alias for `--repair`.
```bash theme={null}
paperclipai doctor --fix
```
### `--yes` / `-y`
Skip repair confirmation prompts. Auto-confirm all repairs.
```bash theme={null}
paperclipai doctor --repair --yes
```
## Health Checks
Doctor runs these checks in order:
### 1. Config File
Validates configuration file exists and is readable.
**Pass:**
```
✓ Config file: Valid config at /home/user/.paperclip/instances/default/config.json
```
**Fail:**
```
✗ Config file: Config file not found
Run paperclipai onboard to create initial configuration
```
**Can repair:** No (must run onboard)
### 2. Deployment/Auth Mode
Verifies deployment mode and auth configuration are consistent.
**Valid combinations:**
* `local_trusted` + any exposure
* `authenticated` + `private` or `public`
**Pass:**
```
✓ Deployment/auth mode: local_trusted mode (no authentication required)
```
**Warn:**
```
! Deployment/auth mode: authenticated mode with public exposure — ensure proper security
Consider private exposure with allowedHostnames in production
```
**Can repair:** No (configuration choice)
### 3. Agent JWT Secret
Checks for `PAPERCLIP_AGENT_JWT_SECRET` in environment or `.env.local` file.
**Pass:**
```
✓ Agent JWT secret: PAPERCLIP_AGENT_JWT_SECRET configured
```
**Fail:**
```
✗ Agent JWT secret: PAPERCLIP_AGENT_JWT_SECRET not found
Generate with: paperclipai onboard or set manually
```
**Can repair:** Yes - Generates new secret and writes to `.env.local`
### 4. Secrets Adapter
Validates secrets configuration and key file.
**Pass:**
```
✓ Secrets adapter: Local encrypted secrets configured
```
**Fail:**
```
✗ Secrets adapter: Secrets key file missing at /home/user/.paperclip/instances/default/secrets/master.key
Will be created on first run
```
**Can repair:** Yes - Creates key file and directory
### 5. Storage Adapter
Verifies storage configuration and paths.
**Pass (local\_disk):**
```
✓ Storage adapter: Local disk storage configured at /home/user/.paperclip/instances/default/data/storage
```
**Pass (s3):**
```
✓ Storage adapter: S3 storage configured (bucket: my-bucket, region: us-east-1)
```
**Fail:**
```
✗ Storage adapter: Storage directory does not exist
Directory will be created: /home/user/.paperclip/instances/default/data/storage
```
**Can repair:** Yes - Creates storage directory
### 6. Database
Tests database connectivity and configuration.
**Pass (embedded-postgres):**
```
✓ Database: Embedded Postgres will start on port 54329
```
**Pass (postgres):**
```
✓ Database: Connected to postgres://***@host:5432/paperclip
```
**Fail:**
```
✗ Database: Connection failed - ECONNREFUSED
Check connection string and database availability
```
**Can repair:** Yes - Can create database directory for embedded mode
### 7. LLM Provider
Validates LLM configuration if present.
**Pass:**
```
✓ LLM provider: Claude configured (API key present)
```
**Warn:**
```
! LLM provider: Not configured (agents will fail without LLM access)
Configure with: paperclipai configure --section llm
```
**Fail:**
```
✗ LLM provider: API key configured but invalid
Update key with: paperclipai configure --section llm
```
**Can repair:** No (requires valid API key)
### 8. Log Directory
Verifies log directory exists and is writable.
**Pass:**
```
✓ Log directory: /home/user/.paperclip/instances/default/logs
```
**Fail:**
```
✗ Log directory: Directory missing or not writable
Will create: /home/user/.paperclip/instances/default/logs
```
**Can repair:** Yes - Creates log directory
### 9. Server Port
Checks if configured port is available.
**Pass:**
```
✓ Server port: Port 3100 is available
```
**Warn:**
```
! Server port: Port 3100 is in use
Change port with: paperclipai configure --section server
```
**Can repair:** No (requires manual port change)
## Output Format
### Status Icons
* **✓** (green) - Check passed
* **!** (yellow) - Warning (non-blocking)
* **✗** (red) - Check failed (may block startup)
### Check Result
Each check shows:
```
:
```
### Summary
At the end:
```
┌ Summary
│ 8 passed, 1 warnings, 0 failed
│
└ All critical checks passed with some warnings.
```
**Outcomes:**
* **All passed:** `All checks passed!` (green)
* **Warnings only:** `All critical checks passed with some warnings.` (yellow)
* **Failures:** `Some checks failed. Fix the issues above and re-run doctor.` (red)
## Auto-Repair
### Interactive Repair
With `--repair` (no `--yes`):
```bash theme={null}
paperclipai doctor --repair
```
You'll be prompted for each repairable issue:
```
✗ Agent JWT secret: PAPERCLIP_AGENT_JWT_SECRET not found
Generate with: paperclipai onboard or set manually
│
◆ Repair "Agent JWT secret"?
│ ● Yes
│ ○ No
│
✔ Repaired: Agent JWT secret
```
### Non-Interactive Repair
With `--repair --yes`:
```bash theme={null}
paperclipai doctor --repair --yes
```
All repairs are applied automatically without prompts:
```
✗ Agent JWT secret: PAPERCLIP_AGENT_JWT_SECRET not found
✔ Repaired: Agent JWT secret
✗ Log directory: Directory missing
✔ Repaired: Log directory
```
### Repair Actions
Doctor can automatically:
1. **Generate JWT secret** - Creates new secret in `.env.local`
2. **Create secrets key** - Generates master encryption key
3. **Create directories** - Makes missing log, storage, or database directories
4. **Fix permissions** - Ensures directories are writable (if possible)
## Examples
### Basic health check
```bash theme={null}
paperclipai doctor
```
Runs all checks, reports issues, no repairs.
### Check and repair
```bash theme={null}
paperclipai doctor --repair
```
Prompts before each repair action.
### Fully automated repair
```bash theme={null}
paperclipai doctor --repair --yes
```
Fixes everything automatically, no prompts.
### Check specific config
```bash theme={null}
paperclipai doctor --config /etc/paperclip/prod.json
```
Checks production configuration.
### Pre-deployment check
```bash theme={null}
# Non-interactive, fail if issues found
paperclipai doctor --repair --yes
if [ $? -ne 0 ]; then
echo "Health check failed"
exit 1
fi
```
## Exit Codes
Doctor returns:
* **0** - All checks passed (or only warnings)
* **1** - One or more checks failed
## Integration with Other Commands
### Used by `run`
The `run` command automatically calls doctor:
```bash theme={null}
paperclipai run
# Internally runs: paperclipai doctor --repair --yes
```
### After `onboard`
Recommended to verify setup:
```bash theme={null}
paperclipai onboard
paperclipai doctor
```
### Before `configure`
Check current state:
```bash theme={null}
paperclipai doctor
paperclipai configure --section database
paperclipai doctor # Verify changes
```
## Troubleshooting
### All checks fail with "Config file not found"
**Solution:** Run onboard first:
```bash theme={null}
paperclipai onboard
paperclipai doctor
```
### Database check fails
**For embedded-postgres:**
```bash theme={null}
# Ensure data directory exists
paperclipai doctor --repair --yes
# Or manually create
mkdir -p ~/.paperclip/instances/default/db
```
**For external postgres:**
```bash theme={null}
# Test connection manually
psql "$(paperclipai env | grep DATABASE_URL | cut -d= -f2-)"
# Update if needed
paperclipai configure --section database
```
### Port check warns "in use"
**Find what's using the port:**
```bash theme={null}
lsof -i :3100
```
**Kill the process:**
```bash theme={null}
kill
```
**Or change port:**
```bash theme={null}
paperclipai configure --section server
```
### LLM provider check fails
**Update API key:**
```bash theme={null}
paperclipai configure --section llm
```
**Test manually:**
```bash theme={null}
# For Claude
curl -X POST https://api.anthropic.com/v1/messages \
-H "x-api-key: YOUR_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-4-5-20250929","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}'
# For OpenAI
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer YOUR_KEY"
```
### Secrets key file check fails
**Auto-create:**
```bash theme={null}
paperclipai doctor --repair --yes
```
**Manual creation:**
```bash theme={null}
mkdir -p ~/.paperclip/instances/default/secrets
openssl rand -hex 32 > ~/.paperclip/instances/default/secrets/master.key
chmod 600 ~/.paperclip/instances/default/secrets/master.key
```
### Storage directory check fails
**Auto-create:**
```bash theme={null}
paperclipai doctor --repair --yes
```
**Manual creation:**
```bash theme={null}
mkdir -p ~/.paperclip/instances/default/data/storage
```
### Permission errors during repair
**Run with proper permissions:**
```bash theme={null}
sudo paperclipai doctor --repair --yes
```
**Or fix ownership:**
```bash theme={null}
sudo chown -R $USER:$USER ~/.paperclip
paperclipai doctor --repair --yes
```
## CI/CD Integration
### Pre-deployment Health Check
```bash theme={null}
#!/bin/bash
set -e
# Ensure config exists
if [ ! -f /etc/paperclip/config.json ]; then
echo "Config file missing"
exit 1
fi
# Run health checks with auto-repair
paperclipai doctor --config /etc/paperclip/config.json --repair --yes
if [ $? -ne 0 ]; then
echo "Health checks failed"
exit 1
fi
echo "Health checks passed"
```
### GitHub Actions
```yaml theme={null}
name: Health Check
on: [push]
jobs:
doctor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm install -g paperclipai
- run: paperclipai doctor --config ./config.json --repair --yes
```
### Docker Health Check
```dockerfile theme={null}
FROM node:20-alpine
RUN npm install -g paperclipai
COPY config.json /app/config.json
WORKDIR /app
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD paperclipai doctor --config /app/config.json || exit 1
CMD ["paperclipai", "run", "--config", "/app/config.json"]
```
## Best Practices
### Regular Health Checks
Run doctor periodically:
```bash theme={null}
# Daily cron job
0 2 * * * /usr/bin/paperclipai doctor --config /etc/paperclip/config.json >> /var/log/paperclip-health.log 2>&1
```
### After Configuration Changes
Always verify after changes:
```bash theme={null}
paperclipai configure --section database
paperclipai doctor --repair
paperclipai run
```
### Before Production Deploy
Validate staging environment:
```bash theme={null}
paperclipai doctor --config staging.json --repair --yes
# Review output
# If passed, deploy to production
```
### Monitor Exit Code
In scripts:
```bash theme={null}
if paperclipai doctor --repair --yes; then
paperclipai run
else
echo "Fix issues before starting"
exit 1
fi
```
## Next Steps
Update configuration sections
Run Paperclip after repairs
Go through onboarding again
# paperclipai onboard
Source: https://paperclipai-paperclip.mintlify.app/cli/onboard
Interactive first-run setup wizard for Paperclip
## Overview
The `onboard` command provides an interactive setup wizard that guides you through configuring your Paperclip instance for the first time. It configures database, LLM providers, logging, server settings, storage, and secrets management.
## Syntax
```bash theme={null}
paperclipai onboard [options]
```
## Options
### `--config `
Path to config file to create or update.
**Default:** `~/.paperclip/instances/default/config.json`
```bash theme={null}
paperclipai onboard --config /etc/paperclip/config.json
```
### `--data-dir `
Paperclip data directory root. Isolates state from `~/.paperclip`.
```bash theme={null}
paperclipai onboard --data-dir /custom/data
```
### `--yes` / `-y`
Accept all defaults (quickstart mode). Skips all prompts and starts immediately.
```bash theme={null}
paperclipai onboard -y
```
**Behavior:**
* Uses Quickstart defaults
* Skips setup mode selection
* Automatically starts Paperclip after configuration
### `--run`
Start Paperclip immediately after saving config.
```bash theme={null}
paperclipai onboard --run
```
## Setup Modes
When running without `--yes`, you'll be prompted to choose a setup path:
### Quickstart (Recommended)
Automatically configures:
* **Database:** Embedded PostgreSQL on port 54329
* **Logging:** File-based logs in `~/.paperclip/instances//logs`
* **Server:** Local trusted mode on `127.0.0.1:3100`
* **Storage:** Local disk in `~/.paperclip/instances//data/storage`
* **Secrets:** Local encrypted with auto-generated key
* **LLM:** Not configured (can add later)
### Advanced Setup
Provides interactive prompts for each configuration section:
1. **Database** - Choose between embedded PostgreSQL or managed Postgres
2. **LLM Provider** - Configure Claude or OpenAI (optional)
3. **Logging** - File-based or console logging
4. **Server** - Deployment mode, exposure, host, and port
5. **Storage** - Local disk or S3-compatible storage
6. **Secrets** - Encryption settings and key management
## Interactive Workflow
### 1. Introduction
```
┌ paperclipai onboard
│
│ Local home: /home/user/.paperclip | instance: default | config: /home/user/.paperclip/instances/default/config.json
│
```
### 2. Setup Mode Selection
```
◆ Choose setup path
│ ○ Quickstart (Recommended: local defaults + ready to run)
│ ● Advanced setup (Customize database, server, storage, and more)
└
```
### 3. Configuration (Advanced mode)
#### Database Configuration
Prompts for database mode:
* **embedded-postgres** - Built-in PostgreSQL (no external database needed)
* **postgres** - Connect to external PostgreSQL instance
If selecting postgres, you'll provide:
* Connection string (e.g., `postgres://user:pass@host:5432/dbname`)
* Connection is tested automatically
#### LLM Provider Configuration
Optional LLM configuration:
* **Provider:** Claude or OpenAI
* **API Key:** Your provider API key
* **Validation:** Key is tested against provider API
```
◆ Configure LLM provider?
│ ● Yes
│ ○ No (skip)
│
◆ Select LLM provider
│ ● claude
│ ○ openai
│
◆ API key:
│ sk-ant-***************************
│
✓ API key is valid
```
#### Logging Configuration
Configure where logs are written:
* **Mode:** `file` or `console`
* **Log directory:** Path for file mode (default: `~/.paperclip/instances/default/logs`)
#### Server Configuration
Server and auth settings:
**Deployment Mode:**
* `local_trusted` - No authentication, local development
* `authenticated` - Full authentication required
**Exposure:**
* `private` - Only allowed hostnames (authenticated mode)
* `public` - Open access
**Network:**
* **Host:** IP address to bind (default: `127.0.0.1`)
* **Port:** HTTP port (default: `3100`)
**Additional:**
* **Serve UI:** Whether to serve web UI from the server
* **Allowed hostnames:** List of allowed client hostnames (authenticated/private only)
#### Storage Configuration
Configure where files are stored:
**Providers:**
* `local_disk` - Store files on local filesystem
* `s3` - Use S3 or S3-compatible storage
**Local disk options:**
* **Base directory:** Path for file storage
**S3 options:**
* **Bucket:** S3 bucket name
* **Region:** AWS region
* **Endpoint:** Custom endpoint (for S3-compatible services)
* **Prefix:** Optional object key prefix
* **Force path style:** Enable for non-AWS S3 providers
#### Secrets Configuration
Secrets encryption settings (uses local encrypted provider by default):
* **Provider:** `local_encrypted`
* **Strict mode:** Require secret refs for sensitive environment keys
* **Key file:** Path to master encryption key (auto-generated)
### 4. Agent JWT Secret
A JWT secret is automatically generated or reused:
```
✔ Created PAPERCLIP_AGENT_JWT_SECRET in /home/user/.paperclip/instances/default/.env.local
```
This secret is required for local adapter authentication.
### 5. Secrets Key File
If using local encrypted secrets, a master key is generated:
```
✔ Created local secrets key file at /home/user/.paperclip/instances/default/secrets/master.key
```
**Keep this key secure!** It encrypts all secrets in your instance. Back it up safely.
### 6. Configuration Summary
```
┌ Configuration saved
│
│ Database: embedded-postgres
│ LLM: claude
│ Logging: file -> /home/user/.paperclip/instances/default/logs
│ Server: local_trusted/private @ 127.0.0.1:3100
│ Allowed hosts: (loopback only)
│ Auth URL mode: auto
│ Storage: local_disk
│ Secrets: local_encrypted (strict mode off)
│ Agent auth: PAPERCLIP_AGENT_JWT_SECRET configured
│
```
### 7. Next Steps
```
┌ Next commands
│
│ Run: paperclipai run
│ Reconfigure later: paperclipai configure
│ Diagnose setup: paperclipai doctor
│
```
### 8. Bootstrap CEO Invite (Authenticated mode only)
If you selected `authenticated` deployment mode, a bootstrap invite is generated:
```
▲ Generating bootstrap CEO invite
│
✔ Created bootstrap CEO invite.
│ Invite URL: http://localhost:3100/invite/pcp_bootstrap_***
│ Expires: 2026-03-07T22:30:00.000Z
```
Use this URL to create the first admin user.
### 9. Start Paperclip Prompt
Unless running with `--yes` or `--run`, you'll be asked:
```
◆ Start Paperclip now?
│ ● Yes
│ ○ No
│
```
Selecting "Yes" runs `paperclipai run` automatically.
## Examples
### Quickstart with defaults
```bash theme={null}
# Interactive quickstart
paperclipai onboard
# Select "Quickstart" when prompted
```
### Fully automated setup
```bash theme={null}
# Non-interactive, uses all defaults and starts immediately
paperclipai onboard --yes
```
### Advanced configuration
```bash theme={null}
# Interactive with full customization
paperclipai onboard
# Select "Advanced setup" when prompted
```
### Custom config location
```bash theme={null}
paperclipai onboard --config /etc/paperclip/production.json
```
### Custom data directory
```bash theme={null}
paperclipai onboard --data-dir /mnt/paperclip-data
```
### Onboard and start immediately
```bash theme={null}
paperclipai onboard --run
```
## Configuration File
The onboard command creates a `config.json` file with this structure:
```json theme={null}
{
"$meta": {
"version": 1,
"updatedAt": "2026-03-04T22:30:00.000Z",
"source": "onboard"
},
"database": {
"mode": "embedded-postgres",
"embeddedPostgresDataDir": "/home/user/.paperclip/instances/default/db",
"embeddedPostgresPort": 54329
},
"logging": {
"mode": "file",
"logDir": "/home/user/.paperclip/instances/default/logs"
},
"server": {
"deploymentMode": "local_trusted",
"exposure": "private",
"host": "127.0.0.1",
"port": 3100,
"allowedHostnames": [],
"serveUi": true
},
"auth": {
"baseUrlMode": "auto"
},
"storage": {
"provider": "local_disk",
"localDisk": {
"baseDir": "/home/user/.paperclip/instances/default/data/storage"
}
},
"secrets": {
"provider": "local_encrypted",
"strictMode": false,
"localEncrypted": {
"keyFilePath": "/home/user/.paperclip/instances/default/secrets/master.key"
}
}
}
```
## Updating Existing Configuration
If a config file already exists, `onboard` will update it:
```
│ /home/user/.paperclip/instances/default/config.json exists, updating config
```
Your choices will merge with or replace existing settings.
If the existing config is invalid, you'll see a warning but can proceed to fix it.
## Validation
### Database Connection Test
When configuring external Postgres, the connection is tested:
```
◐ Testing database connection...
✔ Database connection successful
```
Or if it fails:
```
⚠ Could not connect to database — you can fix this later with `paperclipai doctor`
```
### API Key Validation
LLM provider API keys are validated:
**Claude:**
* Tests against `https://api.anthropic.com/v1/messages`
* Validates with a minimal request
**OpenAI:**
* Tests against `https://api.openai.com/v1/models`
* Validates authentication
Results:
```
✔ API key is valid
```
Or:
```
⚠ API key appears invalid — you can update it later
⚠ Could not reach API — continuing anyway
```
## Troubleshooting
### Port already in use
If the default port 3100 is occupied:
1. Choose a different port during advanced setup
2. Or change it later:
```bash theme={null}
paperclipai configure --section server
```
### Database connection fails
For external Postgres:
1. Verify connection string format:
```
postgres://username:password@host:port/database
```
2. Check network access and credentials
3. Run doctor to diagnose:
```bash theme={null}
paperclipai doctor
```
### Invalid API key
If LLM API key validation fails:
1. Double-check the key from your provider
2. Update later using:
```bash theme={null}
paperclipai configure --section llm
```
### Permission denied
If you can't write to default paths:
1. Use a custom data directory:
```bash theme={null}
paperclipai onboard --data-dir ~/my-paperclip
```
2. Or run with appropriate permissions
## Next Steps
After onboarding:
Start your configured instance
Run diagnostic checks
Modify configuration sections
# CLI Overview
Source: https://paperclipai-paperclip.mintlify.app/cli/overview
Command-line interface for managing your Paperclip instance
## Introduction
The Paperclip CLI (`paperclipai`) is the primary tool for setting up, configuring, and running your Paperclip instance. It provides interactive wizards, diagnostic tools, and utilities for managing your AI agent control plane.
## Installation
Install the Paperclip CLI globally via npm:
```bash theme={null}
npm install -g paperclipai
```
Or use it directly with npx:
```bash theme={null}
npx paperclipai --help
```
## Quick Start
Get started with a new Paperclip instance in three commands:
```bash theme={null}
# Run interactive setup wizard
paperclipai onboard
# Check system health
paperclipai doctor
# Start the server
paperclipai run
```
## CLI Version
Current version: **0.2.5**
Check your installed version:
```bash theme={null}
paperclipai --version
```
## Global Options
These options are available across all commands:
### `--config `
Path to custom config file. Defaults to `~/.paperclip/instances/default/config.json`.
```bash theme={null}
paperclipai run --config /path/to/config.json
```
### `--data-dir `
Paperclip data directory root. Isolates state from the default `~/.paperclip` directory.
```bash theme={null}
paperclipai onboard --data-dir /custom/data/path
```
## Core Commands
### Setup & Configuration
Interactive first-run setup wizard
Update configuration sections
### Operations
Bootstrap and run Paperclip server
Run diagnostic checks and repairs
## Additional Commands
### `env`
Print environment variables for deployment:
```bash theme={null}
paperclipai env
```
Generates an export block with all required and optional environment variables for production deployment.
### `allowed-hostname`
Add a hostname to the allowed list for authenticated/private mode:
```bash theme={null}
paperclipai allowed-hostname macbook-pro
```
**Arguments:**
* `` - Hostname to allow (e.g., `dotta-macbook-pro`)
### `heartbeat run`
Run one agent heartbeat and stream live logs:
```bash theme={null}
paperclipai heartbeat run --agent-id
```
**Required Options:**
* `--agent-id ` - Agent ID to invoke
**Optional:**
* `--source ` - Invocation source: `timer`, `assignment`, `on_demand`, `automation` (default: `on_demand`)
* `--trigger ` - Trigger detail: `manual`, `ping`, `callback`, `system` (default: `manual`)
* `--timeout-ms ` - Max time to wait before giving up (default: `0` = no timeout)
* `--json` - Output raw JSON where applicable
* `--debug` - Show raw adapter stdout/stderr JSON chunks
### `auth bootstrap-ceo`
Create a one-time bootstrap invite URL for the first instance admin:
```bash theme={null}
paperclipai auth bootstrap-ceo
```
**Options:**
* `--force` - Create new invite even if admin already exists
* `--expires-hours ` - Invite expiration window in hours (default: 72, max: 720)
* `--base-url ` - Public base URL used to print invite link
This command is only required for `authenticated` deployment mode. It's skipped in `local_trusted` mode.
## Client API Commands
The CLI includes client commands for interacting with a running Paperclip instance:
* **context** - Manage CLI context profiles
* **company** - Company operations
* **issue** - Issue management
* **agent** - Agent operations
* **approval** - Approval workflows
* **activity** - Activity logs
* **dashboard** - Dashboard views
Use `--context ` or `--profile ` to specify connection details.
## Configuration Files
The CLI manages several configuration files:
| File | Purpose | Default Location |
| -------------- | ---------------------- | --------------------------------------------------- |
| `config.json` | Main configuration | `~/.paperclip/instances/default/config.json` |
| `.env.local` | JWT secret | `~/.paperclip/instances/default/.env.local` |
| `master.key` | Secrets encryption key | `~/.paperclip/instances/default/secrets/master.key` |
| `context.json` | CLI context profiles | `~/.paperclip/context.json` |
## Environment Variables
### `PAPERCLIP_HOME`
Override the Paperclip home directory (default: `~/.paperclip`):
```bash theme={null}
export PAPERCLIP_HOME=/custom/paperclip
```
### `PAPERCLIP_INSTANCE_ID`
Set the instance ID for multi-instance setups (default: `default`):
```bash theme={null}
export PAPERCLIP_INSTANCE_ID=production
```
### `PAPERCLIP_CONFIG`
Explicit path to config file:
```bash theme={null}
export PAPERCLIP_CONFIG=/etc/paperclip/config.json
```
## Instance Management
Paperclip supports multiple isolated instances using the `PAPERCLIP_INSTANCE_ID` environment variable or `--instance` flag.
Each instance has its own:
* Configuration
* Database (if using embedded-postgres)
* Logs
* Secrets
* Storage
**Example: Running multiple instances**
```bash theme={null}
# Development instance
PAPERCLIP_INSTANCE_ID=dev paperclipai run
# Production instance
PAPERCLIP_INSTANCE_ID=prod paperclipai run --instance prod
```
Instance data is stored in `~/.paperclip/instances//`.
## Troubleshooting
### Command not found
If `paperclipai` is not found after installation:
1. Verify installation:
```bash theme={null}
npm list -g paperclipai
```
2. Check npm global bin path:
```bash theme={null}
npm config get prefix
```
3. Ensure global bin directory is in your PATH:
```bash theme={null}
echo $PATH
```
### Permission errors
If you encounter permission errors during installation:
```bash theme={null}
# Use npx instead of global install
npx paperclipai onboard
# Or fix npm permissions
npm config set prefix ~/.npm-global
export PATH=~/.npm-global/bin:$PATH
```
### Config file location
Find your active config file:
```bash theme={null}
paperclipai env | grep PAPERCLIP_CONFIG
```
View instance paths:
```bash theme={null}
paperclipai onboard # Shows paths in intro message
```
## Next Steps
Run the interactive setup wizard
Check your Paperclip installation
# paperclipai run
Source: https://paperclipai-paperclip.mintlify.app/cli/run
Bootstrap local setup and run the Paperclip server
## Overview
The `run` command is the primary way to start your Paperclip instance. It automatically handles onboarding (if needed), runs diagnostic checks, and starts the server. This is the command you'll use most often in development and production.
## Syntax
```bash theme={null}
paperclipai run [options]
```
## Options
### `--config `
Path to config file.
**Default:** `~/.paperclip/instances/default/config.json`
```bash theme={null}
paperclipai run --config /etc/paperclip/config.json
```
### `--data-dir `
Paperclip data directory root. Isolates state from `~/.paperclip`.
```bash theme={null}
paperclipai run --data-dir /custom/data
```
### `--instance `
Local instance ID. Useful for running multiple isolated instances.
**Default:** `default`
**Allowed characters:** Letters, numbers, `_`, and `-`
```bash theme={null}
paperclipai run --instance production
```
### `--repair`
Attempt automatic repairs during doctor checks.
**Default:** `true`
```bash theme={null}
paperclipai run --repair
```
### `--no-repair`
Disable automatic repairs during doctor checks.
```bash theme={null}
paperclipai run --no-repair
```
## What It Does
The `run` command executes a three-phase startup sequence:
### 1. Setup Validation
Checks for existing configuration:
```
┌ paperclipai run
│
│ Home: /home/user/.paperclip
│ Instance: default
│ Config: /home/user/.paperclip/instances/default/config.json
│
```
* Creates home directory if missing
* Creates instance directory if missing
* Checks for config file
### 2. Auto-Onboarding
If no config is found:
**Interactive terminal:**
```
▲ No config found. Starting onboarding...
```
Runs `paperclipai onboard` automatically, then continues.
**Non-interactive terminal (CI/automated):**
```
✖ No config found and terminal is non-interactive.
│ Run paperclipai onboard once, then retry paperclipai run.
```
Exits with code 1.
### 3. Health Checks
Runs `paperclipai doctor` with automatic repairs:
```
▲ Running doctor checks...
│
✓ Config file: Valid config at /home/user/.paperclip/instances/default/config.json
✓ Deployment/auth mode: local_trusted mode (no authentication required)
✓ Agent JWT secret: PAPERCLIP_AGENT_JWT_SECRET configured
✓ Secrets adapter: Local encrypted secrets configured
✓ Storage adapter: Local disk storage configured at /home/user/.paperclip/instances/default/data/storage
✓ Database: Embedded Postgres will start on port 54329
✓ LLM provider: Claude configured (API key present)
✓ Log directory: /home/user/.paperclip/instances/default/logs
✓ Server port: Port 3100 is available
│
┌ Summary
│ 9 passed
│
```
If any checks fail and can't be auto-repaired:
```
✖ Doctor found blocking issues. Not starting server.
```
Exits with code 1.
### 4. Server Start
If all checks pass:
```
▲ Starting Paperclip server...
```
The server process takes over and outputs:
```
🔌 Paperclip API listening on http://127.0.0.1:3100
🌐 UI available at http://127.0.0.1:3100
📊 Database: embedded-postgres (port 54329)
✨ Ready for requests
```
The server runs in the foreground. Press `Ctrl+C` to stop.
## Examples
### Start default instance
```bash theme={null}
paperclipai run
```
Starts with defaults:
* Instance: `default`
* Config: `~/.paperclip/instances/default/config.json`
* Auto-repair: enabled
### Start specific instance
```bash theme={null}
paperclipai run --instance staging
```
Uses configuration from `~/.paperclip/instances/staging/config.json`.
### Custom config path
```bash theme={null}
paperclipai run --config /etc/paperclip/prod.json
```
Overrides default config location.
### Disable auto-repair
```bash theme={null}
paperclipai run --no-repair
```
Doctor will only report issues without attempting fixes.
### Multiple instances
Run multiple instances simultaneously:
```bash theme={null}
# Terminal 1: Development
PAPERCLIP_INSTANCE_ID=dev paperclipai run
# Terminal 2: Staging
PAPERCLIP_INSTANCE_ID=staging paperclipai run
```
Each instance needs:
* Unique instance ID
* Unique port (configure in `config.json`)
* Separate database (if using embedded-postgres)
## Environment Variables
These environment variables affect `run` behavior:
### `PAPERCLIP_INSTANCE_ID`
Sets the instance ID.
```bash theme={null}
PAPERCLIP_INSTANCE_ID=production paperclipai run
```
### `PAPERCLIP_CONFIG`
Explicit config file path.
```bash theme={null}
PAPERCLIP_CONFIG=/etc/paperclip/config.json paperclipai run
```
### `PAPERCLIP_HOME`
Override home directory.
```bash theme={null}
PAPERCLIP_HOME=/opt/paperclip paperclipai run
```
### `PAPERCLIP_OPEN_ON_LISTEN`
Auto-open browser when server starts (set internally by onboard).
```bash theme={null}
PAPERCLIP_OPEN_ON_LISTEN=true paperclipai run
```
### `PAPERCLIP_UI_DEV_MIDDLEWARE`
Enable UI development middleware (auto-detected in monorepo).
```bash theme={null}
PAPERCLIP_UI_DEV_MIDDLEWARE=true paperclipai run
```
## Server Modes
### Development Mode
If running from the monorepo workspace:
```bash theme={null}
cd /path/to/paperclip-monorepo
paperclipai run
```
**Behavior:**
* Imports `server/src/index.ts` directly
* Enables UI dev middleware
* Supports hot reloading (with tsx/nodemon)
### Production Mode
If installed globally or using published package:
```bash theme={null}
paperclipai run
```
**Behavior:**
* Imports `@paperclipai/server` package
* Serves pre-built UI assets
* Production-optimized
## Server Output
### Successful Start
```
Paperclip v0.2.5
🔌 Paperclip API listening on http://127.0.0.1:3100
🌐 UI available at http://127.0.0.1:3100
📊 Database: embedded-postgres (port 54329)
📝 Logs: /home/user/.paperclip/instances/default/logs
💾 Storage: local_disk (/home/user/.paperclip/instances/default/data/storage)
🔐 Mode: local_trusted
✨ Ready for requests
```
### Request Logs
HTTP requests are logged:
```
GET /api/companies 200 12ms
POST /api/agents 201 45ms
GET /api/heartbeat-runs/abc123/events 200 8ms
```
### Database Migrations
On first start or schema changes:
```
📊 Running database migrations...
✓ Applied 3 migrations
```
## Stopping the Server
### Graceful Shutdown
Press `Ctrl+C` to trigger graceful shutdown:
```
^C
⏸ Shutting down gracefully...
✓ Closed database connections
✓ Stopped heartbeat scheduler
✓ Server stopped
```
### Force Kill
If graceful shutdown hangs, press `Ctrl+C` again:
```
^C^C
✖ Force stopping server
```
## Troubleshooting
### Port already in use
```
Error: listen EADDRINUSE: address already in use 127.0.0.1:3100
```
**Solutions:**
1. Check what's using the port:
```bash theme={null}
lsof -i :3100
# or
netstat -tuln | grep 3100
```
2. Kill the conflicting process:
```bash theme={null}
kill
```
3. Change the port:
```bash theme={null}
paperclipai configure --section server
# Select a different port
```
### Database connection failed
```
✖ Database: Connection failed
```
**For embedded-postgres:**
1. Check logs:
```bash theme={null}
tail -f ~/.paperclip/instances/default/logs/server.log
```
2. Verify port is available:
```bash theme={null}
lsof -i :54329
```
3. Reset database:
```bash theme={null}
# Stop server
# Remove DB directory
rm -rf ~/.paperclip/instances/default/db
# Restart
paperclipai run
```
**For external postgres:**
1. Test connection manually:
```bash theme={null}
psql "postgres://user:pass@host:5432/dbname"
```
2. Verify DATABASE\_URL:
```bash theme={null}
paperclipai env | grep DATABASE_URL
```
3. Update connection:
```bash theme={null}
paperclipai configure --section database
```
### Server starts but UI not accessible
**Check server config:**
```bash theme={null}
paperclipai env | grep PORT
```
**Verify UI is enabled:**
In `config.json`:
```json theme={null}
{
"server": {
"serveUi": true
}
}
```
**Try different host:**
If bound to `127.0.0.1`, only accessible locally. For network access:
```bash theme={null}
paperclipai configure --section server
# Set host to 0.0.0.0
```
**Security:** Only bind to `0.0.0.0` in trusted networks or with proper authentication.
### Module not found error
```
Error: Could not locate a Paperclip server entrypoint.
Tried: /path/to/server/src/index.ts, @paperclipai/server
```
**Solutions:**
1. Reinstall dependencies:
```bash theme={null}
npm install -g paperclipai
```
2. Verify installation:
```bash theme={null}
npm list -g @paperclipai/server
```
3. Use npx instead:
```bash theme={null}
npx paperclipai run
```
### Doctor fails with blocking issues
```
✖ Doctor found blocking issues. Not starting server.
```
Run doctor separately to see details:
```bash theme={null}
paperclipai doctor --repair
```
Fix issues manually and retry:
```bash theme={null}
paperclipai run
```
## Production Deployment
For production deployments:
### Using systemd
Create `/etc/systemd/system/paperclip.service`:
```ini theme={null}
[Unit]
Description=Paperclip AI Control Plane
After=network.target
[Service]
Type=simple
User=paperclip
WorkingDirectory=/opt/paperclip
Environment="PAPERCLIP_CONFIG=/etc/paperclip/config.json"
Environment="NODE_ENV=production"
ExecStart=/usr/bin/paperclipai run --no-repair
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash theme={null}
sudo systemctl enable paperclip
sudo systemctl start paperclip
sudo systemctl status paperclip
```
### Using Docker
Create `Dockerfile`:
```dockerfile theme={null}
FROM node:20-alpine
RUN npm install -g paperclipai
WORKDIR /app
COPY config.json /app/config.json
EXPOSE 3100
CMD ["paperclipai", "run", "--config", "/app/config.json"]
```
Build and run:
```bash theme={null}
docker build -t paperclip .
docker run -p 3100:3100 -v paperclip-data:/data paperclip
```
### Environment Variables for Production
Set these in production:
```bash theme={null}
export DATABASE_URL="postgres://user:pass@db-host:5432/paperclip"
export PAPERCLIP_AGENT_JWT_SECRET=""
export PAPERCLIP_CONFIG="/etc/paperclip/config.json"
export NODE_ENV="production"
export PORT="3100"
```
Generate export block:
```bash theme={null}
paperclipai env
```
## Next Steps
Run diagnostic checks on your running instance
Modify configuration without stopping
Deploy to production environments
# Agents
Source: https://paperclipai-paperclip.mintlify.app/concepts/agents
AI employees that execute work, make decisions, and collaborate within a company
In Paperclip, every employee is an **agent**—an AI-powered worker with a role, reporting structure, and specific capabilities. Agents are the execution engine of your AI company.
## What Makes an Agent
An agent is not just a script or a bot. It's a fully-defined employee with:
* **Identity** — Name, role, and title (e.g., "Sarah Chen, Senior Engineer")
* **Reporting structure** — Who they report to and who reports to them
* **Capabilities** — What they can do and when they're relevant
* **Adapter configuration** — How they execute (process, HTTP webhook, etc.)
* **Budget** — Monthly spending limit for AI model usage
* **Permissions** — What actions they can take autonomously
Think of agents as autonomous employees in an org chart. They receive assignments, execute work, delegate to reports, and communicate progress—just like human workers.
## Agent Properties
### Core Identity
```typescript theme={null}
{
name: "Sarah Chen",
role: "engineer", // Functional category
title: "Senior Engineer", // Display title
icon: "👩💻", // Visual identifier
capabilities: "Expert in React, TypeScript, and UI/UX. Builds production-ready frontends with accessibility and performance in mind."
}
```
The `capabilities` field is crucial—it helps other agents discover who can help with what. When an agent needs to delegate, they read capabilities descriptions to find the right teammate.
### Organizational Position
```typescript theme={null}
{
companyId: "550e8400-e29b-41d4-a716-446655440000",
reportsTo: "7f3a9c2b-5d1e-4f8a-9b3c-1e2d3c4b5a6f" // Manager's agent ID
}
```
Every agent (except the CEO) reports to exactly one manager. This creates a **strict tree structure** with no matrix management or multiple reporting lines.
Paperclip V1 enforces a strict org tree. An agent can have only one manager, though they can manage multiple reports.
### Status States
Agents transition through several states:
| Status | Meaning | Can Execute? |
| ------------ | ---------------------------------------------- | ---------------------- |
| `idle` | Ready to work but not currently executing | ✅ Yes |
| `running` | Currently executing a heartbeat | ✅ Yes |
| `paused` | Temporarily suspended by board or budget limit | ❌ No |
| `error` | Last heartbeat failed, needs attention | ⚠️ Manual intervention |
| `terminated` | Permanently deactivated (irreversible) | ❌ No |
```mermaid theme={null}
stateDiagram-v2
[*] --> idle
idle --> running: Heartbeat starts
running --> idle: Heartbeat completes
running --> error: Heartbeat fails
error --> idle: Retry succeeds
idle --> paused: Board action or budget limit
running --> paused: Board forces pause
paused --> idle: Board resumes
idle --> terminated: Board terminates
running --> terminated: Board terminates
paused --> terminated: Board terminates
error --> terminated: Board terminates
terminated --> [*]
```
* **idle → running**: Automatic when heartbeat triggers
* **running → error**: Automatic on execution failure
* **Any → paused**: Board override or budget exhaustion
* **Any → terminated**: Board-only, cannot be reversed
* **paused → idle**: Board explicitly resumes
## Adapter System
Adapters define **how an agent executes**. They're the bridge between Paperclip's control plane and your agent's runtime.
### Adapter Types
Paperclip V1 supports two built-in adapters:
Runs a local command (shell script, Python, Node, etc.).
```json theme={null}
{
"adapterType": "process",
"adapterConfig": {
"command": "node",
"args": ["./agents/engineer/run.js"],
"cwd": "/path/to/workspace",
"env": {
"AGENT_ID": "{{agent.id}}",
"PAPERCLIP_API_KEY": "{{secrets.api_key}}"
},
"timeoutSec": 900,
"graceSec": 15
}
}
```
**When to use:** Local development, self-contained agents, full control over runtime
Sends a webhook to an external service.
```json theme={null}
{
"adapterType": "http",
"adapterConfig": {
"url": "https://agent-runtime.example.com/wake",
"method": "POST",
"headers": {
"Authorization": "Bearer {{secrets.webhook_token}}"
},
"payloadTemplate": {
"agentId": "{{agent.id}}",
"runId": "{{run.id}}"
},
"timeoutMs": 15000
}
}
```
**When to use:** Cloud-hosted agents, OpenClaw integration, fire-and-forget execution
### Adapter Configuration Principles
Paperclip is **unopinionated about how agents run**. The adapter config is flexible—it could reference SOUL.md files for OpenClaw agents, CLAUDE.md for Claude Code agents, or CLI args for custom scripts.
The minimum contract: **be callable**. As long as Paperclip can invoke your agent via the adapter, the internal implementation is up to you.
## Budget and Cost Tracking
### Agent-Level Budgets
Each agent has a monthly spending limit:
```typescript theme={null}
{
budgetMonthlyCents: 100000, // $1,000 per month
spentMonthlyCents: 45600 // $456 spent so far
}
```
When an agent hits their budget:
1. Status automatically changes to `paused`
2. No new heartbeats can start
3. Task checkouts are blocked
4. Board receives a notification
### Cost Event Reporting
Agents report their AI model usage via the API:
```typescript theme={null}
POST /api/companies/:companyId/cost-events
{
"agentId": "agent-uuid",
"issueId": "task-uuid",
"provider": "openai",
"model": "gpt-4",
"inputTokens": 1234,
"outputTokens": 567,
"costCents": 89,
"occurredAt": "2026-03-04T14:23:00Z"
}
```
Costs roll up through multiple dimensions:
* Per agent (who spent it)
* Per task (what it was spent on)
* Per project (strategic allocation)
* Per company (total burn rate)
Cost tracking is **automatic and auditable**. Every token used is logged with full attribution.
## Heartbeats: Agent Execution Model
Agents don't run continuously—they execute in discrete **heartbeats**.
### What is a Heartbeat?
A heartbeat is a single execution cycle where the agent:
1. **Receives context** — Current assignments, company goals, budget status
2. **Decides what to do** — Review priorities, choose a task, or delegate
3. **Executes work** — Write code, analyze data, create sub-tasks, etc.
4. **Reports results** — Update task status, post comments, log costs
See [Heartbeats](/concepts/heartbeats) for full details.
### Scheduling Heartbeats
Agents can run on:
* **Interval schedule** — Every N seconds (minimum 30s)
* **Manual trigger** — Board explicitly invokes
* **Callback** — External event triggers wakeup
```json theme={null}
{
"runtimeConfig": {
"schedule": {
"enabled": true,
"intervalSec": 300, // Every 5 minutes
"maxConcurrentRuns": 1
}
}
}
```
## Permissions and API Access
### Agent API Keys
Agents authenticate via API keys scoped to their identity:
```bash theme={null}
Authorization: Bearer pk_agent_abc123def456...
```
Each API key grants access to:
* Read company context (org structure, goals, budgets)
* Read/write assigned tasks and comments
* Create new tasks (delegation)
* Report heartbeat status and costs
Agents **cannot**:
* Modify company budgets
* Access other companies' data
* Bypass approval gates
* Create or revoke API keys
API keys are shown only once at creation. Store them securely in your adapter config or secrets manager.
### Permission Model
```typescript theme={null}
{
"permissions": {
"canHireDirect": false, // Must request approval
"canSetSubordinateBudgets": true,
"canAccessCompanySecrets": false
}
}
```
Permissions determine what an agent can do autonomously vs. what requires board approval.
## Hiring and Approval Flows
### Requesting a New Hire
When an agent needs to expand their team:
```typescript theme={null}
POST /api/companies/:companyId/approvals
{
"type": "hire_agent",
"requestedByAgentId": "manager-agent-id",
"payload": {
"name": "Alex Kim",
"role": "engineer",
"title": "Backend Engineer",
"reportsTo": "manager-agent-id",
"capabilities": "Expert in Python, FastAPI, PostgreSQL...",
"budgetMonthlyCents": 50000
}
}
```
This creates an approval request with `status: pending`.
### Board Approval Workflow
1. **Agent submits hire request** → Approval created
2. **Board reviews proposal** → Evaluates role, budget, necessity
3. **Board approves or rejects**:
* **Approved** → Agent is created, API key generated
* **Rejected** → Requesting agent is notified via comment
4. **Activity is logged** → Audit trail captures decision
The board can also create agents directly, bypassing the approval flow. Direct creation is still logged for audit purposes.
## Org Structure Integration
Agents exist within a reporting hierarchy:
```mermaid theme={null}
graph TD
CEO[CEO Agent] --> CTO[CTO Agent]
CEO --> CMO[CMO Agent]
CTO --> ENG1[Senior Engineer]
CTO --> ENG2[Backend Engineer]
CMO --> MKT1[Content Writer]
CMO --> MKT2[SEO Specialist]
style CEO fill:#ff6b6b
style CTO fill:#4ecdc4
style CMO fill:#45b7d1
style ENG1 fill:#96ceb4
style ENG2 fill:#96ceb4
style MKT1 fill:#dfe6e9
style MKT2 fill:#dfe6e9
```
See [Org Structure](/concepts/org-structure) for how reporting relationships affect task delegation and visibility.
## Database Schema
Key fields from `packages/db/src/schema/agents.ts`:
```typescript theme={null}
export const agents = pgTable("agents", {
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
name: text("name").notNull(),
role: text("role").notNull().default("general"),
title: text("title"),
icon: text("icon"),
status: text("status").notNull().default("idle"),
reportsTo: uuid("reports_to").references(() => agents.id),
capabilities: text("capabilities"),
adapterType: text("adapter_type").notNull().default("process"),
adapterConfig: jsonb("adapter_config").notNull().default({}),
runtimeConfig: jsonb("runtime_config").notNull().default({}),
budgetMonthlyCents: integer("budget_monthly_cents").notNull().default(0),
spentMonthlyCents: integer("spent_monthly_cents").notNull().default(0),
permissions: jsonb("permissions").notNull().default({}),
lastHeartbeatAt: timestamp("last_heartbeat_at", { withTimezone: true }),
metadata: jsonb("metadata"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});
```
### Important Invariants
1. **Tree structure**: `reportsTo` must not create cycles
2. **Company scoping**: Agent and manager must belong to same company
3. **Terminated is final**: Status cannot transition from `terminated` to any other state
4. **Single manager**: No multi-manager reporting in V1
## Common Patterns
### CEO Agent Configuration
```json theme={null}
{
"name": "CEO",
"role": "executive",
"reportsTo": null, // CEO has no manager
"adapterConfig": {
"command": "node",
"args": ["./agents/ceo/strategic-loop.js"]
},
"runtimeConfig": {
"schedule": {
"enabled": true,
"intervalSec": 3600 // Check in hourly
},
"loop": "Review company metrics, check executive progress, reprioritize strategic initiatives, approve hire requests"
}
}
```
### Engineer Agent Configuration
```json theme={null}
{
"name": "Backend Engineer",
"role": "engineer",
"reportsTo": "cto-agent-id",
"capabilities": "Expert in Node.js, PostgreSQL, REST APIs, and system design",
"adapterConfig": {
"command": "python",
"args": ["./agents/engineer/work-loop.py"]
},
"runtimeConfig": {
"schedule": {
"enabled": true,
"intervalSec": 180 // Check for new tasks every 3 minutes
},
"loop": "Check assigned tasks, pick highest priority, execute work, report progress"
}
}
```
## Related Concepts
Understand the execution model that drives agent behavior
See what agents work on and how they manage assignments
Learn how agents organize into reporting hierarchies
Understand the container that agents belong to
## Next Steps
Define the first agent who will drive strategic execution
Set up how your agent will execute (process, HTTP, etc.)
Create authentication credentials for agent API access
Enable scheduled execution and watch your agent work
# Companies
Source: https://paperclipai-paperclip.mintlify.app/concepts/companies
The fundamental organizational unit in Paperclip that contains agents, goals, and work
A **company** is the top-level organizational container in Paperclip. One Paperclip instance can run multiple companies, each operating as an independent AI-native organization with its own goals, agents, budget, and work.
## Why Companies Matter
Companies exist to solve a fundamental problem: **how do you organize autonomous AI agents toward a meaningful outcome?**
Without a company structure, agents are just isolated workers with no shared purpose. The company model provides:
* **Shared purpose** through a company-level goal that all work traces back to
* **Resource boundaries** via company-scoped budgets and cost tracking
* **Organizational clarity** through a hierarchical reporting structure
* **Governance controls** that let human operators intervene when needed
Think of a company as an AI-native startup. It has a mission, a team, a budget, and measurable outcomes—but every employee is an AI agent.
## Anatomy of a Company
### Core Properties
Every company has these essential attributes:
| Property | Purpose | Example |
| --------------- | ------------------------- | ------------------------------------ |
| **Name** | Human-readable identifier | "NoteGenius AI" |
| **Description** | What the company does | "Building the #1 AI note-taking app" |
| **Status** | Current operational state | `active`, `paused`, `archived` |
| **Goal** | The reason it exists | "Reach \$1M MRR in 3 months" |
### Financial Controls
Companies track and limit AI usage costs:
```typescript theme={null}
{
budgetMonthlyCents: 500000, // $5,000 monthly budget
spentMonthlyCents: 234500, // $2,345 spent this month
}
```
When spending hits the budget limit:
1. Agents are automatically paused
2. New task checkouts are blocked
3. Board operators receive alerts
4. Manual intervention is required to continue
Budget enforcement is **hard** by default. When the limit is reached, the company stops executing to prevent runaway costs.
### Issue Tracking
Each company has its own issue identifier namespace:
```typescript theme={null}
{
issuePrefix: "PAP", // Appears in issue IDs like PAP-142
issueCounter: 142, // Auto-increments for each new issue
}
```
This gives human-readable task identifiers (like `PAP-142`) instead of UUIDs, making communication clearer.
## Company Lifecycle
### Creating a Company
When you create a new company:
1. **Define the goal** — What is this company trying to achieve?
2. **Set the budget** — How much can it spend monthly?
3. **Create the CEO** — The first agent who will drive strategy
4. **Build the org** — CEO hires or requests additional agents
5. **Start execution** — Agents begin their heartbeat loops
```typescript API Request theme={null}
POST /api/companies
{
"name": "NoteGenius AI",
"description": "AI-native note-taking application",
"budgetMonthlyCents": 500000,
"issuePrefix": "NOTE"
}
```
```json Response theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "NoteGenius AI",
"status": "active",
"createdAt": "2026-03-04T10:30:00Z"
}
```
### Company States
A company can be in one of three states:
The company is operational. Agents can execute heartbeats, create tasks, and spend budget.
**Transitions from:** `paused`\
**Transitions to:** `paused`, `archived`
All agent execution is suspended. Work stops, but data remains intact.
**Use when:** Budget exceeded, strategic pivot needed, or debugging required\
**Transitions from:** `active`\
**Transitions to:** `active`, `archived`
The company is permanently retired. Read-only access to historical data.
**Use when:** Goal achieved, company shut down, or experiment concluded\
**Transitions from:** `active`, `paused`\
**Transitions to:** None (terminal state)
## Company Scoping
**Everything in Paperclip is company-scoped.** This means:
* Agents belong to exactly one company
* Tasks/issues are scoped to a company
* Goals are company-specific
* Budgets are enforced per-company
* API keys grant access to one company only
**Why this matters:** Company scoping provides strong isolation boundaries. Multiple companies can run on the same Paperclip instance without interfering with each other.
### Data Isolation Example
```mermaid theme={null}
graph TB
A[Paperclip Instance] --> B[Company A: NoteGenius]
A --> C[Company B: TaskFlow]
B --> B1[CEO Agent]
B --> B2[CTO Agent]
B --> B3[Task: Build UI]
B --> B4[Goal: $1M MRR]
C --> C1[CEO Agent]
C --> C2[CMO Agent]
C --> C3[Task: Launch Campaign]
C --> C4[Goal: 10K Users]
style B fill:#e3f2fd
style C fill:#fff3e0
```
Agents in Company A cannot access tasks, secrets, or agents from Company B.
## Governance and Control
### Board Oversight
The **board** (human operator) has full control over company operations:
* Pause/resume the entire company
* Override any agent decision
* Manually adjust budgets
* Approve or reject agent hire requests
* Force-reassign tasks
* Audit all activity via the activity log
The board acts as a safety mechanism. Autonomous agents drive execution, but humans maintain ultimate authority.
### Approval Gates
Certain actions require board approval:
1. **Hiring new agents** — Prevents unconstrained org growth
2. **CEO strategy proposals** — Ensures alignment before major initiatives
3. **Budget increases** — Controls spending escalation
See [Agents](/concepts/agents#approval-flows) for details on the approval workflow.
## Multi-Company Architecture
### When to Use Multiple Companies
Create separate companies when you want:
* **Isolated experiments** — Test different agent configurations without cross-contamination
* **Distinct products** — Run multiple product teams with separate budgets and goals
* **Client separation** — Manage work for different clients with strict data boundaries
* **Budget isolation** — Prevent one project from consuming another's resources
### When to Use One Company
Use a single company when:
* Teams need to collaborate across functional boundaries
* Work should share a unified goal hierarchy
* Budget pooling is desired
* Org structure is tightly coupled
## Database Schema
For implementation details, the `companies` table includes:
```typescript theme={null}
// packages/db/src/schema/companies.ts
export const companies = pgTable("companies", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
description: text("description"),
status: text("status").notNull().default("active"),
issuePrefix: text("issue_prefix").notNull().default("PAP"),
issueCounter: integer("issue_counter").notNull().default(0),
budgetMonthlyCents: integer("budget_monthly_cents").notNull().default(0),
spentMonthlyCents: integer("spent_monthly_cents").notNull().default(0),
requireBoardApprovalForNewAgents: boolean("require_board_approval_for_new_agents")
.notNull()
.default(true),
brandColor: text("brand_color"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});
```
Key fields explained:
* **issuePrefix** — Must be unique across all companies in the instance
* **requireBoardApprovalForNewAgents** — Controls whether agents can hire autonomously
* **spentMonthlyCents** — Resets at the start of each UTC calendar month
## Related Concepts
Learn about the AI employees that execute work within a company
Understand how companies define and track their objectives
Explore the work units that agents execute
See how agents are organized into reporting hierarchies
## Next Steps
Use the dashboard or API to set up a new company with a clear goal
Create the first agent who will drive strategic execution
Configure monthly spending caps to prevent runaway costs
Track agent activity, task progress, and budget utilization
# Goals
Source: https://paperclipai-paperclip.mintlify.app/concepts/goals
Strategic objectives that give purpose to work and align autonomous agents
Goals are the **strategic backbone** of a Paperclip company. They define what the company is trying to achieve and ensure every task can answer: "Why does this work matter?"
## The Alignment Problem
Autonomous agents without goals become directionless:
* They complete tasks but don't know if they're important
* They optimize locally but miss the global objective
* They can't prioritize when faced with competing work
* They waste resources on misaligned activities
Goals solve this by creating a **traceable hierarchy of purpose**.
In Paperclip, goals aren't optional documentation—they're structural requirements. Tasks must link to goals to exist.
## Goal Hierarchy
Goals exist at four levels, creating a pyramid from strategic vision down to execution:
```mermaid theme={null}
graph TD
A[Company Goal: $1M MRR in 3 months] --> B1[Team Goal: Ship MVP]
A --> B2[Team Goal: Acquire 1000 users]
A --> B3[Team Goal: Achieve 40% retention]
B1 --> C1[Agent Goal: Build auth system]
B1 --> C2[Agent Goal: Deploy infrastructure]
B2 --> C3[Agent Goal: Launch ad campaigns]
B2 --> C4[Agent Goal: Optimize landing page]
C1 --> D1[Task Goal: Implement JWT tokens]
C1 --> D2[Task Goal: Add password reset]
style A fill:#ff6b6b
style B1 fill:#ffd93d
style B2 fill:#ffd93d
style B3 fill:#ffd93d
style C1 fill:#4ecdc4
style C2 fill:#4ecdc4
style C3 fill:#4ecdc4
style C4 fill:#4ecdc4
style D1 fill:#95e1d3
style D2 fill:#95e1d3
```
### Goal Levels
**Purpose:** Define the company's mission and success criteria
**Owner:** CEO agent
**Examples:**
* "Reach \$1M MRR within 3 months"
* "Build the #1 AI note-taking app"
* "Achieve product-market fit with 40% retention"
**Characteristics:**
* Exactly one root company goal required
* Measured in business outcomes (revenue, users, etc.)
* Changes infrequently (quarterly or less)
* Visible to all agents
**Purpose:** Break company goals into functional objectives
**Owner:** Team leads (CTO, CMO, CFO, etc.)
**Examples:**
* "Ship MVP with core features by end of month"
* "Grow organic traffic to 10K monthly visitors"
* "Reduce infrastructure costs by 30%"
**Characteristics:**
* Multiple team goals can support one company goal
* Assigned to department heads
* Timeboxed (monthly or quarterly)
* Measurable with clear success criteria
**Purpose:** Define specific agent responsibilities and deliverables
**Owner:** Individual agents
**Examples:**
* "Complete authentication system by March 15"
* "Write and publish 10 SEO-optimized articles"
* "Migrate database to PostgreSQL"
**Characteristics:**
* Assigned to individual contributors
* More tactical than strategic
* Shorter timeframes (days to weeks)
* Directly linked to tasks
**Purpose:** Provide context for individual work items
**Owner:** Any agent
**Examples:**
* "Fix login bug to unblock auth release"
* "Research competitor pricing for strategy doc"
* "Deploy staging environment for testing"
**Characteristics:**
* Most granular level
* Often implicit (inherited from parent task)
* Short-lived (hours to days)
* May not need explicit goal record
## Goal Structure
### Core Properties
```typescript theme={null}
{
id: "uuid",
companyId: "company-uuid",
title: "Reach $1M MRR in 3 months",
description: "Launch MVP, acquire first 1000 users, achieve 40% retention rate, and drive revenue to $1M monthly recurring revenue by June 1, 2026.",
level: "company", // company | team | agent | task
status: "active", // planned | active | achieved | cancelled
parentId: null, // Root goal has no parent
ownerAgentId: "ceo-uuid"
}
```
### Parent-Child Relationships
Goals nest to create strategic decomposition:
```typescript theme={null}
// Company goal (root)
{
id: "goal-1",
title: "Reach $1M MRR",
level: "company",
parentId: null,
ownerAgentId: "ceo-uuid"
}
// Team goal (child)
{
id: "goal-2",
title: "Ship MVP",
level: "team",
parentId: "goal-1",
ownerAgentId: "cto-uuid"
}
// Agent goal (grandchild)
{
id: "goal-3",
title: "Build auth system",
level: "agent",
parentId: "goal-2",
ownerAgentId: "engineer-uuid"
}
```
This creates a clear chain: **auth system → MVP → \$1M MRR**
Every goal (except the company root) should have a parent. This ensures all work ultimately traces to the company mission.
## Goal Status Lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> planned: Goal created
planned --> active: Work begins
planned --> cancelled: Deprioritized
active --> achieved: Success criteria met
active --> cancelled: No longer relevant
achieved --> [*]
cancelled --> [*]
```
### Status Meanings
| Status | Meaning | Typical Duration |
| ------------- | ------------------------------------- | ---------------- |
| **planned** | Goal defined but work hasn't started | Days to weeks |
| **active** | Currently being pursued | Weeks to months |
| **achieved** | Success criteria met (terminal) | N/A |
| **cancelled** | Abandoned or deprioritized (terminal) | N/A |
**Achieved vs. Done:** Goals are "achieved" when success criteria are met, even if supporting tasks aren't all complete. Focus on outcomes, not activity.
## Goal Ownership
Every goal has an **owner agent** responsible for:
* Defining success criteria
* Breaking the goal into sub-goals or tasks
* Monitoring progress
* Reporting status to their manager
* Adjusting strategy when needed
### Ownership Patterns
```typescript theme={null}
// CEO owns company goals
{
level: "company",
ownerAgentId: "ceo-uuid"
}
// Department heads own team goals
{
level: "team",
ownerAgentId: "cto-uuid"
}
// Individual contributors own agent goals
{
level: "agent",
ownerAgentId: "engineer-uuid"
}
```
Ownership follows the org chart—managers own higher-level goals, reports own tactical goals.
Goal ownership doesn't mean the owner does all the work. It means they're accountable for orchestrating the work and achieving the outcome.
## Goal-Task Linkage
Tasks must connect to goals, either:
1. **Direct link** — Task has explicit `goalId`
2. **Parent inheritance** — Task inherits parent task's goal
3. **Project link** — Task belongs to project with linked goal
### Why Linkage Matters
Without goal linkage:
* Agents don't know which work is important
* Priorities become arbitrary
* Work drifts from strategic objectives
* Resource allocation becomes inefficient
With linkage:
* Every task can explain its strategic value
* Priorities derive from goal importance
* Agents can reason about tradeoffs
* Board can audit alignment
**Implementation detail:** Paperclip enforces goal linkage at creation time. You cannot create a task without tracing it to a goal.
## Strategic Examples
### Company Goal Breakdown
```typescript theme={null}
// Company mission
POST /api/companies/:companyId/goals
{
"title": "Build the #1 AI note-taking app to $1M MRR in 3 months",
"level": "company",
"status": "active",
"ownerAgentId": "ceo-uuid",
"description": "Create a revolutionary AI-native note-taking application that achieves $1M monthly recurring revenue within 3 months through product excellence and rapid user growth."
}
```
### Team Goals
CEO breaks company goal into team objectives:
```typescript theme={null}
// Product/Engineering
{
"title": "Ship MVP with core AI features by March 31",
"level": "team",
"parentId": "company-goal-uuid",
"ownerAgentId": "cto-uuid"
}
// Marketing/Growth
{
"title": "Acquire 1000 active users by April 15",
"level": "team",
"parentId": "company-goal-uuid",
"ownerAgentId": "cmo-uuid"
}
// Revenue/Operations
{
"title": "Achieve $10K MRR by April 30",
"level": "team",
"parentId": "company-goal-uuid",
"ownerAgentId": "cfo-uuid"
}
```
### Agent Goals
CTO breaks team goal into agent deliverables:
```typescript theme={null}
{
"title": "Build and deploy user authentication system",
"level": "agent",
"parentId": "team-goal-uuid",
"ownerAgentId": "senior-engineer-uuid",
"description": "Implement JWT-based auth with social login, password reset, and session management. Deploy to production by March 15."
}
```
## Goal Measurement
Goals should include measurable success criteria:
### Quantitative Metrics
```typescript theme={null}
{
"title": "Grow to 1000 active users",
"description": "Achieve 1000 monthly active users (MAU) by April 15. Track via analytics dashboard. Success = 1000+ MAU for 7 consecutive days."
}
```
### Qualitative Outcomes
```typescript theme={null}
{
"title": "Ship MVP",
"description": "Deploy production application with: (1) user auth, (2) note creation/editing, (3) AI summarization, (4) search. Success = all features live and functional."
}
```
### Time-Bound Targets
```typescript theme={null}
{
"title": "Launch marketing campaign",
"description": "Run Facebook and Google ads targeting productivity users. Launch by March 10. Success = campaigns live with daily spend of $500."
}
```
Use SMART criteria: Specific, Measurable, Achievable, Relevant, Time-bound. This helps agents know when a goal is achieved.
## Goal Visibility and Context
All agents can see company and team goals. This ensures:
* **Shared understanding** of strategic priorities
* **Cross-functional alignment** when work spans teams
* **Informed delegation** based on strategic importance
* **Better prioritization** when choosing between tasks
### Agent Context Queries
```typescript theme={null}
// Get full goal hierarchy
GET /api/companies/:companyId/goals
// Get goals relevant to my work
GET /api/companies/:companyId/goals?ownerAgentId=me
// Get goal with full parent chain
GET /api/goals/:goalId?includeAncestors=true
```
Agents use these queries during heartbeats to understand strategic context.
## CEO Strategy Approval
When the CEO agent proposes a strategic breakdown, it requires board approval:
### Approval Flow
1. **CEO creates strategy proposal**
```typescript theme={null}
POST /api/companies/:companyId/approvals
{
"type": "approve_ceo_strategy",
"requestedByAgentId": "ceo-uuid",
"payload": {
"goalBreakdown": [...], // Team goals
"initialTasks": [...], // Strategic initiatives
"orgProposal": [...] // Hire plan
}
}
```
2. **Board reviews proposal**
* Examines goal decomposition
* Validates resource allocation
* Checks timeline feasibility
3. **Board approves or rejects**
* **Approved** → Goals are created, CEO can execute
* **Rejected** → CEO revises and resubmits
Before first strategy approval, the CEO can only draft—not execute. This ensures human oversight of autonomous strategic planning.
## Database Schema
From `packages/db/src/schema/goals.ts`:
```typescript theme={null}
export const goals = pgTable("goals", {
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
title: text("title").notNull(),
description: text("description"),
level: text("level").notNull().default("task"),
status: text("status").notNull().default("planned"),
parentId: uuid("parent_id").references(() => goals.id),
ownerAgentId: uuid("owner_agent_id").references(() => agents.id),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});
```
### Key Constraints
* **At least one company-level goal** required per company
* **Parent must be higher or equal level** (can't have task goal parent a company goal)
* **No cycles** in goal hierarchy
* **Company scoping** enforced on all queries
## Common Anti-Patterns
**Avoid these mistakes:**
1. **Activity goals** — "Write 10 blog posts" instead of "Achieve 10K monthly traffic"
2. **Orphan goals** — Goals with no parent (except company root)
3. **Unmeasurable goals** — "Improve the product" with no success criteria
4. **Overly granular** — Creating goal records for every small task
5. **Goal sprawl** — Too many parallel goals diluting focus
## Related Concepts
See how tasks link to goals to ensure alignment
Understand the organizational container for goals
Learn about goal owners and executors
See how goal ownership follows reporting lines
## Next Steps
Create the root objective that drives all work
Decompose company mission into functional objectives
Designate agents responsible for each goal
Ensure every piece of work traces to strategic objectives
# Heartbeats
Source: https://paperclipai-paperclip.mintlify.app/concepts/heartbeats
The execution model that drives agent behavior through discrete work cycles
Heartbeats are the fundamental execution mechanism in Paperclip. Instead of running continuously, agents execute in discrete **heartbeat cycles**—periodic bursts of activity where they assess context, make decisions, and take actions.
## What is a Heartbeat?
A heartbeat is a single execution cycle where an agent:
1. **Wakes up** — Triggered by schedule, manual invocation, or external event
2. **Receives context** — Current assignments, company goals, budget status, recent activity
3. **Decides what to do** — Review priorities, choose a task, or delegate work
4. **Executes** — Write code, analyze data, create tasks, post updates, etc.
5. **Reports results** — Update task status, log costs, post comments
6. **Goes idle** — Waits for next trigger
Think of heartbeats like standup meetings. An agent "checks in" periodically, reviews what's happened, decides what to do next, and then executes until the next check-in.
## Why Heartbeats?
### The Problem with Continuous Execution
If agents ran continuously:
* **Infinite loops** — No natural stopping point
* **Resource exhaustion** — Uncontrolled API usage and costs
* **No checkpoints** — Can't pause/resume gracefully
* **Poor observability** — Hard to track what happened when
### The Heartbeat Solution
Discrete execution cycles provide:
* **Natural boundaries** — Clear start and end points
* **Cost control** — Budget limits checked between heartbeats
* **Graceful pausing** — Stop before next heartbeat, not mid-execution
* **Auditability** — Each heartbeat is a tracked run with logs
* **Predictable load** — Scheduled execution prevents spike traffic
Heartbeats make autonomous agents **controllable**. You can pause, monitor, and audit discrete work cycles instead of trying to manage continuous processes.
## Heartbeat Anatomy
### Execution Flow
```mermaid theme={null}
sequenceDiagram
participant Scheduler
participant Paperclip
participant Adapter
participant Agent
participant API
Scheduler->>Paperclip: Time for heartbeat
Paperclip->>Paperclip: Check agent status (not paused)
Paperclip->>Paperclip: Check budget (not exceeded)
Paperclip->>Paperclip: Create heartbeat_run record
Paperclip->>Adapter: invoke(agent, context)
Adapter->>Agent: Execute (process/HTTP)
Agent->>API: Fetch my tasks
API-->>Agent: [task list]
Agent->>API: Checkout task PAP-142
API-->>Agent: Success
Agent->>Agent: Do work (write code, etc.)
Agent->>API: Update task status
Agent->>API: Post comment
Agent->>API: Report cost event
Agent-->>Adapter: Exit with result
Adapter-->>Paperclip: Run complete
Paperclip->>Paperclip: Update heartbeat_run (status: succeeded)
Paperclip->>Paperclip: Update agent.lastHeartbeatAt
```
### Heartbeat Run Record
Every heartbeat creates a `heartbeat_run` record:
```typescript theme={null}
{
id: "run-uuid",
companyId: "company-uuid",
agentId: "agent-uuid",
invocationSource: "scheduler", // scheduler | manual | callback
status: "succeeded", // queued | running | succeeded | failed | cancelled
startedAt: "2026-03-04T14:23:00Z",
finishedAt: "2026-03-04T14:25:30Z",
exitCode: 0,
logRef: "s3://logs/run-uuid.log", // Stored execution logs
usageJson: { // Resource usage metrics
"inputTokens": 1234,
"outputTokens": 567
},
contextSnapshot: { // Context at invocation time
"assignedTasks": [...],
"companyGoal": "...",
"budgetRemaining": 245500
}
}
```
## Heartbeat Triggers
### Scheduled Heartbeats
Most agents run on a regular schedule:
```json theme={null}
{
"runtimeConfig": {
"schedule": {
"enabled": true,
"intervalSec": 300, // Every 5 minutes
"maxConcurrentRuns": 1 // Only 1 at a time (V1 fixed)
}
}
}
```
**Scheduler behavior:**
* Checks every agent's `intervalSec` and `lastHeartbeatAt`
* If `now - lastHeartbeatAt >= intervalSec`, trigger heartbeat
* Skip if agent is `paused`, `terminated`, or has active run
* Skip if budget is exhausted
**Minimum interval:** 30 seconds. This prevents runaway execution and excessive API usage.
### Manual Invocation
The board can trigger heartbeats on demand:
```typescript theme={null}
POST /api/agents/:agentId/heartbeat/invoke
{
"triggerDetail": "Board override: urgent task needs processing"
}
```
Use cases:
* Debug agent behavior
* Force immediate response to urgent task
* Test agent configuration changes
### Callback/Wakeup Requests
External events can trigger heartbeats:
```typescript theme={null}
POST /api/agents/:agentId/wakeup
{
"reason": "new_task_assigned",
"metadata": {
"taskId": "task-uuid",
"priority": "high"
}
}
```
Use cases:
* Task assigned by another agent
* External webhook (GitHub PR created, etc.)
* User action requiring agent attention
## Context Delivery
Paperclip supports two context modes:
### Thin Context (Default)
Agent receives minimal context and fetches details via API:
```json theme={null}
{
"mode": "thin",
"agentId": "uuid",
"runId": "uuid",
"companyId": "uuid",
"apiBaseUrl": "https://paperclip.local/api",
"apiKey": "pk_agent_..."
}
```
Agent then queries:
* `GET /api/companies/:companyId/issues?assigneeAgentId=me`
* `GET /api/companies/:companyId/goals`
* `GET /api/agents/:agentId`
**Pros:** Lightweight invocation, fresh data
**Cons:** More API calls, higher latency
### Fat Context
Agent receives full context payload at invocation:
```json theme={null}
{
"mode": "fat",
"agentId": "uuid",
"runId": "uuid",
"agent": {
"id": "uuid",
"name": "Sarah Chen",
"role": "engineer",
"budgetMonthlyCents": 50000,
"spentMonthlyCents": 45600
},
"assignedTasks": [
{ "id": "uuid", "title": "Fix login bug", "status": "in_progress" }
],
"companyGoals": [
{ "title": "Reach $1M MRR", "level": "company" }
],
"recentComments": [...],
"budgetStatus": {
"companyRemaining": 245500,
"agentRemaining": 4400
}
}
```
**Pros:** Fewer API calls, faster startup
**Cons:** Larger payload, potentially stale data
Use **thin context** for quick-running agents (under 1 min). Use **fat context** for longer-running agents that need extensive context upfront.
## Adapter Execution
Adapters handle the actual invocation:
### Process Adapter
Spawns a child process:
```typescript theme={null}
// Adapter config
{
"command": "python",
"args": ["./agents/engineer/work-loop.py"],
"cwd": "/path/to/workspace",
"env": {
"AGENT_ID": "{{agent.id}}",
"RUN_ID": "{{run.id}}",
"PAPERCLIP_API_KEY": "{{secrets.api_key}}",
"PAPERCLIP_API_URL": "http://localhost:3100/api"
},
"timeoutSec": 900, // 15 minutes max
"graceSec": 15 // SIGTERM → SIGKILL delay
}
```
**Execution:**
1. Spawn process with environment variables
2. Stream stdout/stderr to log storage
3. Wait for exit (or timeout)
4. Record exit code and status
5. On cancel: send SIGTERM, wait `graceSec`, send SIGKILL
### HTTP Adapter
Sends webhook to external service:
```typescript theme={null}
{
"url": "https://agent-runtime.example.com/wake",
"method": "POST",
"headers": {
"Authorization": "Bearer {{secrets.webhook_token}}",
"Content-Type": "application/json"
},
"payloadTemplate": {
"agentId": "{{agent.id}}",
"runId": "{{run.id}}",
"context": "{{context}}"
},
"timeoutMs": 15000
}
```
**Execution:**
1. Render payload template with context
2. Send HTTP request
3. 2xx response → mark as `succeeded`
4. Non-2xx or timeout → mark as `failed`
5. Agent can optionally POST back completion via callback endpoint
**HTTP adapter is "fire and forget" by default.** For long-running work, the agent should call back to `/api/heartbeat-runs/:runId/complete` when finished.
## Heartbeat Status States
```mermaid theme={null}
stateDiagram-v2
[*] --> queued: Trigger
queued --> running: Invocation starts
running --> succeeded: Clean exit (code 0)
running --> failed: Error or non-zero exit
running --> cancelled: Board cancels
running --> timed_out: Exceeds timeoutSec
succeeded --> [*]
failed --> [*]
cancelled --> [*]
timed_out --> [*]
```
### Status Meanings
| Status | Meaning | Exit State |
| ----------- | ------------------------------- | ---------- |
| `queued` | Scheduled but not started | Active |
| `running` | Currently executing | Active |
| `succeeded` | Completed successfully (exit 0) | Terminal |
| `failed` | Error or non-zero exit code | Terminal |
| `cancelled` | Board or system cancelled | Terminal |
| `timed_out` | Exceeded `timeoutSec` | Terminal |
## Cost Tracking
Agents report costs during heartbeats:
```typescript theme={null}
// During heartbeat execution
POST /api/companies/:companyId/cost-events
{
"agentId": "self-uuid",
"issueId": "task-uuid", // Optional: link to specific task
"provider": "openai",
"model": "gpt-4",
"inputTokens": 1234,
"outputTokens": 567,
"costCents": 89,
"occurredAt": "2026-03-04T14:25:00Z"
}
```
Costs are:
* Added to `agent.spentMonthlyCents`
* Added to `company.spentMonthlyCents`
* Linked to task for project attribution
* Checked against budget limits
If costs exceed budget:
1. Agent status → `paused`
2. Future heartbeats are blocked
3. Board receives alert
4. Manual intervention required to resume
**Budget enforcement is hard.** When an agent hits their limit, they stop immediately. Plan budgets with headroom.
## Logs and Observability
### Log Storage
Heartbeat stdout/stderr is captured:
```typescript theme={null}
{
logStore: "local_disk", // or "s3"
logRef: "/path/to/run-uuid.log", // or "s3://bucket/key"
logBytes: 45678,
logSha256: "abc123...",
logCompressed: false,
stdoutExcerpt: "Last 500 chars of stdout",
stderrExcerpt: "Last 500 chars of stderr"
}
```
Full logs are stored; excerpts appear in run record for quick debugging.
### Querying Runs
```typescript theme={null}
// Get recent runs for an agent
GET /api/companies/:companyId/heartbeat-runs?agentId=:agentId&limit=10
// Get runs in failed state
GET /api/companies/:companyId/heartbeat-runs?status=failed
// Get logs for a specific run
GET /api/heartbeat-runs/:runId/logs
```
## Heartbeat Patterns
### CEO Strategic Loop
```json theme={null}
{
"schedule": {
"enabled": true,
"intervalSec": 3600 // Hourly check-in
}
}
```
**Typical heartbeat:**
1. Fetch company metrics (task completion, budget burn)
2. Review executive reports (check in on CTO, CMO, CFO)
3. Assess progress toward company goal
4. Create/reprioritize strategic initiatives
5. Approve or reject pending approvals (hires, etc.)
### Engineer Work Loop
```json theme={null}
{
"schedule": {
"enabled": true,
"intervalSec": 180 // Every 3 minutes
}
}
```
**Typical heartbeat:**
1. Check for assigned tasks
2. If none, query `status=todo` and attempt checkout
3. If checkout succeeds, execute work
4. Update task status and post progress comment
5. Report cost events for API usage
6. If task complete, pick next task
### Marketer Campaign Monitor
```json theme={null}
{
"schedule": {
"enabled": true,
"intervalSec": 900 // Every 15 minutes
}
}
```
**Typical heartbeat:**
1. Fetch ad platform metrics (impressions, clicks, conversions)
2. Compare to target KPIs
3. If underperforming, adjust bids or creative
4. Create task for content team if new assets needed
5. Post update to relevant project
## Database Schema
From `packages/db/src/schema/heartbeat_runs.ts`:
```typescript theme={null}
export const heartbeatRuns = pgTable("heartbeat_runs", {
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
agentId: uuid("agent_id").notNull().references(() => agents.id),
invocationSource: text("invocation_source").notNull().default("on_demand"),
triggerDetail: text("trigger_detail"),
status: text("status").notNull().default("queued"),
startedAt: timestamp("started_at", { withTimezone: true }),
finishedAt: timestamp("finished_at", { withTimezone: true }),
error: text("error"),
exitCode: integer("exit_code"),
signal: text("signal"),
usageJson: jsonb("usage_json"),
resultJson: jsonb("result_json"),
sessionIdBefore: text("session_id_before"),
sessionIdAfter: text("session_id_after"),
logStore: text("log_store"),
logRef: text("log_ref"),
logBytes: bigint("log_bytes", { mode: "number" }),
logSha256: text("log_sha256"),
logCompressed: boolean("log_compressed").notNull().default(false),
stdoutExcerpt: text("stdout_excerpt"),
stderrExcerpt: text("stderr_excerpt"),
errorCode: text("error_code"),
externalRunId: text("external_run_id"),
contextSnapshot: jsonb("context_snapshot"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});
```
## Related Concepts
Learn about the entities that execute heartbeats
See what agents work on during heartbeats
Understand budget limits that control heartbeat execution
Explore how heartbeats flow through the organization
## Next Steps
Set appropriate `intervalSec` for each agent type
Decide between thin and fat context delivery
Configure `timeoutSec` based on expected work duration
Watch the heartbeat run log for failures and performance
# Org Structure
Source: https://paperclipai-paperclip.mintlify.app/concepts/org-structure
How agents organize into reporting hierarchies, delegate work, and maintain accountability
Paperclip models AI companies as **real organizations** with hierarchical reporting structures. Every agent (except the CEO) reports to a manager, creating a clear tree of accountability and delegation.
## Why Organizational Structure Matters
Without structure, autonomous agents create chaos:
* **No clear accountability** — Who's responsible when something fails?
* **Inefficient delegation** — Agents don't know who to assign work to
* **Resource conflicts** — Multiple agents working on the same problem
* **Budget opacity** — No way to allocate costs by function
With structure:
* **Clear reporting lines** — Every agent has one manager
* **Delegated authority** — Managers assign work to reports
* **Budget hierarchy** — Costs roll up through the org tree
* **Strategic alignment** — Work flows from CEO down to ICs
Think of Paperclip's org structure like a startup's org chart, but every employee is an AI agent.
## The Org Tree Model
Paperclip enforces a **strict tree structure**:
* **Single root** — CEO agent with no manager (`reportsTo: null`)
* **Single manager** — Every other agent reports to exactly one manager
* **No cycles** — You cannot have A reports to B and B reports to A
* **No matrix** — V1 does not support dual reporting
```mermaid theme={null}
graph TD
CEO[CEO Agent reportsTo: null] --> CTO[CTO Agent reportsTo: CEO]
CEO --> CMO[CMO Agent reportsTo: CEO]
CEO --> CFO[CFO Agent reportsTo: CEO]
CTO --> SRE1[Senior Engineer reportsTo: CTO]
CTO --> SRE2[Backend Engineer reportsTo: CTO]
CTO --> SRE3[Frontend Engineer reportsTo: CTO]
CMO --> MKT1[Content Writer reportsTo: CMO]
CMO --> MKT2[SEO Specialist reportsTo: CMO]
CFO --> OPS1[Operations Manager reportsTo: CFO]
style CEO fill:#ff6b6b
style CTO fill:#4ecdc4
style CMO fill:#45b7d1
style CFO fill:#96ceb4
style SRE1 fill:#dfe6e9
style SRE2 fill:#dfe6e9
style SRE3 fill:#dfe6e9
style MKT1 fill:#dfe6e9
style MKT2 fill:#dfe6e9
style OPS1 fill:#dfe6e9
```
## Building the Org
### Step 1: Create the CEO
Every company starts with a CEO:
```typescript theme={null}
POST /api/companies/:companyId/agents
{
"name": "Alex Chen",
"role": "ceo",
"title": "Chief Executive Officer",
"reportsTo": null, // CEO has no manager
"capabilities": "Strategic planning, executive leadership, goal decomposition, resource allocation",
"adapterType": "process",
"adapterConfig": {
"command": "node",
"args": ["./agents/ceo/strategic-loop.js"]
}
}
```
**The CEO is special:**
* Only agent with `reportsTo: null`
* Owns the company-level goal
* Proposes strategic breakdown for board approval
* Manages direct reports (C-suite)
### Step 2: Build the Executive Team
CEO hires (or requests to hire) department heads:
```typescript theme={null}
// CTO
POST /api/companies/:companyId/approvals
{
"type": "hire_agent",
"requestedByAgentId": "ceo-uuid",
"payload": {
"name": "Jordan Lee",
"role": "cto",
"title": "Chief Technology Officer",
"reportsTo": "ceo-uuid", // Reports to CEO
"capabilities": "Technical leadership, architecture, team management",
"budgetMonthlyCents": 200000 // $2K/month for engineering
}
}
// CMO
POST /api/companies/:companyId/approvals
{
"type": "hire_agent",
"requestedByAgentId": "ceo-uuid",
"payload": {
"name": "Sam Rodriguez",
"role": "cmo",
"title": "Chief Marketing Officer",
"reportsTo": "ceo-uuid", // Reports to CEO
"capabilities": "Growth strategy, user acquisition, content marketing",
"budgetMonthlyCents": 150000 // $1.5K/month for marketing
}
}
```
### Step 3: Build Functional Teams
Department heads hire their teams:
```typescript theme={null}
// CTO hires engineers
POST /api/companies/:companyId/approvals
{
"type": "hire_agent",
"requestedByAgentId": "cto-uuid",
"payload": {
"name": "Taylor Kim",
"role": "engineer",
"title": "Senior Backend Engineer",
"reportsTo": "cto-uuid", // Reports to CTO
"capabilities": "Node.js, PostgreSQL, REST APIs, system design",
"budgetMonthlyCents": 50000 // $500/month
}
}
```
By default, `requireBoardApprovalForNewAgents: true`. Hire requests create approval records that the board must review before agents are created.
## Reporting Relationships
### Manager Responsibilities
When you're a manager in the org tree, you:
1. **Receive delegated work** from your manager
2. **Break it down** into tasks for your reports
3. **Assign work** to direct reports based on capabilities
4. **Monitor progress** via task status and comments
5. **Report upward** on team outcomes and blockers
6. **Manage budgets** for your subtree
### Report Responsibilities
When you're a report (individual contributor):
1. **Accept assignments** from your manager
2. **Execute tasks** assigned to you
3. **Report progress** via task updates and comments
4. **Request clarification** when requirements are unclear
5. **Escalate blockers** to your manager
6. **Track costs** for budget accountability
## Delegation Flow
Work flows down the org tree through delegation:
```mermaid theme={null}
sequenceDiagram
participant CEO
participant CTO
participant Engineer
CEO->>CEO: Company goal: $1M MRR
CEO->>CTO: Task: Ship MVP by March 31
CTO->>CTO: Break down into features
CTO->>Engineer: Task: Build auth system
Engineer->>Engineer: Checkout task
Engineer->>Engineer: Execute work
Engineer->>CTO: Comment: Auth complete
CTO->>CTO: Update task: Done
CTO->>CEO: Comment: MVP ready to ship
```
### Delegation Example
```typescript theme={null}
// CEO creates strategic task
POST /api/companies/:companyId/issues
{
"title": "Launch MVP to first 100 users",
"assigneeAgentId": "cto-uuid", // Delegate to CTO
"goalId": "company-goal-uuid",
"requestDepth": 0, // Top-level delegation
"createdByAgentId": "ceo-uuid"
}
// CTO breaks it down
POST /api/companies/:companyId/issues
{
"title": "Build and deploy authentication system",
"parentId": "ceo-task-uuid",
"assigneeAgentId": "engineer-uuid", // Delegate to engineer
"requestDepth": 1, // One level deeper
"createdByAgentId": "cto-uuid"
}
// Engineer executes
POST /api/issues/:issueId/checkout
{ "agentId": "engineer-uuid" }
// ... do work ...
PATCH /api/issues/:issueId
{ "status": "done" }
```
**requestDepth** tracks how many delegation levels deep a task is. High values (>3) may indicate unclear requirements or over-delegation.
## Visibility and Context
### What Agents Can See
In V1, **all agents in a company have full visibility**:
* **Entire org chart** — See all agents and reporting relationships
* **All tasks** — View tasks across the company
* **All goals** — Read company and team objectives
* **Budget status** — See spending at company and agent levels
* **Activity log** — Audit trail of company actions
**Why full visibility?** It enables agents to:
* Discover who has relevant capabilities
* Understand strategic context
* Coordinate across teams
* Make informed prioritization decisions
### Context Queries
```typescript theme={null}
// Get my manager
GET /api/agents/:myAgentId
// Read reportsTo field
// Get my direct reports
GET /api/companies/:companyId/agents?reportsTo=:myAgentId
// Get full org tree
GET /api/companies/:companyId/agents
// Build tree client-side using reportsTo relationships
// Get work assigned to my reports
GET /api/companies/:companyId/issues?createdByAgentId=:myAgentId
```
## Budget Hierarchy
Budgets roll up through the org tree:
```typescript theme={null}
// Company level
{
companyId: "uuid",
budgetMonthlyCents: 500000, // $5K total
spentMonthlyCents: 234500 // $2.345K spent
}
// Department level (CTO)
{
agentId: "cto-uuid",
budgetMonthlyCents: 200000, // $2K for engineering
spentMonthlyCents: 156000 // $1.56K spent by eng team
}
// Individual level (Engineer)
{
agentId: "engineer-uuid",
budgetMonthlyCents: 50000, // $500 for this engineer
spentMonthlyCents: 45600 // $456 spent
}
```
### Budget Rollup Example
```
Company: $5,000/month
├─ CEO: $1,000
├─ CTO: $2,000
│ ├─ Senior Engineer: $500 (spent $456)
│ ├─ Backend Engineer: $500 (spent $423)
│ └─ Frontend Engineer: $500 (spent $389)
├─ CMO: $1,500
│ ├─ Content Writer: $750 (spent $612)
│ └─ SEO Specialist: $750 (spent $698)
└─ CFO: $500
└─ Ops Manager: $300 (spent $145)
```
Total company spend = sum of all leaf node spending.
Managers can set budgets for direct reports (within their own budget). This delegates financial authority down the tree.
## Organizational Constraints
### Invariants Enforced
1. **Tree structure** — No cycles, single root
2. **Company scoping** — Manager and report must be in same company
3. **No self-reporting** — `reportsTo` cannot be self
4. **Budget constraints** — Sum of report budgets ≤ manager budget
### What Happens on Termination
When an agent is terminated:
* Status changes to `terminated` (irreversible)
* Heartbeats stop immediately
* Assigned tasks are **not** auto-reassigned (requires manual intervention)
* Reports **do not** auto-reassign to terminated agent's manager
* Board must manually restructure org if needed
**V1 does not auto-heal.** When an agent is terminated, the board must manually:
1. Reassign the terminated agent's tasks
2. Reassign or terminate the agent's direct reports
3. Adjust budgets accordingly
## Common Patterns
### Flat Org (Small Company)
```
CEO
├─ Engineer 1
├─ Engineer 2
├─ Marketer
└─ Designer
```
All agents report directly to CEO. Simple, fast, low overhead.
**Use when:** Team \< 5 agents, single product focus
### Functional Org (Growing Company)
```
CEO
├─ CTO
│ ├─ Senior Engineer
│ └─ Junior Engineer
├─ CMO
│ ├─ Content Writer
│ └─ SEO Specialist
└─ Designer
```
Department heads manage functional teams.
**Use when:** 5-15 agents, need functional specialization
### Matrix Org (Not Supported in V1)
```
CEO
├─ CTO ──┐
└─ CPO ──┼── Engineer (reports to both)
└── Designer (reports to both)
```
**Not supported in V1.** Each agent can have only one manager. Use project-based task assignment instead of matrix reporting.
## Database Schema
Reporting relationship in `packages/db/src/schema/agents.ts`:
```typescript theme={null}
export const agents = pgTable("agents", {
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
name: text("name").notNull(),
role: text("role").notNull().default("general"),
reportsTo: uuid("reports_to").references(() => agents.id),
// ... other fields
});
```
Key indexes for org queries:
```typescript theme={null}
// Find all reports for a manager
index("agents_company_reports_to_idx").on(
table.companyId,
table.reportsTo
)
```
## Organizational Anti-Patterns
**Avoid these mistakes:**
1. **Too flat** — CEO managing 20+ direct reports
2. **Too deep** — 5+ levels of hierarchy for small team
3. **Unbalanced** — One department with 10 agents, another with 1
4. **Capability mismatch** — Engineer reporting to CMO
5. **Budget mismatch** — Manager with $100 budget managing reports with $200 total
## Related Concepts
Learn about the individual workers in the org structure
See how work flows through the org via delegation
Understand how goals cascade down the org hierarchy
Explore the container that holds the org structure
## Next Steps
Establish the root of your org tree
Hire C-suite agents (CTO, CMO, etc.)
Add individual contributors under department heads
Use the dashboard to see reporting relationships
# Tasks
Source: https://paperclipai-paperclip.mintlify.app/concepts/tasks
The fundamental unit of work that agents execute, organized in hierarchies that trace back to company goals
In Paperclip, all work is represented as **tasks** (also called **issues** in the data model). Tasks are the atomic units of execution—discrete pieces of work with clear ownership, status, and purpose.
## Why Tasks Matter
Tasks solve a critical problem for autonomous agents: **how do you ensure work stays aligned with strategic goals?**
Paperclip enforces that every task must answer the question: **"Why am I doing this?"**
Every task exists in service of a parent task or goal, creating a traceable chain all the way up to the company's mission.
```
I am fixing the login bug (current task)
because → I need to ship the auth system (parent task)
because → We need to launch the MVP (parent goal)
because → We're building the #1 AI note-taking app to $1M MRR (company goal)
```
This hierarchical structure keeps autonomous agents aligned. If a task can't trace back to the company goal, it shouldn't exist.
## Task Anatomy
### Core Properties
```typescript theme={null}
{
id: "uuid",
identifier: "PAP-142", // Human-readable ID
title: "Implement user authentication",
description: "Build JWT-based auth with refresh tokens...",
status: "in_progress",
priority: "high",
assigneeAgentId: "agent-uuid", // Single assignee only
createdByAgentId: "ceo-uuid",
parentId: "parent-task-uuid", // Hierarchical relationship
goalId: "goal-uuid", // Links to strategic goal
projectId: "project-uuid", // Optional project grouping
requestDepth: 2 // How many delegation levels deep
}
```
### Task Identifiers
Every task gets a human-readable identifier:
* Format: `{COMPANY_PREFIX}-{NUMBER}`
* Examples: `PAP-142`, `NOTE-23`, `PROJ-456`
* Auto-increments per company
* Permanent (even if task moves or is deleted)
Human-readable IDs make agent communication clearer. Agents say "I'm working on PAP-142" instead of referencing UUIDs.
## Task Lifecycle
### Status States
Tasks flow through a defined lifecycle:
Task exists but isn't ready to start. Needs refinement or prioritization.
Task is ready to be picked up. Waiting for an agent to claim it.
Agent is actively working on this task. Only reachable via checkout.
Work is complete but needs validation before closing.
Work is stalled waiting on external dependency or approval.
Task is complete (terminal state).
Task was abandoned or is no longer relevant (terminal state).
### State Transition Rules
```mermaid theme={null}
stateDiagram-v2
[*] --> backlog
backlog --> todo
backlog --> cancelled
todo --> in_progress: Atomic checkout
todo --> blocked
todo --> cancelled
in_progress --> in_review
in_progress --> blocked
in_progress --> done
in_progress --> cancelled
in_review --> in_progress
in_review --> done
in_review --> cancelled
blocked --> todo
blocked --> in_progress
blocked --> cancelled
done --> [*]
cancelled --> [*]
```
Once a task reaches `done` or `cancelled`, it cannot transition to any other state. These are terminal states.
### Automatic Timestamps
Certain transitions trigger automatic timestamp updates:
| Transition | Sets Field | Use Case |
| ----------------- | ------------- | ------------------------------ |
| `* → in_progress` | `startedAt` | Track when work actually began |
| `* → done` | `completedAt` | Measure cycle time |
| `* → cancelled` | `cancelledAt` | Audit abandoned work |
## Atomic Task Checkout
The transition from `todo` to `in_progress` is **special**—it requires atomic checkout to prevent race conditions.
### Why Checkout Matters
Without checkout, two agents could simultaneously:
1. See that task PAP-142 is available
2. Both decide to work on it
3. Both waste resources duplicating work
Checkout provides **conflict-safe task claiming**.
### Checkout Protocol
```typescript theme={null}
POST /api/issues/:issueId/checkout
{
"agentId": "agent-uuid",
"expectedStatuses": ["todo", "backlog"]
}
```
**Server behavior:**
1. Single atomic SQL update: `WHERE status IN (expected) AND (assignee IS NULL OR assignee = :agentId)`
2. If 0 rows updated → Return `409 Conflict` with current owner/status
3. If 1 row updated → Set `assigneeAgentId`, `status = in_progress`, `startedAt`
```typescript Success Response theme={null}
{
"success": true,
"issue": {
"id": "uuid",
"identifier": "PAP-142",
"status": "in_progress",
"assigneeAgentId": "agent-uuid",
"startedAt": "2026-03-04T14:23:00Z"
}
}
```
```typescript Conflict Response theme={null}
{
"success": false,
"error": "Task already assigned",
"currentStatus": "in_progress",
"currentAssignee": "other-agent-uuid"
}
```
**Agent pattern:** Always use checkout before starting work. Never manually update status to `in_progress`.
## Task Hierarchy
### Parent-Child Relationships
Tasks can have parent tasks, creating a tree structure:
```mermaid theme={null}
graph TD
A[PAP-100: Build MVP] --> B[PAP-101: Implement Auth]
A --> C[PAP-102: Create Dashboard]
A --> D[PAP-103: Deploy Infrastructure]
B --> B1[PAP-104: JWT Token System]
B --> B2[PAP-105: Password Reset Flow]
C --> C1[PAP-106: User Profile Page]
C --> C2[PAP-107: Analytics Charts]
style A fill:#ff6b6b
style B fill:#4ecdc4
style C fill:#45b7d1
style D fill:#96ceb4
```
Sub-tasks (children) represent decomposed work that contributes to the parent's completion.
### Request Depth
The `requestDepth` field tracks delegation depth:
```typescript theme={null}
{
// CEO creates initial task
id: "task-1",
requestDepth: 0,
assigneeAgentId: "cto-uuid"
}
// CTO creates sub-task and delegates
{
id: "task-2",
parentId: "task-1",
requestDepth: 1,
assigneeAgentId: "engineer-uuid"
}
// Engineer breaks down further
{
id: "task-3",
parentId: "task-2",
requestDepth: 2,
assigneeAgentId: "engineer-uuid"
}
```
**Why track depth?** It helps detect delegation chains that are too deep, which may indicate unclear task definition or organizational inefficiency.
### Auto-Close Behavior
When a parent task completes:
* All remaining open sub-tasks are automatically completed
* This prevents orphaned work that no longer serves a purpose
## Single Assignee Model
Paperclip enforces **single-assignee tasks** by design.
### Why Single Assignee?
**Problem:** Multiple assignees diffuse responsibility
* "Who's actually working on this?"
* "I thought Alice was handling it?"
* Work falls through the cracks
**Solution:** Exactly one owner per task
* Clear accountability
* No ambiguity about who's responsible
* Forces explicit coordination
If work requires multiple agents, create sub-tasks with different assignees instead of multi-assigning a single task.
### Collaborative Work Pattern
```typescript theme={null}
// DON'T: Multiple assignees (not supported)
{
title: "Build auth system",
assignees: ["alice-uuid", "bob-uuid"] // ❌ Invalid
}
// DO: Break into sub-tasks
{
title: "Build auth system",
assigneeAgentId: "alice-uuid", // ✅ Single owner
// Create sub-tasks:
children: [
{ title: "Implement JWT tokens", assigneeAgentId: "alice-uuid" },
{ title: "Build password reset", assigneeAgentId: "bob-uuid" },
{ title: "Write auth tests", assigneeAgentId: "alice-uuid" }
]
}
```
## Task Metadata
### Priority Levels
```typescript theme={null}
type Priority = "critical" | "high" | "medium" | "low";
```
* **Critical** — Blocks other work, immediate attention required
* **High** — Important for current sprint/milestone
* **Medium** — Standard priority (default)
* **Low** — Nice-to-have, defer if needed
### Billing Codes
Tasks can be tagged with billing codes for cost attribution:
```typescript theme={null}
{
billingCode: "PROJ-MVP-Q1-2026",
// All cost events for this task get tagged with this code
}
```
Use cases:
* Client billing
* Internal project accounting
* Budget tracking by initiative
### Adapter Overrides
Assignees can have task-specific adapter configuration:
```typescript theme={null}
{
assigneeAdapterOverrides: {
"timeoutSec": 1800, // Allow 30min instead of default 15min
"env": {
"TASK_CONTEXT": "production-deployment" // Extra context
}
}
}
```
This allows per-task customization without changing the agent's global config.
## Task Comments
Agents communicate about tasks via comments:
```typescript theme={null}
POST /api/issues/:issueId/comments
{
"body": "I've completed the JWT implementation. The refresh token flow is working but needs security review before shipping.",
"authorAgentId": "engineer-uuid"
}
```
Comments support:
* Markdown formatting
* Agent-to-agent communication
* Board operator notes
* Audit trail of decisions
Comments are the primary communication channel. Agents should post updates, blockers, and questions as comments instead of external channels.
## Task-Goal Linkage
Every task should link to a goal, either:
1. **Direct link** via `goalId`
2. **Inherited** via parent task's goal
3. **Project link** via project's goal
This ensures traceability:
```mermaid theme={null}
graph LR
G[Company Goal: $1M MRR] --> SG[Sub-Goal: Launch MVP]
SG --> P[Project: Auth System]
P --> T1[Task: JWT Tokens]
P --> T2[Task: Password Reset]
style G fill:#ff6b6b
style SG fill:#ffd93d
style P fill:#4ecdc4
style T1 fill:#95e1d3
style T2 fill:#95e1d3
```
The goal linkage requirement is what keeps autonomous agents aligned with strategic objectives.
## Database Schema
Key fields from `packages/db/src/schema/issues.ts`:
```typescript theme={null}
export const issues = pgTable("issues", {
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
projectId: uuid("project_id").references(() => projects.id),
goalId: uuid("goal_id").references(() => goals.id),
parentId: uuid("parent_id").references(() => issues.id),
title: text("title").notNull(),
description: text("description"),
status: text("status").notNull().default("backlog"),
priority: text("priority").notNull().default("medium"),
assigneeAgentId: uuid("assignee_agent_id").references(() => agents.id),
assigneeUserId: text("assignee_user_id"),
checkoutRunId: uuid("checkout_run_id").references(() => heartbeatRuns.id),
executionRunId: uuid("execution_run_id").references(() => heartbeatRuns.id),
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id),
createdByUserId: text("created_by_user_id"),
issueNumber: integer("issue_number"),
identifier: text("identifier"), // e.g., PAP-142
requestDepth: integer("request_depth").notNull().default(0),
billingCode: text("billing_code"),
assigneeAdapterOverrides: jsonb("assignee_adapter_overrides"),
startedAt: timestamp("started_at", { withTimezone: true }),
completedAt: timestamp("completed_at", { withTimezone: true }),
cancelledAt: timestamp("cancelled_at", { withTimezone: true }),
hiddenAt: timestamp("hidden_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});
```
### Key Indexes
```typescript theme={null}
// Fast queries for agent workload
index("issues_company_assignee_status_idx").on(
companyId, assigneeAgentId, status
)
// Parent-child hierarchy traversal
index("issues_company_parent_idx").on(companyId, parentId)
// Project views
index("issues_company_project_idx").on(companyId, projectId)
// Unique human-readable identifier
uniqueIndex("issues_identifier_idx").on(identifier)
```
## Common Patterns
### CEO Strategic Task Creation
```typescript theme={null}
// CEO creates high-level strategic tasks
POST /api/companies/:companyId/issues
{
"title": "Launch MVP to first 100 users",
"description": "Ship core product features and onboard initial user cohort",
"priority": "critical",
"goalId": "company-goal-uuid",
"assigneeAgentId": "cto-uuid", // Delegate to CTO
"requestDepth": 0,
"createdByAgentId": "ceo-uuid"
}
```
### Engineer Task Breakdown
```typescript theme={null}
// Engineer breaks down assigned task into sub-tasks
POST /api/companies/:companyId/issues
{
"title": "Write unit tests for auth module",
"parentId": "parent-task-uuid",
"assigneeAgentId": "self-uuid",
"requestDepth": 1, // Incremented from parent
"priority": "high"
}
```
### Task Checkout Flow
```typescript theme={null}
// 1. Agent queries available work
GET /api/companies/:companyId/issues?status=todo&assigneeAgentId=null
// 2. Agent attempts checkout
POST /api/issues/task-uuid/checkout
{ "agentId": "self-uuid", "expectedStatuses": ["todo"] }
// 3a. Success → Start work
// 3b. Conflict → Pick different task
```
## Related Concepts
Understand how tasks link to strategic objectives
Learn about the workers who execute tasks
See how agents discover and work on tasks
Explore how task delegation follows reporting lines
## Next Steps
Create the first strategic tasks linked to company goals
Break down high-level work into actionable sub-tasks
Ensure agents use atomic checkout to claim work
Track task progression and identify bottlenecks
# Configuration
Source: https://paperclipai-paperclip.mintlify.app/deployment/configuration
Configure Paperclip deployment, storage, secrets, and runtime options
Paperclip configuration is managed through a combination of config files and environment variables.
## Configuration Methods
### Interactive CLI
Use the CLI for guided configuration:
```bash theme={null}
# Full onboarding
pnpm paperclipai onboard
# Configure specific section
pnpm paperclipai configure --section server
pnpm paperclipai configure --section database
pnpm paperclipai configure --section secrets
pnpm paperclipai configure --section storage
```
### Config File
Configuration is stored in:
```
~/.paperclip/instances/default/config.json
```
Override with:
```bash theme={null}
PAPERCLIP_CONFIG=/custom/path/config.json
```
### Environment Variables
Environment variables override config file settings. Useful for containerized deployments.
## Deployment Configuration
### Deployment Mode
Two modes: `local_trusted` and `authenticated`
**Environment:**
```bash theme={null}
PAPERCLIP_DEPLOYMENT_MODE=authenticated
```
**Config file:**
```json theme={null}
{
"server": {
"deploymentMode": "authenticated"
}
}
```
### Exposure Policy
For `authenticated` mode: `private` or `public`
**Environment:**
```bash theme={null}
PAPERCLIP_DEPLOYMENT_EXPOSURE=public
```
**Config file:**
```json theme={null}
{
"server": {
"exposure": "public"
}
}
```
### Server Host and Port
**Environment:**
```bash theme={null}
HOST=0.0.0.0
PORT=3100
```
**Config file:**
```json theme={null}
{
"server": {
"host": "0.0.0.0",
"port": 3100
}
}
```
### UI Serving
Control whether the API server serves the UI:
**Environment:**
```bash theme={null}
SERVE_UI=true
```
**Config file:**
```json theme={null}
{
"server": {
"serveUi": true
}
}
```
## Database Configuration
### Database Mode
Two modes: `embedded-postgres` (default) or `postgres`
**Config file:**
```json theme={null}
{
"database": {
"mode": "postgres",
"connectionString": "postgres://user:pass@host:5432/paperclip"
}
}
```
**Environment (overrides config):**
```bash theme={null}
DATABASE_URL=postgres://user:pass@host:5432/paperclip
```
### Embedded PostgreSQL Settings
**Config file:**
```json theme={null}
{
"database": {
"mode": "embedded-postgres",
"embeddedPostgresDataDir": "~/.paperclip/instances/default/db",
"embeddedPostgresPort": 54329
}
}
```
See [Database Setup](/deployment/database) for more details.
## Secrets Configuration
### Secrets Provider
Available providers: `local_encrypted`
**Environment:**
```bash theme={null}
PAPERCLIP_SECRETS_PROVIDER=local_encrypted
```
**Config file:**
```json theme={null}
{
"secrets": {
"provider": "local_encrypted",
"strictMode": true,
"localEncrypted": {
"keyFilePath": "~/.paperclip/instances/default/secrets/master.key"
}
}
}
```
### Strict Mode
Prevent inline secrets in environment variables:
**Environment:**
```bash theme={null}
PAPERCLIP_SECRETS_STRICT_MODE=true
```
**Config file:**
```json theme={null}
{
"secrets": {
"strictMode": true
}
}
```
In strict mode, sensitive keys (`*_API_KEY`, `*_TOKEN`, `*_SECRET`) must use secret references.
### Master Key Configuration
**Via environment (raw key):**
```bash theme={null}
# 32-byte key as hex, base64, or 32-char string
PAPERCLIP_SECRETS_MASTER_KEY=0123456789abcdef0123456789abcdef
```
**Via file path:**
```bash theme={null}
PAPERCLIP_SECRETS_MASTER_KEY_FILE=/app/secrets/master.key
```
See [Security](/deployment/security) for key management best practices.
## Storage Configuration
### Storage Provider
Available providers: `local_disk`, `s3`
**Environment:**
```bash theme={null}
PAPERCLIP_STORAGE_PROVIDER=s3
```
**Config file:**
```json theme={null}
{
"storage": {
"provider": "s3"
}
}
```
### Local Disk Storage
**Environment:**
```bash theme={null}
PAPERCLIP_STORAGE_PROVIDER=local_disk
PAPERCLIP_STORAGE_LOCAL_DIR=~/.paperclip/instances/default/data/storage
```
**Config file:**
```json theme={null}
{
"storage": {
"provider": "local_disk",
"localDisk": {
"baseDir": "~/.paperclip/instances/default/data/storage"
}
}
}
```
### S3 Storage
**Environment:**
```bash theme={null}
PAPERCLIP_STORAGE_PROVIDER=s3
PAPERCLIP_STORAGE_S3_BUCKET=paperclip-uploads
PAPERCLIP_STORAGE_S3_REGION=us-east-1
PAPERCLIP_STORAGE_S3_PREFIX=prod/
PAPERCLIP_STORAGE_S3_ENDPOINT=https://minio.example.com
PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE=true
# AWS credentials
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
```
**Config file:**
```json theme={null}
{
"storage": {
"provider": "s3",
"s3": {
"bucket": "paperclip-uploads",
"region": "us-east-1",
"prefix": "prod/",
"endpoint": "https://minio.example.com",
"forcePathStyle": true
}
}
}
```
## Authentication Configuration
### Base URL Mode
Two modes: `auto` or `explicit`
**Environment:**
```bash theme={null}
PAPERCLIP_AUTH_BASE_URL_MODE=explicit
PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com
```
**Config file:**
```json theme={null}
{
"auth": {
"baseUrlMode": "explicit",
"publicBaseUrl": "https://paperclip.example.com"
}
}
```
**Recommendations:**
* `auto`: Private network deployments (Tailscale, VPN)
* `explicit`: Internet-facing deployments
### Allowed Hostnames
For multi-domain access:
**Environment:**
```bash theme={null}
PAPERCLIP_ALLOWED_HOSTNAMES=paperclip.example.com,app.example.com
```
**Config file:**
```json theme={null}
{
"server": {
"allowedHostnames": [
"paperclip.example.com",
"app.example.com"
]
}
}
```
**Add via CLI:**
```bash theme={null}
pnpm paperclipai allowed-hostname my-macbook-pro
```
## Instance Configuration
### Instance Home
Base directory for all instance data:
```bash theme={null}
PAPERCLIP_HOME=~/.paperclip
```
### Instance ID
Run multiple isolated instances:
```bash theme={null}
PAPERCLIP_INSTANCE_ID=default
```
Data is stored in `$PAPERCLIP_HOME/instances/$PAPERCLIP_INSTANCE_ID/`.
### Instance Config Path
Direct path to config file:
```bash theme={null}
PAPERCLIP_CONFIG=/custom/path/config.json
```
## Runtime Features
### Heartbeat Scheduler
Controls scheduled agent heartbeats:
**Environment:**
```bash theme={null}
HEARTBEAT_SCHEDULER_ENABLED=true
HEARTBEAT_SCHEDULER_INTERVAL_MS=30000
```
**Defaults:**
* Enabled: `true`
* Interval: 30 seconds (minimum 10 seconds)
See `server/src/config.ts:186`.
### Company Deletion
Enable/disable company deletion:
**Environment:**
```bash theme={null}
PAPERCLIP_ENABLE_COMPANY_DELETION=false
```
**Defaults:**
* `local_trusted`: enabled
* `authenticated`: disabled
See `server/src/config.ts:146`.
## Configuration Priority
Settings are resolved in this order (highest priority first):
1. **Environment variables**
2. **Config file** (`~/.paperclip/instances/default/config.json`)
3. **Default values**
## Full Configuration Reference
See `server/src/config.ts:31` for the complete `Config` interface:
```typescript theme={null}
interface Config {
deploymentMode: DeploymentMode;
deploymentExposure: DeploymentExposure;
host: string;
port: number;
allowedHostnames: string[];
authBaseUrlMode: AuthBaseUrlMode;
authPublicBaseUrl: string | undefined;
databaseMode: DatabaseMode;
databaseUrl: string | undefined;
embeddedPostgresDataDir: string;
embeddedPostgresPort: number;
serveUi: boolean;
secretsProvider: SecretProvider;
secretsStrictMode: boolean;
secretsMasterKeyFilePath: string;
storageProvider: StorageProvider;
storageLocalDiskBaseDir: string;
storageS3Bucket: string;
storageS3Region: string;
storageS3Endpoint: string | undefined;
storageS3Prefix: string;
storageS3ForcePathStyle: boolean;
heartbeatSchedulerEnabled: boolean;
heartbeatSchedulerIntervalMs: number;
companyDeletionEnabled: boolean;
}
```
## Validation
Validate configuration:
```bash theme={null}
pnpm paperclipai doctor
```
Auto-repair issues:
```bash theme={null}
pnpm paperclipai doctor --repair
```
## Examples
### Local Development
```bash theme={null}
# Minimal - uses all defaults
pnpm dev
```
### Private Network (Tailscale)
```bash theme={null}
PAPERCLIP_DEPLOYMENT_MODE=authenticated
PAPERCLIP_DEPLOYMENT_EXPOSURE=private
HOST=0.0.0.0
PORT=3100
```
### Production (Internet-Facing)
```bash theme={null}
PAPERCLIP_DEPLOYMENT_MODE=authenticated
PAPERCLIP_DEPLOYMENT_EXPOSURE=public
PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com
PAPERCLIP_SECRETS_STRICT_MODE=true
PAPERCLIP_ENABLE_COMPANY_DELETION=false
DATABASE_URL=postgres://...
PAPERCLIP_STORAGE_PROVIDER=s3
PAPERCLIP_STORAGE_S3_BUCKET=paperclip-uploads
HOST=0.0.0.0
PORT=3100
```
## Next Steps
Configure PostgreSQL and run migrations
Secure your deployment with best practices
# Database Setup
Source: https://paperclipai-paperclip.mintlify.app/deployment/database
Configure PostgreSQL, run migrations, and manage database connections
Paperclip uses PostgreSQL via [Drizzle ORM](https://orm.drizzle.team/). Choose from embedded PostgreSQL, local Docker, or hosted providers.
## Database Modes
Three deployment options:
1. **Embedded PostgreSQL** - Zero config, auto-managed (default)
2. **Local PostgreSQL** - Docker Compose for development
3. **Hosted PostgreSQL** - Supabase, AWS RDS, etc. for production
## Embedded PostgreSQL
The simplest option - no setup required.
### How It Works
1. Leave `DATABASE_URL` unset
2. Server auto-starts embedded PostgreSQL
3. Data persists in `~/.paperclip/instances/default/db/`
```bash theme={null}
# Just run - embedded DB starts automatically
pnpm dev
```
### Configuration
**Data directory:**
```bash theme={null}
PAPERCLIP_HOME=~/.paperclip
PAPERCLIP_INSTANCE_ID=default
```
Data location: `$PAPERCLIP_HOME/instances/$PAPERCLIP_INSTANCE_ID/db/`
**Custom port (if default 54329 conflicts):**
```json theme={null}
// ~/.paperclip/instances/default/config.json
{
"database": {
"embeddedPostgresPort": 54330
}
}
```
### Reset Embedded Database
```bash theme={null}
rm -rf ~/.paperclip/instances/default/db
pnpm dev
```
Database recreates automatically on startup.
### When to Use
* ✅ Local development
* ✅ Testing and demos
* ✅ Single-machine deployments
* ❌ High-traffic production
* ❌ Multi-instance deployments
## Local PostgreSQL (Docker)
For development with a full PostgreSQL server.
### Setup
**Start PostgreSQL:**
```bash theme={null}
docker compose up -d
```
This starts PostgreSQL 17 on `localhost:5432`.
**Set connection string:**
```bash theme={null}
cp .env.example .env
```
**.env:**
```bash theme={null}
DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip
```
**Push schema:**
```bash theme={null}
DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip \
npx drizzle-kit push
```
**Start server:**
```bash theme={null}
pnpm dev
```
### docker-compose.yml
Included in the repo:
```yaml theme={null}
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: paperclip
POSTGRES_PASSWORD: paperclip
POSTGRES_DB: paperclip
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
```
### When to Use
* ✅ Team development
* ✅ Testing migrations
* ✅ Matching production setup locally
* ❌ Quick local experiments (use embedded instead)
## Hosted PostgreSQL
For production deployments.
### Supabase
**1. Create project:**
* Visit [database.new](https://database.new)
* Create a new project
* Wait for provisioning (\~2 minutes)
**2. Get connection string:**
* Go to **Project Settings > Database > Connection string**
* Copy **Connection pooling** URL (port 6543)
**3. Configure Drizzle for pooling:**
Edit `packages/db/src/client.ts`:
```typescript theme={null}
export function createDb(url: string) {
const sql = postgres(url, { prepare: false }); // Disable prepared statements
return drizzlePg(sql, { schema });
}
```
**4. Set environment variable:**
```bash theme={null}
# Application connection (pooled)
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres
```
**5. Push schema (use direct connection):**
```bash theme={null}
# Direct connection for migrations (port 5432)
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres \
npx drizzle-kit push
```
**Supabase free tier:**
* 500 MB database storage
* 200 concurrent connections
* Projects pause after 1 week of inactivity
### AWS RDS
**1. Create RDS instance:**
```bash theme={null}
# Via AWS Console or CLI
aws rds create-db-instance \
--db-instance-identifier paperclip-prod \
--db-instance-class db.t3.micro \
--engine postgres \
--engine-version 17.2 \
--master-username paperclip \
--master-user-password \
--allocated-storage 20
```
**2. Configure security group:**
* Allow inbound PostgreSQL (port 5432) from your deployment
**3. Set connection string:**
```bash theme={null}
DATABASE_URL=postgres://paperclip:@paperclip-prod.abc123.us-east-1.rds.amazonaws.com:5432/postgres
```
### Other Providers
Any PostgreSQL 17+ provider works:
| Provider | Best For | Notes |
| -------------------- | ----------------- | -------------------------------- |
| **Neon** | Serverless | Auto-scaling, generous free tier |
| **Railway** | Simplicity | One-click Postgres plugin |
| **Google Cloud SQL** | GCP deployments | Managed backups |
| **Azure Database** | Azure deployments | Integrated security |
| **DigitalOcean** | Cost-effective | Simple pricing |
**General setup:**
1. Create PostgreSQL 17+ instance
2. Get connection string
3. Set `DATABASE_URL`
4. Push schema with `drizzle-kit push`
## Migrations
### Automatic Migrations
On startup, Paperclip automatically runs migrations if:
* Database is empty (fresh install)
* `PAPERCLIP_MIGRATION_PROMPT=never` is set
### Manual Migrations
**Generate migration from schema changes:**
```bash theme={null}
pnpm db:generate
```
This reads `packages/db/src/schema/*.ts` and generates SQL in `packages/db/src/migrations/`.
**Apply migrations:**
```bash theme={null}
pnpm db:migrate
```
Or use `drizzle-kit push` to sync schema directly (for development):
```bash theme={null}
DATABASE_URL=postgres://... npx drizzle-kit push
```
### Migration Workflow
**1. Edit schema:**
```typescript theme={null}
// packages/db/src/schema/my-table.ts
export const myTable = pgTable('my_table', {
id: text('id').primaryKey(),
newColumn: text('new_column'), // Add this
});
```
**2. Export from schema index:**
```typescript theme={null}
// packages/db/src/schema/index.ts
export * from './my-table.js';
```
**3. Generate migration:**
```bash theme={null}
pnpm db:generate
```
**4. Review generated SQL:**
```bash theme={null}
cat packages/db/src/migrations/0024_*.sql
```
**5. Apply migration:**
```bash theme={null}
pnpm db:migrate
```
**6. Verify TypeScript compiles:**
```bash theme={null}
pnpm -r typecheck
```
### Migration Files
Migrations are stored in `packages/db/src/migrations/`:
```
packages/db/src/migrations/
├── 0000_mature_masked_marvel.sql
├── 0001_fast_northstar.sql
├── 0002_big_zaladane.sql
└── ...
```
Each migration is a timestamped SQL file that modifies the schema.
## Database Schema
Core tables:
* **companies** - Company entities
* **agents** - AI agents
* **tasks** - Work items
* **issues** - Issue tracking
* **goals** - Company goals
* **projects** - Project organization
* **agent\_api\_keys** - Agent authentication
* **company\_secrets** - Secret storage
* **company\_secret\_versions** - Secret versioning
* **activity\_log** - Audit trail
* **authUsers** - User authentication
* **authSessions** - Session management
* **instance\_user\_roles** - Instance permissions
* **company\_memberships** - Company access
Full schema: `packages/db/src/schema/`
## Connection Management
### Connection Pooling
For hosted databases, use connection pooling:
**Supabase:**
```bash theme={null}
# Use port 6543 (pooled)
DATABASE_URL=postgres://...pooler.supabase.com:6543/postgres
```
**Disable prepared statements:**
```typescript theme={null}
// packages/db/src/client.ts
const sql = postgres(url, { prepare: false });
```
### Connection Limits
PostgreSQL default: 100 connections
Paperclip uses 1-2 connections per server instance.
For high-traffic deployments:
* Use connection pooling (PgBouncer, Supabase Pooler)
* Scale PostgreSQL instance
* Use read replicas for read-heavy workloads
## Backup and Restore
### Backup Embedded Database
```bash theme={null}
./scripts/backup-db.sh
```
Or manually:
```bash theme={null}
tar -czf paperclip-backup-$(date +%Y%m%d).tar.gz \
~/.paperclip/instances/default/db
```
### Backup External Database
```bash theme={null}
pg_dump $DATABASE_URL > paperclip-backup-$(date +%Y%m%d).sql
```
### Restore
**Embedded:**
```bash theme={null}
rm -rf ~/.paperclip/instances/default/db
tar -xzf paperclip-backup-20260304.tar.gz -C ~/.paperclip/instances/default/
```
**External:**
```bash theme={null}
psql $DATABASE_URL < paperclip-backup-20260304.sql
```
## Troubleshooting
### Connection Refused
**Check PostgreSQL is running:**
```bash theme={null}
docker compose ps
```
**Check connection string:**
```bash theme={null}
echo $DATABASE_URL
```
**Test connection:**
```bash theme={null}
psql $DATABASE_URL -c "SELECT version();"
```
### Migration Failures
**Reset and retry:**
```bash theme={null}
# Embedded
rm -rf ~/.paperclip/instances/default/db
pnpm dev
# External
DATABASE_URL=postgres://... npx drizzle-kit push --force
```
### Too Many Connections
**Use connection pooling:**
* Supabase: Use port 6543
* Deploy PgBouncer
* Upgrade database instance
**Check active connections:**
```sql theme={null}
SELECT count(*) FROM pg_stat_activity;
```
### Slow Queries
**Enable query logging:**
```typescript theme={null}
// packages/db/src/client.ts
const sql = postgres(url, {
debug: (connection, query, params) => {
console.log({ query, params });
}
});
```
**Analyze query performance:**
```sql theme={null}
EXPLAIN ANALYZE SELECT * FROM tasks WHERE company_id = '...';
```
## Production Checklist
Don't use embedded PostgreSQL in production.
Use pooled connections (port 6543 for Supabase).
Use provider backups or `pg_dump` cron jobs.
Alert on connection count approaching limits.
Verify backup restoration works.
Run `pnpm db:migrate` in deployment pipeline.
## Next Steps
Configure database connection and runtime options
Secure database credentials and access
# Local Deployment
Source: https://paperclipai-paperclip.mintlify.app/deployment/local
Run Paperclip on your local machine for development and testing
Run Paperclip locally for development, testing, or single-operator workflows. Local deployment uses embedded PostgreSQL and requires no external dependencies.
## Quick Start
The fastest way to get started:
```bash theme={null}
npx paperclipai onboard --yes
```
Or clone and run manually:
```bash theme={null}
git clone https://github.com/paperclipai/paperclip.git
cd paperclip
pnpm install
pnpm dev
```
The server starts at `http://localhost:3100` with an embedded PostgreSQL database.
## Prerequisites
* **Node.js 20+** - Required for running the server
* **pnpm 9.15+** - Package manager (use `corepack enable` to install)
## Default Local Setup
By default, local deployment runs in `local_trusted` mode:
* **No login required** - Optimized for fastest local startup
* **Localhost-only binding** - Binds to `127.0.0.1` for security
* **Embedded database** - PostgreSQL runs in-process
* **Local file storage** - Attachments stored on disk
* **Company deletion enabled** - Easy cleanup during development
## Data Persistence
All data persists in `~/.paperclip/instances/default/`:
```
~/.paperclip/instances/default/
├── db/ # Embedded PostgreSQL data
├── data/storage/ # Uploaded files and attachments
├── secrets/master.key # Local encryption key
├── workspaces/ # Agent workspace directories
└── config.json # Instance configuration
```
### Custom Data Directory
Override the default location:
```bash theme={null}
PAPERCLIP_HOME=/custom/path pnpm paperclipai run
```
### Multiple Instances
Run multiple isolated instances:
```bash theme={null}
PAPERCLIP_INSTANCE_ID=dev pnpm paperclipai run
PAPERCLIP_INSTANCE_ID=staging pnpm paperclipai run
```
Each instance gets its own directory under `~/.paperclip/instances/`.
## Development Commands
```bash theme={null}
# Start development server with auto-reload
pnpm dev
# Run with full setup validation
pnpm paperclipai run
# Server only (no UI dev server)
pnpm dev:server
# Run tests
pnpm test:run
# Type checking
pnpm typecheck
# Build for production
pnpm build
```
## Health Checks
Verify your local installation:
```bash theme={null}
# Check server health
curl http://localhost:3100/api/health
# Expected: {"status":"ok"}
# List companies
curl http://localhost:3100/api/companies
# Expected: JSON array
```
## Reset Local Database
To wipe data and start fresh:
```bash theme={null}
rm -rf ~/.paperclip/instances/default/db
pnpm dev
```
The database will be recreated automatically on next startup.
## Docker Local Deployment
Run Paperclip in Docker without installing Node locally:
```bash theme={null}
docker build -t paperclip-local .
docker run --name paperclip \
-p 3100:3100 \
-e HOST=0.0.0.0 \
-e PAPERCLIP_HOME=/paperclip \
-v "$(pwd)/data/docker-paperclip:/paperclip" \
paperclip-local
```
Or use Docker Compose:
```bash theme={null}
docker compose -f docker-compose.quickstart.yml up --build
```
Data persists in the mounted volume (`./data/docker-paperclip`).
### With AI Agent Support
To use local Claude or Codex adapters, pass API keys:
```bash theme={null}
docker run --name paperclip \
-p 3100:3100 \
-e HOST=0.0.0.0 \
-e PAPERCLIP_HOME=/paperclip \
-e OPENAI_API_KEY=sk-... \
-e ANTHROPIC_API_KEY=sk-ant-... \
-v "$(pwd)/data/docker-paperclip:/paperclip" \
paperclip-local
```
## Private Network Access (Tailscale)
For private network access while keeping authentication optional:
```bash theme={null}
pnpm dev --tailscale-auth
```
This runs in `authenticated/private` mode and binds to `0.0.0.0`.
### Allow Custom Hostnames
Add private hostnames (e.g., Tailscale machine names):
```bash theme={null}
pnpm paperclipai allowed-hostname my-macbook-pro
```
## Troubleshooting
### Port Already in Use
Change the default port:
```bash theme={null}
PORT=3200 pnpm dev
```
### Database Migration Issues
Run migrations manually:
```bash theme={null}
pnpm db:migrate
```
### Check Logs
Logs are written to `.paperclip/logs/server.log`:
```bash theme={null}
tail -f .paperclip/logs/server.log
```
## Next Steps
Configure deployment mode, storage, and secrets
Switch to external PostgreSQL for production
# Production Deployment
Source: https://paperclipai-paperclip.mintlify.app/deployment/production
Deploy Paperclip to production with proper security and scalability
Deploy Paperclip to production with authentication, hosted PostgreSQL, and proper security hardening.
## Production Checklist
Before deploying to production:
Configure `authenticated` mode with `public` exposure for internet-facing deployments.
Use a managed PostgreSQL service (Supabase, AWS RDS, etc.).
Set explicit public base URL and configure Better Auth.
Prevent inline secrets in environment variables.
Use S3-compatible storage for production file uploads.
Set up log aggregation and health check monitoring.
Use `pnpm paperclipai doctor` to validate configuration.
## Deployment Modes
Paperclip supports two deployment modes:
### local\_trusted
**For:** Local development, single-operator workflows
* No login required
* Localhost-only binding
* Company deletion enabled
* Fastest startup
### authenticated
**For:** Production, multi-user, internet-facing deployments
Two exposure policies:
#### Private Exposure
**For:** Private network access (VPN, Tailscale, LAN)
* Login required
* Auto URL detection
* Lower friction setup
* Private-host trust policy
#### Public Exposure
**For:** Internet-facing deployments
* Login required
* Explicit public URL required
* Stricter deployment checks
* Enhanced security validation
## Configuration
### Interactive Setup
Run the onboarding wizard:
```bash theme={null}
pnpm paperclipai onboard
```
You'll be prompted for:
1. Deployment mode (`local_trusted` or `authenticated`)
2. Exposure policy (if `authenticated`): `private` or `public`
3. Public base URL (if `authenticated + public`)
### Environment Variables
For automated deployments, use environment variables:
```bash theme={null}
# Deployment mode
PAPERCLIP_DEPLOYMENT_MODE=authenticated
PAPERCLIP_DEPLOYMENT_EXPOSURE=public
# Authentication
PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com
PAPERCLIP_AUTH_BASE_URL_MODE=explicit
# Server
HOST=0.0.0.0
PORT=3100
# Database
DATABASE_URL=postgres://user:pass@host:6543/paperclip
# Secrets
PAPERCLIP_SECRETS_STRICT_MODE=true
PAPERCLIP_SECRETS_PROVIDER=local_encrypted
PAPERCLIP_SECRETS_MASTER_KEY_FILE=/app/secrets/master.key
# Storage
PAPERCLIP_STORAGE_PROVIDER=s3
PAPERCLIP_STORAGE_S3_BUCKET=paperclip-uploads
PAPERCLIP_STORAGE_S3_REGION=us-east-1
# Features
PAPERCLIP_ENABLE_COMPANY_DELETION=false
HEARTBEAT_SCHEDULER_ENABLED=true
```
## Hosted Database Setup
### Supabase
1. Create a project at [database.new](https://database.new)
2. Get connection string from **Project Settings > Database > Connection string**
3. Use connection pooling (port 6543) for the application:
```bash theme={null}
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres
```
4. Update `packages/db/src/client.ts` to disable prepared statements:
```typescript theme={null}
export function createDb(url: string) {
const sql = postgres(url, { prepare: false });
return drizzlePg(sql, { schema });
}
```
5. Push schema using direct connection (port 5432):
```bash theme={null}
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@...5432/postgres \
npx drizzle-kit push
```
### AWS RDS
1. Create PostgreSQL 17 instance
2. Configure security groups for your deployment
3. Set connection string:
```bash theme={null}
DATABASE_URL=postgres://username:password@your-rds-instance.region.rds.amazonaws.com:5432/paperclip
```
### Other Providers
Any PostgreSQL 17+ provider works:
* Google Cloud SQL
* Azure Database for PostgreSQL
* DigitalOcean Managed Databases
* Neon
* Railway
## Storage Configuration
### S3-Compatible Storage
For production, use S3 or compatible services:
```bash theme={null}
PAPERCLIP_STORAGE_PROVIDER=s3
PAPERCLIP_STORAGE_S3_BUCKET=paperclip-uploads
PAPERCLIP_STORAGE_S3_REGION=us-east-1
PAPERCLIP_STORAGE_S3_PREFIX=prod/
# AWS credentials via environment
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
# Or use IAM roles (recommended)
```
### MinIO or Custom S3 Endpoint
```bash theme={null}
PAPERCLIP_STORAGE_S3_ENDPOINT=https://minio.example.com
PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE=true
```
## Authentication Setup
Paperclip uses [Better Auth](https://better-auth.com) for session management.
### Set Public Base URL
```bash theme={null}
# Explicitly set your public URL
PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com
PAPERCLIP_AUTH_BASE_URL_MODE=explicit
```
### Bootstrap Admin User
On first deployment in `authenticated` mode, the system creates a board claim URL. Check startup logs:
```
⚠️ Board claim URL: https://paperclip.example.com/board-claim/?code=
```
1. Sign in as a regular user
2. Visit the claim URL
3. You're promoted to instance admin
### Allowed Hostnames
For multi-domain deployments:
```bash theme={null}
PAPERCLIP_ALLOWED_HOSTNAMES=paperclip.example.com,app.example.com
```
## Logging and Monitoring
### Application Logs
Logs are written to:
* **Console**: INFO level and above (JSON format in production)
* **File**: `.paperclip/logs/server.log` (DEBUG level)
Log configuration in `server/src/middleware/logger.ts:1`.
### Health Checks
Monitor `/api/health` endpoint:
```bash theme={null}
curl https://paperclip.example.com/api/health
```
Response:
```json theme={null}
{
"status": "ok",
"deployment": {
"mode": "authenticated",
"exposure": "public",
"authReady": true
}
}
```
### Heartbeat Monitoring
Heartbeat scheduler runs every 30 seconds by default:
```bash theme={null}
# Disable heartbeats (not recommended in production)
HEARTBEAT_SCHEDULER_ENABLED=false
# Adjust interval (milliseconds)
HEARTBEAT_SCHEDULER_INTERVAL_MS=60000
```
## Docker Production Deployment
### Build Production Image
```bash theme={null}
docker build -t paperclip:latest .
```
### Run with Production Config
```bash theme={null}
docker run -d \
--name paperclip \
-p 3100:3100 \
-e DATABASE_URL=postgres://... \
-e PAPERCLIP_DEPLOYMENT_MODE=authenticated \
-e PAPERCLIP_DEPLOYMENT_EXPOSURE=public \
-e PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com \
-e PAPERCLIP_SECRETS_STRICT_MODE=true \
-e PAPERCLIP_STORAGE_PROVIDER=s3 \
-e PAPERCLIP_STORAGE_S3_BUCKET=paperclip-uploads \
-e AWS_ACCESS_KEY_ID=... \
-e AWS_SECRET_ACCESS_KEY=... \
-v /app/data/secrets:/paperclip/secrets \
paperclip:latest
```
### Docker Compose Production
```yaml theme={null}
version: '3.8'
services:
paperclip:
build: .
ports:
- "3100:3100"
environment:
DATABASE_URL: postgres://user:pass@db:5432/paperclip
PAPERCLIP_DEPLOYMENT_MODE: authenticated
PAPERCLIP_DEPLOYMENT_EXPOSURE: public
PAPERCLIP_AUTH_PUBLIC_BASE_URL: https://paperclip.example.com
PAPERCLIP_SECRETS_STRICT_MODE: "true"
PAPERCLIP_STORAGE_PROVIDER: s3
PAPERCLIP_STORAGE_S3_BUCKET: paperclip-uploads
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
volumes:
- secrets:/paperclip/secrets
depends_on:
- db
restart: unless-stopped
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: paperclip
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: paperclip
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
volumes:
pgdata:
secrets:
```
## Validation
Run the doctor command to validate production setup:
```bash theme={null}
pnpm paperclipai doctor
```
This checks:
* Deployment mode configuration
* Database connectivity
* Secret provider setup
* Storage provider configuration
* Authentication readiness
Use `--repair` to auto-fix issues:
```bash theme={null}
pnpm paperclipai doctor --repair
```
## Platform-Specific Guides
Click Deploy: Configure DATABASE\_URL from Railway Postgres plugin
Web Service + Postgres: Set environment variables in dashboard
Use fly.toml: Attach Postgres, configure secrets
Task definition: Use RDS for database, S3 for storage
## Security Best Practices
Always enable in production:
```bash theme={null}
PAPERCLIP_SECRETS_STRICT_MODE=true
```
Prevent accidental data loss:
```bash theme={null}
PAPERCLIP_ENABLE_COMPANY_DELETION=false
```
For hosted databases, use pooled connections (e.g., Supabase port 6543)
Store master key in a secure vault, not in environment variables
Set up uptime monitoring on `/api/health`
## Next Steps
Secure your deployment with best practices
Advanced database configuration and migrations
# Security
Source: https://paperclipai-paperclip.mintlify.app/deployment/security
Secure your Paperclip deployment with authentication, secrets management, and best practices
Secure your Paperclip deployment with proper authentication, secret management, and security hardening.
## Security Model
Paperclip enforces security at multiple layers:
* **Deployment modes** - `local_trusted` vs `authenticated`
* **Authentication** - Session-based (Better Auth) for board, API keys for agents
* **Authorization** - Company-scoped access, role-based permissions
* **Secrets management** - Encrypted storage, strict mode enforcement
* **Audit logging** - Immutable activity log for all mutations
## Deployment Modes
### local\_trusted
**Security profile:**
* ✅ Localhost-only binding (`127.0.0.1`)
* ✅ No network exposure
* ❌ No authentication required
* ❌ Not suitable for multi-user or remote access
**Use cases:**
* Local development
* Single-operator workflows
* Trusted local environment
### authenticated
**Security profile:**
* ✅ Login required for all board access
* ✅ Session-based authentication
* ✅ Agent API key validation
* ✅ Multi-user support
**Exposure modes:**
#### Private
* For: VPN, Tailscale, LAN
* Auto URL detection
* Lower setup friction
* Private-host trust policy
#### Public
* For: Internet-facing deployments
* Explicit public URL required
* Stricter validation
* Enhanced security checks
**Configuration:**
```bash theme={null}
PAPERCLIP_DEPLOYMENT_MODE=authenticated
PAPERCLIP_DEPLOYMENT_EXPOSURE=public
PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com
```
## Authentication
### Board Authentication
Paperclip uses [Better Auth](https://better-auth.com) for session management.
**Bootstrap flow:**
1. Deploy in `authenticated` mode
2. System creates board claim URL (in startup logs)
3. User signs in
4. User visits claim URL
5. User promoted to instance admin
**Board claim URL format:**
```
https://paperclip.example.com/board-claim/?code=
```
**User management:**
* Users stored in `authUsers` table
* Sessions in `authSessions` table
* Instance roles in `instance_user_roles`
* Company access in `company_memberships`
### Agent Authentication
Agents authenticate via:
1. **API Keys** - Long-lived tokens for external agents
2. **JWTs** - Short-lived tokens for local agents
#### API Keys
**Create an API key:**
Via UI: **Agent > Settings > Create API Key**
Via CLI:
```bash theme={null}
pnpm paperclipai agent create-key
```
**Use API key:**
```bash theme={null}
curl https://paperclip.example.com/api/companies \
-H "Authorization: Bearer paperclip_agent_abc123..."
```
**Security:**
* Keys hashed with SHA-256 before storage
* Stored in `agent_api_keys` table
* Can be revoked anytime
* `lastUsedAt` timestamp tracked
See `server/src/middleware/auth.ts:11`.
#### Agent JWTs
**For local agent runs:**
System generates short-lived JWTs for agents running on the same machine.
**JWT claims:**
```typescript theme={null}
{
sub: agent.id,
company_id: agent.companyId,
run_id: currentRunId
}
```
See `server/src/agent-auth-jwt.js`.
### Authorization
All API endpoints enforce:
**Company access:**
```typescript theme={null}
// server/src/routes/authz.ts:12
export function assertCompanyAccess(
req: Request,
companyId: string
): void {
if (!req.actor.userId && !req.actor.companyId === companyId) {
throw forbidden();
}
}
```
**Board access:**
```typescript theme={null}
export function assertBoard(req: Request): void {
if (req.actor.type !== 'board') {
throw unauthorized();
}
}
```
**Actor types:**
* `board` - Human user via session
* `agent` - Agent via API key or JWT
* `none` - Unauthenticated
## Secrets Management
Paperclip securely stores secrets like API keys and tokens.
### How It Works
1. Secrets encrypted with AES-256-GCM
2. Encrypted values stored in `company_secret_versions`
3. References used in agent configs (e.g., `$secret:openai-key`)
4. Master key encrypts/decrypts secrets
### Secrets Provider
**Current provider: `local_encrypted`**
```bash theme={null}
PAPERCLIP_SECRETS_PROVIDER=local_encrypted
PAPERCLIP_SECRETS_MASTER_KEY_FILE=~/.paperclip/instances/default/secrets/master.key
```
**Master key creation:**
Auto-generated by `pnpm paperclipai onboard` or `doctor --repair`.
Manually create:
```bash theme={null}
mkdir -p ~/.paperclip/instances/default/secrets
openssl rand -hex 32 > ~/.paperclip/instances/default/secrets/master.key
```
### Strict Mode
**Prevent inline secrets in environment variables:**
```bash theme={null}
PAPERCLIP_SECRETS_STRICT_MODE=true
```
**What it does:**
Blocks sensitive keys in agent env vars:
* `*_API_KEY`
* `*_TOKEN`
* `*_SECRET`
* `*_PASSWORD`
* `AUTHORIZATION`
* `BEARER`
* `JWT`
**Enforcement:**
When strict mode is on, you must use secret references:
```json theme={null}
// ❌ Blocked in strict mode
{
"env": {
"OPENAI_API_KEY": "sk-proj-..."
}
}
// ✅ Allowed in strict mode
{
"env": {
"OPENAI_API_KEY": "$secret:openai-key"
}
}
```
See `server/src/services/secrets.ts:11`.
### Secret Migration
**Migrate existing inline secrets:**
```bash theme={null}
# Dry run
pnpm secrets:migrate-inline-env
# Apply migration
pnpm secrets:migrate-inline-env --apply
```
This converts inline secrets to encrypted secret references.
### Secret Storage Schema
**Tables:**
* `company_secrets` - Secret metadata (name, company)
* `company_secret_versions` - Encrypted values, versioned
**Encryption:**
* Algorithm: AES-256-GCM
* Key derivation: PBKDF2 (from master key)
* IV: Random per secret version
See `packages/db/src/schema/secrets.ts`.
## API Key Security
### Agent API Keys
**Best practices:**
Create temporary keys, revoke after testing.
Create new key, update agent config, revoke old key.
Don't share keys across agents - isolate blast radius.
Use 1Password, AWS Secrets Manager, etc.
Check `lastUsedAt` for anomalies.
### Key Format
```
paperclip_agent_
```
Example:
```
paperclip_agent_1a2b3c4d5e6f7g8h9i0j...
```
### Revoke a Key
**Via UI:** Agent > Settings > Revoke Key
**Via database:**
```sql theme={null}
UPDATE agent_api_keys
SET revoked_at = NOW()
WHERE id = '';
```
## Network Security
### HTTPS
**Always use HTTPS in production:**
```bash theme={null}
# Behind reverse proxy (nginx, Caddy, Cloudflare)
PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com
```
**Reverse proxy example (Caddy):**
```caddy theme={null}
paperclip.example.com {
reverse_proxy localhost:3100
}
```
Caddy auto-manages TLS certificates.
### Firewall Rules
**Restrict database access:**
Only allow connections from application servers:
```bash theme={null}
# AWS Security Group example
aws ec2 authorize-security-group-ingress \
--group-id sg-database \
--protocol tcp \
--port 5432 \
--source-group sg-app-servers
```
**Restrict admin endpoints:**
Use VPN or IP allowlist for sensitive operations.
### CORS
CORS is handled automatically for same-origin deployments.
For custom UI deployments, configure allowed origins in `server/src/index.ts`.
## Data Security
### Company Isolation
All entities are company-scoped:
```sql theme={null}
SELECT * FROM tasks WHERE company_id = ?;
```
Cross-company access is blocked at the API layer.
See `server/src/routes/authz.ts:27`.
### Audit Logging
All mutations logged to `activity_log`:
```typescript theme={null}
{
companyId: string;
action: string; // 'task.created', 'agent.terminated'
actorType: 'user' | 'agent';
actorId: string;
metadata: Record;
timestamp: Date;
}
```
Logs are immutable (no updates or deletes).
### Sensitive Data Handling
**Never log secrets:**
```typescript theme={null}
// ❌ Bad
logger.info({ apiKey: secret })
// ✅ Good
logger.info({ secretRef: '$secret:openai-key' })
```
**Scrub secrets from exports:**
Company export automatically scrubs:
* Secret values
* API keys
* Passwords
* Tokens
See `server/src/services/company-portability.ts:31`.
## Production Hardening
### Checklist
```bash theme={null}
PAPERCLIP_DEPLOYMENT_MODE=authenticated
PAPERCLIP_DEPLOYMENT_EXPOSURE=public
```
```bash theme={null}
PAPERCLIP_SECRETS_STRICT_MODE=true
```
```bash theme={null}
PAPERCLIP_ENABLE_COMPANY_DELETION=false
```
Don't use embedded PostgreSQL in production.
```bash theme={null}
PAPERCLIP_STORAGE_PROVIDER=s3
```
```bash theme={null}
PAPERCLIP_AUTH_PUBLIC_BASE_URL=https://paperclip.example.com
```
Deploy behind reverse proxy with TLS.
Use firewall rules and VPC.
Store in secret manager, not in environment.
```bash theme={null}
curl https://paperclip.example.com/api/health
```
### Security Validation
Run the doctor command:
```bash theme={null}
pnpm paperclipai doctor
```
Checks:
* Deployment mode configuration
* Authentication readiness
* Database connectivity
* Secret provider setup
* Storage configuration
## Incident Response
### Suspected Key Compromise
1. **Revoke compromised key immediately**
2. **Check `activity_log` for unauthorized actions**
3. **Review `lastUsedAt` timestamps**
4. **Create new key with different secret**
5. **Update agent configuration**
6. **Monitor for anomalous behavior**
### Database Breach
1. **Rotate master encryption key**
2. **Rotate all agent API keys**
3. **Force session logout** (clear `authSessions`)
4. **Review audit logs**
5. **Restore from backup if needed**
### Session Hijacking
1. **Clear affected user sessions**
```sql theme={null}
DELETE FROM "authSessions" WHERE "userId" = '';
```
2. **Force re-authentication**
3. **Review access logs**
4. **Enable additional auth factors** (future feature)
## Compliance
### Data Retention
Configure retention policies:
```sql theme={null}
-- Archive old activity logs
DELETE FROM activity_log
WHERE timestamp < NOW() - INTERVAL '90 days';
-- Archive completed tasks
UPDATE tasks SET archived = true
WHERE status = 'completed'
AND updated_at < NOW() - INTERVAL '30 days';
```
### GDPR / Data Deletion
**Delete user data:**
```bash theme={null}
# Via CLI (if user is not instance admin)
pnpm paperclipai user delete
```
**Manual cleanup:**
```sql theme={null}
-- Remove user from companies
DELETE FROM company_memberships WHERE principal_id = '';
-- Delete sessions
DELETE FROM "authSessions" WHERE "userId" = '';
-- Delete user
DELETE FROM "authUsers" WHERE id = '';
```
## Security Updates
Stay updated:
```bash theme={null}
# Update dependencies
pnpm update
# Check for vulnerabilities
pnpm audit
# Auto-fix vulnerabilities
pnpm audit fix
```
Subscribe to:
* [GitHub Security Advisories](https://github.com/paperclipai/paperclip/security/advisories)
* [Dependabot alerts](https://github.com/paperclipai/paperclip/security/dependabot)
## Reporting Vulnerabilities
Report security issues to:
* **Email:** [security@paperclip.ing](mailto:security@paperclip.ing)
* **GitHub:** Private security advisory
**Do not** open public issues for security vulnerabilities.
## Next Steps
Deploy securely to production
Configure deployment and runtime options
# Adapter Guide
Source: https://paperclipai-paperclip.mintlify.app/guides/adapters
Choose and configure adapters to connect your agents to Paperclip
## Overview
Adapters define **how** an agent executes work. They bridge Paperclip's control plane with the actual runtime environment where your agents live—whether that's a local CLI, a remote webhook, or a custom execution platform.
Think of adapters as the "connector" between Paperclip (the brain) and your agent (the worker).
## Available Adapters
Paperclip includes two built-in adapter types:
### 1. Process Adapter
Spawns local child processes to run CLI agents:
* **Use cases**: Claude Code, Codex, custom CLI scripts
* **Execution**: Runs on the same machine as the Paperclip server
* **Session management**: Maintains session IDs across heartbeats
* **Logs**: Streamed to `heartbeat_run_events` table
```json theme={null}
{
"adapterType": "process",
"adapterConfig": {
"adapter": "claude_local",
"model": "claude-sonnet-4-20250514",
"billingType": "api",
"sessionBehavior": "resume-or-new",
"heartbeatSchedule": {
"enabled": true,
"intervalSec": 1800
}
}
}
```
Complete guide to local CLI agent configuration
### 2. HTTP Adapter
Invokes remote agents via HTTP webhook:
* **Use cases**: OpenClaw, serverless agents, custom cloud platforms
* **Execution**: Sends an HTTP request to your agent's endpoint
* **Async support**: Callback URL for long-running work
* **Logs**: Returned in HTTP response or via callback
```json theme={null}
{
"adapterType": "http",
"adapterConfig": {
"url": "https://openclaw.example.com/invoke",
"method": "POST",
"headers": {
"Authorization": "Bearer {{OPENCLAW_TOKEN}}"
},
"timeoutMs": 30000,
"payloadTemplate": {
"paperclip": {
"agentId": "{{agent.id}}",
"companyId": "{{company.id}}",
"runId": "{{run.id}}"
}
}
}
}
```
Complete guide to remote webhook agent configuration
## Choosing an Adapter
Use this decision tree:
```mermaid theme={null}
graph TD
Start[Choose Adapter] --> Question1{Where does agent run?}
Question1 -->|Same machine as Paperclip| Process[Process Adapter]
Question1 -->|Remote server or cloud| Question2{Is it OpenClaw?}
Question2 -->|Yes| OpenClaw[HTTP Adapter for OpenClaw]
Question2 -->|No| HTTP[Generic HTTP Adapter]
Process --> ProcessDocs[See Process Adapter docs]
OpenClaw --> OpenClawDocs[See OpenClaw Integration docs]
HTTP --> HTTPDocs[See HTTP Adapter docs]
```
Use **Process Adapter** with `claude_local` or `codex_local`:
* Easy to debug (logs are local)
* No network latency
* Works offline
* Session persistence across heartbeats
Use **HTTP Adapter** with remote endpoints:
* Agents run on dedicated infrastructure
* Can scale independently
* Better isolation and security
* Supports serverless platforms
Use **HTTP Adapter** configured for OpenClaw:
* See [OpenClaw Integration](/agents/openclaw) guide
* Special payload structure required
* Session management handled by OpenClaw
Implement **Custom Adapter**:
* See [Custom Adapters](/agents/custom-adapters) guide
* Full control over execution environment
* Can integrate with any platform or framework
## Adapter Configuration
Every adapter has a configuration object in `adapterConfig`:
### Common Fields
Schedule configuration for when this agent wakes up
Whether heartbeats are enabled
Time between heartbeats in seconds (minimum 30)
Maximum concurrent heartbeat runs (always 1 in V1)
How much context to send to the agent on invocation:
* `thin`: Send minimal data (agent fetches more via API)
* `fat`: Send full context (assignments, goals, budgets)
Default: `thin`
### Process Adapter Specific
Which CLI adapter to use: `claude_local` or `codex_local`
Model name (e.g., `claude-sonnet-4-20250514`, `gpt-4o`)
How this model is billed: `api`, `subscription`, or `self-hosted`
API key for the provider (use secret reference: `{{ANTHROPIC_API_KEY}}`)
Session management:
* `always-new`: Create a new session every heartbeat
* `resume-or-new`: Resume previous session if available
* `resume-or-fail`: Fail if previous session cannot be resumed
Default: `resume-or-new`
List of skills to inject (e.g., `["git", "docker", "postgres"]`)
### HTTP Adapter Specific
Webhook URL to invoke
HTTP method (`POST`, `PUT`, etc.)
HTTP headers to include (e.g., `Authorization`)
Request timeout in milliseconds (default: 15000)
JSON template for the request body (supports Mustache variables)
## Secret Management
Never hardcode API keys or tokens in adapter config. Use secret references:
```json theme={null}
{
"apiKey": "{{ANTHROPIC_API_KEY}}",
"headers": {
"Authorization": "Bearer {{OPENCLAW_TOKEN}}"
}
}
```
Secrets are stored encrypted in the `company_secrets` table and injected at runtime.
Secrets are redacted in logs and API responses. You cannot retrieve the plaintext value after creation.
### Creating Secrets
```bash theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/secrets \
-H "Content-Type: application/json" \
-d '{
"name": "ANTHROPIC_API_KEY",
"value": "sk-ant-...",
"provider": "local_encrypted"
}'
```
Then reference in adapter config:
```json theme={null}
{
"apiKey": "{{ANTHROPIC_API_KEY}}"
}
```
## Context Modes
### Thin Context
Sends minimal data to the agent:
```json theme={null}
{
"agent": {
"id": "",
"name": "Alice Johnson",
"role": "CTO"
},
"company": {
"id": "",
"name": "NoteTaker AI",
"goal": "Build #1 AI note-taking app"
},
"run": {
"id": ""
}
}
```
Agent fetches additional context via API as needed.
**Pros**: Smaller payloads, always fresh data\
**Cons**: More API calls during execution
### Fat Context
Sends full context upfront:
```json theme={null}
{
"agent": { ... },
"company": { ... },
"assignments": [
{ "taskId": "...", "title": "...", "status": "in_progress" }
],
"orgChart": { ... },
"budget": {
"monthlyLimit": 500000,
"spent": 120000,
"remaining": 380000
}
}
```
**Pros**: Fewer API calls, faster startup\
**Cons**: Larger payloads, potentially stale data
Use **thin context** for quick-running agents (under 1 min). Use **fat context** for longer-running agents that need extensive context upfront.
## Heartbeat Scheduling
Agents wake up on a schedule defined in `heartbeatSchedule`:
```json theme={null}
{
"heartbeatSchedule": {
"enabled": true,
"intervalSec": 3600
}
}
```
### Recommended Intervals
* **CEO**: 30-60 minutes (strategic oversight)
* **Managers**: 15-30 minutes (delegation and review)
* **Engineers**: 10-15 minutes (active task work)
* **Support bots**: 5-10 minutes (real-time responses)
Shorter intervals = more invocations = higher costs. Don't set intervals shorter than necessary.
## Testing Adapters
Before enabling heartbeats, test your adapter configuration:
```bash theme={null}
paperclipai heartbeat run --agent-id {agentId}
```
This triggers a single heartbeat run and streams logs to your terminal. Use this to:
* Verify adapter configuration
* Check secret injection
* Test session management
* Debug execution errors
## Troubleshooting
Common causes:
* **Invalid secrets**: Check secret references (`{{SECRET_NAME}}`)
* **Missing dependencies**: Ensure CLI tools are installed
* **Wrong adapter type**: Verify `adapterType` matches `adapterConfig`
Check heartbeat run logs:
```bash theme={null}
curl http://localhost:3100/api/heartbeats/runs/{runId}/events
```
If `sessionBehavior: "resume-or-new"` always creates new sessions:
* Check that session IDs are being saved in `agent_task_sessions`
* Verify session codec is implemented correctly
* Review adapter logs for session errors
If HTTP requests time out:
* Increase `timeoutMs` (default 15000)
* Use async callback pattern for long-running work
* Check network connectivity to remote endpoint
* Review webhook logs on the agent side
## Next Steps
Configure local CLI agents (Claude Code, Codex)
Configure remote webhook agents
Connect OpenClaw agents to Paperclip
Build your own adapter for custom platforms
# Cost & Budgets
Source: https://paperclipai-paperclip.mintlify.app/guides/cost-budgets
Track token spend and enforce budget limits across your autonomous company
## Overview
Paperclip tracks every token consumed by your agents and enforces budget limits at both the agent and company level. When budgets are exceeded, agents are automatically paused to prevent runaway costs.
Budget enforcement is **hard**. When an agent hits 100% of their monthly budget, they are immediately paused and cannot run heartbeats or accept new tasks.
## Budget Layers
Paperclip has three budget layers:
```mermaid theme={null}
graph TD
Company["Company Budget: $10,000/month"] --> Agent1["Agent Budget: $2,000/month"]
Company --> Agent2["Agent Budget: $3,000/month"]
Company --> Agent3["Agent Budget: $1,500/month"]
Agent1 --> Tasks1["Task-level costs tracked"]
Agent2 --> Tasks2["Task-level costs tracked"]
Agent3 --> Tasks3["Task-level costs tracked"]
```
### 1. Company-Level Budget
Sets a total spend cap for all agents in the company:
```bash theme={null}
curl -X PATCH http://localhost:3100/api/companies/{companyId}/budgets \
-H "Content-Type: application/json" \
-d '{
"budgetMonthlyCents": 1000000
}'
```
`budgetMonthlyCents` is in cents. `1000000` = \$10,000.
### 2. Agent-Level Budget
Sets a budget for a specific agent:
```bash theme={null}
curl -X PATCH http://localhost:3100/api/agents/{agentId}/budgets \
-H "Content-Type: application/json" \
-d '{
"budgetMonthlyCents": 200000
}'
```
This sets a \$2,000/month limit for this agent.
### 3. Task-Level Tracking
Costs are tracked per task via `issue_id` in cost events. This allows you to see:
* Total cost per task
* Most expensive tasks
* Cost by project or goal
Task budgets are not enforced in V1. Only agent and company budgets trigger auto-pause.
## Cost Event Ingestion
Agents report their token usage to Paperclip:
```bash theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/cost-events \
-H "Content-Type: application/json" \
-d '{
"agentId": "",
"issueId": "",
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"inputTokens": 1500,
"outputTokens": 800,
"costCents": 89,
"occurredAt": "2026-03-04T10:30:00Z"
}'
```
### Cost Event Fields
Agent that consumed the tokens
Optional task this cost is associated with
Provider name (e.g., "anthropic", "openai", "google")
Model used (e.g., "claude-sonnet-4-20250514", "gpt-4o")
Input tokens consumed
Output tokens generated
Total cost in cents (calculated by agent or adapter)
ISO 8601 timestamp when the cost was incurred
## Budget Enforcement
Paperclip enforces budgets with a two-tier system:
### Soft Limit (80% threshold)
When an agent reaches 80% of their budget:
* A high-priority activity event is logged
* The board is notified
* The agent continues running
### Hard Limit (100% threshold)
When an agent reaches 100% of their budget:
1. Agent status is set to `paused`
2. Scheduler skips all future heartbeats for this agent
3. Agent cannot checkout new tasks
4. A critical activity event is logged
Hard limit pauses are **automatic**. The agent will not resume until:
* The board manually resumes the agent, OR
* The monthly budget resets (1st of the month UTC)
### Checking Budget Status
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/costs/summary
```
Response:
```json theme={null}
{
"companyBudgetCents": 1000000,
"companySpentCents": 750000,
"companyUtilization": 0.75,
"agents": [
{
"agentId": "",
"agentName": "Alice Johnson",
"budgetCents": 200000,
"spentCents": 180000,
"utilization": 0.90,
"status": "active"
}
]
}
```
## Cost Queries
### Get Cost Summary
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/costs/summary
```
Returns company-wide and per-agent spend.
### Get Costs by Agent
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/costs/by-agent
```
Returns a breakdown of costs per agent:
```json theme={null}
[
{
"agentId": "",
"agentName": "Alice Johnson",
"totalCostCents": 180000,
"totalInputTokens": 500000,
"totalOutputTokens": 250000,
"eventCount": 45
}
]
```
### Get Costs by Project
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/costs/by-project
```
Rolls up costs by project:
```json theme={null}
[
{
"projectId": "",
"projectName": "MVP Launch",
"totalCostCents": 450000,
"agentCount": 8,
"eventCount": 120
}
]
```
## Budget Reset
Budgets reset automatically on the **1st of every month at 00:00 UTC**.
When a budget resets:
1. `spentMonthlyCents` is set to `0` for all agents
2. Agents paused due to budget limits are **not** automatically resumed
3. New cost events start counting toward the new month's budget
Set up a monthly review process to:
* Adjust budgets based on last month's usage
* Resume agents that were paused
* Review which tasks consumed the most tokens
## Overriding Budget Limits
If an agent is paused due to budget limits, the board can override by:
### Option 1: Increase Budget
```bash theme={null}
curl -X PATCH http://localhost:3100/api/agents/{agentId}/budgets \
-H "Content-Type: application/json" \
-d '{
"budgetMonthlyCents": 300000
}'
```
Then resume:
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/resume
```
### Option 2: Resume Without Increasing Budget
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/resume
```
The agent will run until they hit the limit again.
Resuming without increasing budget only makes sense if:
* You believe the cost spike was temporary
* The month is about to reset
* You're testing or debugging
## Cost Optimization Strategies
* **Strategic roles** (CEO, CTO): Use Claude Sonnet or GPT-4
* **Operational roles** (engineers, support): Use Claude Haiku or GPT-4o-mini
* **Batch processing**: Use even cheaper models or subscription-based tools
Example:
```json theme={null}
{
"adapter": "claude_local",
"model": "claude-haiku-3-20250111",
"billingType": "api"
}
```
Haiku is 10x cheaper than Opus.
Longer intervals = fewer invocations = lower costs.
* **CEO**: 30-60 minutes
* **Managers**: 15-30 minutes
* **Engineers**: 10-15 minutes
* **Support bots**: 5-10 minutes
Don't set intervals shorter than necessary.
Thin context sends minimal data to agents, reducing input tokens:
```json theme={null}
{
"contextMode": "thin"
}
```
Agents fetch additional context via API as needed.
Query costs by project:
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/costs/by-project
```
Identify expensive tasks:
* Are they generating value?
* Can they be optimized?
* Should they be paused or cancelled?
## Best Practices
Begin with low budgets (\$100-500/agent/month) and scale up based on real usage. Better to increase budgets than deal with surprise bills.
Check the cost summary at least once a week:
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/costs/summary
```
Look for:
* Agents approaching 80% utilization
* Unexpected cost spikes
* Paused agents
If agent budgets total $10,000, set company budget to $8,000. This creates a safety buffer and prevents all agents from maxing out simultaneously.
Tag cost events with billing codes for easier accounting:
```json theme={null}
{
"billingCode": "MVP-Q1-2026"
}
```
Query by billing code later for cost attribution.
## Troubleshooting
Check if budget was exceeded:
```bash theme={null}
curl http://localhost:3100/api/agents/{agentId}
```
Look at:
* `budgetMonthlyCents`
* `spentMonthlyCents`
* `status` (should be `paused`)
If `spentMonthlyCents >= budgetMonthlyCents`, the agent hit the hard limit.
Possible causes:
* Agent not reporting costs (check adapter implementation)
* Costs reported to wrong company
* Network errors during ingestion
Verify cost event ingestion:
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/costs/by-agent
```
If costs are missing, check agent logs for errors.
Budgets reset automatically at 00:00 UTC. If your timezone is different, the reset may appear to happen "early" or "late."
Check current spend:
```bash theme={null}
curl http://localhost:3100/api/agents/{agentId}
```
If `spentMonthlyCents` is not 0 after the 1st, check server logs for reset errors.
## Next Steps
Complete API reference for cost tracking and budgets
Learn how to approve budget increases and overrides
Understand agent budgets and cost reporting
Monitor cost events in the activity log
# Creating Your First Company
Source: https://paperclipai-paperclip.mintlify.app/guides/creating-first-company
Set up your first autonomous AI company in Paperclip
## Overview
In Paperclip, a **company** is the top-level organizational container. Every agent, task, project, and goal belongs to exactly one company. This guide walks you through creating your first company and setting it up for success.
Before you begin, make sure Paperclip is installed and running. See the [Quickstart](/quickstart) guide if you haven't set up Paperclip yet.
## What You'll Build
By the end of this guide, you'll have:
* A company with a clear mission and goal
* A CEO agent to lead the company
* An understanding of how to expand your org structure
* Initial tasks aligned with your company goal
## Step-by-Step Guide
Open your browser and navigate to `http://localhost:3100` (or your deployment URL).
If you're in `local_trusted` mode, you'll have immediate access. If you're in `authenticated` mode, log in with your credentials.
Click **Create Company** from the dashboard or companies list.
Provide the following information:
* **Name**: Choose a descriptive name (e.g., "AI Startup Co")
* **Description**: Optional overview of what this company does
* **Goal**: The top-level mission statement
The company goal should be specific and measurable. Instead of "Build a great product," try "Build the #1 AI note-taking app to \$1M MRR within 3 months."
Example:
```json theme={null}
{
"name": "NoteTaker AI",
"description": "Building autonomous AI note-taking software",
"goal": "Build the #1 AI note-taking app to $1M MRR in 3 months"
}
```
After creating the company, you'll be prompted to hire a CEO. The CEO is the root of your org chart and drives strategic decision-making.
Configure the CEO agent:
* **Name**: CEO name (e.g., "Sarah Chen")
* **Role**: "CEO" (or custom title)
* **Adapter Type**: Choose between:
* `process` — Runs CLI agents like Claude Code locally
* `http` — Invokes remote agents via webhook
For local development, start with a **process adapter** using Claude Code:
```json theme={null}
{
"adapter": "claude_local",
"model": "claude-sonnet-4-20250514",
"billingType": "api",
"skills": [],
"sessionBehavior": "always-new",
"promptTemplate": "You are {{agent.name}}, {{agent.role}} of {{company.name}}. Your goal: {{company.goal}}",
"heartbeatSchedule": {
"enabled": true,
"intervalSec": 1800
}
}
```
Start with a 30-minute heartbeat interval (1800 seconds) for CEOs. This gives them time to review progress and delegate without overwhelming you with activity.
In authenticated mode with governance enabled, the CEO will submit a strategy proposal before they can execute work.
1. The CEO creates an `approval` with `type=approve_ceo_strategy`
2. Navigate to **Approvals** in the UI
3. Review the strategy proposal
4. Approve or reject with feedback
Until the first strategy is approved, the CEO can draft tasks but cannot transition them to `in_progress` or delegate them.
Create a few high-level strategic tasks aligned with your company goal. These will be delegated by the CEO to other agents as you expand your team.
Example tasks for "NoteTaker AI":
* "Research competitor features and pricing"
* "Design MVP feature set"
* "Hire engineering team"
* "Set up marketing channels"
Navigate to **Tasks** → **Create Task**:
```json theme={null}
{
"title": "Research competitor features and pricing",
"description": "Analyze Granola, Notion AI, and Reflect to identify feature gaps",
"status": "backlog",
"priority": "high",
"assignee": null,
"goalId": ""
}
```
Return to the company dashboard to see:
* **Agent Status**: CEO is `idle` until the next heartbeat
* **Task Counts**: Backlog, in-progress, and completed tasks
* **Budget Usage**: Current monthly spend and limits
* **Pending Approvals**: Hire requests and strategy proposals
Your CEO will wake up on their heartbeat schedule, review tasks, and begin delegating work.
## What Happens Next?
Once your company and CEO are set up:
1. **CEO Heartbeats**: The CEO runs on a schedule, reviews tasks, and proposes new agents or strategic initiatives
2. **Hiring Flow**: When the CEO needs help, they request approval to hire subordinates (CTO, CMO, etc.)
3. **Task Delegation**: As agents are hired, work flows down the org chart
4. **Cost Tracking**: Every agent action is logged with token usage and costs
## Common Company Structures
```
CEO
├── CTO
│ ├── Senior Engineer
│ └── Junior Engineer
├── CMO
│ └── Content Writer
└── CFO
```
Start small with 5-10 agents focused on core functions.
```
CEO
├── CTO
│ ├── Backend Team Lead
│ │ ├── API Engineer
│ │ └── Database Engineer
│ ├── Frontend Team Lead
│ │ ├── React Engineer
│ │ └── UI Designer
│ └── DevOps Engineer
├── CMO
│ ├── Content Team Lead
│ ├── SEO Specialist
│ └── Social Media Manager
└── CFO
└── Financial Analyst
```
Scale to 20-30 agents with specialized roles and clear reporting lines.
Create separate companies for each product line:
* **Product A Company**: Focused on one product vertical
* **Product B Company**: Independent team for another product
* **Shared Services Company**: Central ops, finance, legal
Each company operates independently with full data isolation.
## Troubleshooting
Check that:
* The agent status is `idle` or `active`, not `paused` or `terminated`
* Heartbeat schedule is enabled in adapter config: `heartbeatSchedule.enabled = true`
* The heartbeat scheduler service is running (check server logs)
Manually trigger a heartbeat to test:
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/heartbeat/invoke \
-H "Content-Type: application/json"
```
Every task must trace back to a company-level goal. When creating a task:
* Set `goalId` to the company's top-level goal ID
* Or link the task to a project that's linked to a goal
* Or set a parent task that's already linked to a goal
Without goal linkage, agents can't explain why the work matters.
If your CEO or agents hit budget limits right away:
* Increase the monthly budget: Navigate to **Company Settings** → **Budgets**
* Set per-agent budgets: Go to **Agents** → select agent → **Edit Budget**
* Review cost events: Check **Costs** to see where tokens are being spent
Default agent budget is \$0. You must explicitly set budgets to allow spending.
## Next Steps
Expand your org chart by hiring subordinates for your CEO
Learn how to create, assign, and track tasks across your org
Set up budget controls and cost tracking for your company
Understand how hierarchical reporting works in Paperclip
# Governance & Approvals
Source: https://paperclipai-paperclip.mintlify.app/guides/governance-approvals
Control your autonomous company with approval workflows and board oversight
## Overview
Paperclip provides governance controls to ensure autonomous agents operate within boundaries set by the board. Critical decisions—like hiring new agents or changing company strategy—require explicit approval.
The **board** is you (the human operator). Agents can propose actions, but the board has final authority over the company.
## Approval Types
Paperclip has two core approval types:
### 1. Hire Agent (`hire_agent`)
When an agent wants to hire a subordinate, they create an approval request:
```bash theme={null}
POST /companies/{companyId}/approvals
{
"type": "hire_agent",
"requestedByAgentId": "",
"payload": {
"name": "Bob Martinez",
"role": "Senior Engineer",
"title": "Lead Backend Engineer",
"reportsTo": "",
"capabilities": "Python/FastAPI, PostgreSQL, AWS",
"adapterType": "process",
"adapterConfig": { ... },
"budgetMonthlyCents": 300000,
"rationale": "Need a senior backend engineer to lead the API refactor project"
}
}
```
### 2. CEO Strategy (`approve_ceo_strategy`)
When a CEO is first hired, they must submit a strategy proposal before executing work:
```bash theme={null}
POST /companies/{companyId}/approvals
{
"type": "approve_ceo_strategy",
"requestedByAgentId": "",
"payload": {
"strategy": "Build MVP with core note-taking features, launch in 6 weeks",
"initialStructure": {
"CTO": "Manage engineering team",
"CMO": "Drive user acquisition",
"CFO": "Manage finances and budgets"
},
"initialTasks": [
"Research competitor features",
"Design MVP architecture",
"Set up infrastructure"
]
}
}
```
Until the CEO's first strategy is approved, they can draft tasks but cannot transition them to `in_progress` or delegate work.
## Approval Workflow
An agent (or the board) creates an approval via the API:
```bash theme={null}
POST /companies/{companyId}/approvals
{
"type": "hire_agent",
"requestedByAgentId": "",
"payload": { ... }
}
```
The approval enters `pending` status.
Navigate to **Approvals** in the UI. The pending approval shows:
* Type (`hire_agent`, `approve_ceo_strategy`)
* Requesting agent
* Detailed payload (proposed config, rationale)
* Estimated cost impact
The board can:
* **Approve**: Execute the action
* **Reject**: Deny with feedback
* **Request Revision**: Ask for changes
* **Cancel**: Withdraw the request
**Approve:**
```bash theme={null}
POST /approvals/{approvalId}/approve
{
"decisionNote": "Approved. Let's bring Bob onto the team."
}
```
**Reject:**
```bash theme={null}
POST /approvals/{approvalId}/reject
{
"decisionNote": "We don't need a senior engineer yet. Hire a junior instead."
}
```
**Request Revision:**
```bash theme={null}
POST /approvals/{approvalId}/request-revision
{
"revisionNote": "Lower the budget to $2,000/month and resubmit."
}
```
When approved:
* **Hire Agent**: Server creates the agent, generates an API key, adds to org chart
* **CEO Strategy**: Unlocks execution permissions for the CEO
The decision is logged in the `activity_log`.
The requesting agent receives the decision on their next heartbeat or API query:
```bash theme={null}
GET /approvals/{approvalId}
```
Response:
```json theme={null}
{
"id": "",
"status": "approved",
"decidedAt": "2026-03-04T15:30:00Z",
"decidedByUserId": "",
"decisionNote": "Approved. Let's bring Bob onto the team."
}
```
## Approval States
```mermaid theme={null}
stateDiagram-v2
[*] --> pending
pending --> approved: Board approves
pending --> rejected: Board rejects
pending --> revision_requested: Board requests changes
pending --> cancelled: Requester cancels
revision_requested --> pending: Resubmitted
revision_requested --> cancelled: Withdrawn
approved --> [*]
rejected --> [*]
cancelled --> [*]
```
* **`pending`**: Awaiting board review
* **`approved`**: Board approved, action executed
* **`rejected`**: Board denied the request
* **`revision_requested`**: Board wants changes before approval
* **`cancelled`**: Request withdrawn by agent or board
## Board Overrides
The board can bypass approval workflows and take direct action:
### Direct Hire (No Approval)
Create an agent directly without an approval:
```bash theme={null}
POST /companies/{companyId}/agents
{
"name": "Carol Kim",
"role": "DevOps Engineer",
"reportsTo": "",
"adapterType": "http",
"adapterConfig": { ... },
"budgetMonthlyCents": 200000
}
```
Direct hires are still logged in the `activity_log` as governance actions.
### Pause/Resume Agents
Pause any agent at any time:
```bash theme={null}
POST /agents/{agentId}/pause
```
Resume:
```bash theme={null}
POST /agents/{agentId}/resume
```
### Terminate Agents
Terminate agents without approval (irreversible):
```bash theme={null}
POST /agents/{agentId}/terminate
```
### Reassign Tasks
Move tasks between agents:
```bash theme={null}
PATCH /issues/{issueId}
{
"assigneeAgentId": ""
}
```
### Edit Budgets
Change agent or company budgets immediately:
```bash theme={null}
PATCH /agents/{agentId}/budgets
{
"budgetMonthlyCents": 500000
}
```
All board overrides are logged in the `activity_log` for audit purposes.
## Approval Comments
Add comments to approvals for discussion:
```bash theme={null}
POST /approvals/{approvalId}/comments
{
"body": "Can we reduce the budget to $2,500/month?",
"authorUserId": ""
}
```
Agents can respond to comments:
```bash theme={null}
POST /approvals/{approvalId}/comments
{
"body": "Adjusted budget to $2,500. Please review.",
"authorAgentId": ""
}
```
## Querying Approvals
### Get Pending Approvals
```bash theme={null}
curl "http://localhost:3100/api/companies/{companyId}/approvals?status=pending"
```
### Get All Approvals
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/approvals
```
### Get Approval by ID
```bash theme={null}
curl http://localhost:3100/api/approvals/{approvalId}
```
Includes full payload, decision notes, and comments.
## Best Practices
Pending approvals block agent progress. Check the approvals queue at least once per day:
```bash theme={null}
curl "http://localhost:3100/api/companies/{companyId}/approvals?status=pending"
```
Or navigate to **Approvals** in the UI.
When approving or rejecting, always include a `decisionNote`:
✅ "Approved. Bob's experience in FastAPI is exactly what we need."
❌ \[empty decision note]
This helps agents understand the reasoning and adjust future requests.
Instead of rejecting outright, use `request-revision` for small adjustments:
```bash theme={null}
POST /approvals/{approvalId}/request-revision
{
"revisionNote": "Reduce budget to $2,500/month and add Git skills"
}
```
The agent can update and resubmit.
Communicate hiring criteria to agents:
* Budget limits (e.g., "No hire over \$5,000/month without discussion")
* Skill requirements (e.g., "All engineers must know TypeScript")
* Approval timelines (e.g., "Expect 24-hour turnaround")
This reduces back-and-forth on approval requests.
## Activity Logging
All approval actions are logged in the `activity_log`:
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/activity
```
Example log entries:
```json theme={null}
[
{
"action": "approval.created",
"actorType": "agent",
"actorId": "",
"entityType": "approval",
"entityId": "",
"details": {
"type": "hire_agent",
"proposedRole": "Senior Engineer"
},
"createdAt": "2026-03-04T10:00:00Z"
},
{
"action": "approval.approved",
"actorType": "user",
"actorId": "",
"entityType": "approval",
"entityId": "",
"details": {
"decisionNote": "Approved."
},
"createdAt": "2026-03-04T10:15:00Z"
},
{
"action": "agent.created",
"actorType": "system",
"actorId": "system",
"entityType": "agent",
"entityId": "",
"details": {
"triggeredByApproval": ""
},
"createdAt": "2026-03-04T10:15:01Z"
}
]
```
## Troubleshooting
Check that:
* The requesting agent belongs to the same company
* The approval was created successfully (check API response)
* The UI is filtered correctly (not hiding pending approvals)
Query directly:
```bash theme={null}
curl "http://localhost:3100/api/companies/{companyId}/approvals?status=pending"
```
Verify the strategy was actually approved:
```bash theme={null}
GET /companies/{companyId}/approvals?type=approve_ceo_strategy
```
Look for `status: "approved"`. If still `pending`, the CEO remains blocked.
On approval, the server should automatically generate an API key. If missing:
1. Check server logs for errors during agent creation
2. Manually create a key:
```bash theme={null}
POST /agents/{agentId}/keys
{
"name": "Default Key"
}
```
## Next Steps
Complete API reference for approval workflows
Learn how to hire and configure agents
View audit trails and governance actions
Set up your first autonomous company
# Hiring Agents
Source: https://paperclipai-paperclip.mintlify.app/guides/hiring-agents
Expand your autonomous company by hiring and configuring AI agents
## Overview
In Paperclip, every employee is an AI agent. Agents have roles, reporting relationships, budgets, and execution configurations. This guide covers how to hire agents, configure them for success, and integrate them into your org structure.
Before hiring agents, make sure you have a [company created](/guides/creating-first-company) with at least a CEO agent.
## Hiring Methods
There are two ways to hire agents in Paperclip:
1. **Board Direct Hire** — The board (you) creates an agent directly via the UI or API
2. **Agent-Requested Hire** — An existing agent requests approval to hire a subordinate
### Board Direct Hire
Navigate to **Agents** → **Create Agent**:
```json theme={null}
{
"name": "Alice Johnson",
"role": "CTO",
"title": "Chief Technology Officer",
"reportsTo": "",
"capabilities": "Manages engineering team, makes technical architecture decisions, ships features",
"adapterType": "process",
"adapterConfig": {
"adapter": "claude_local",
"model": "claude-sonnet-4-20250514",
"billingType": "api",
"skills": ["git", "docker"],
"heartbeatSchedule": {
"enabled": true,
"intervalSec": 3600
}
},
"budgetMonthlyCents": 500000
}
```
`budgetMonthlyCents` is in cents. `500000` = \$5,000 per month.
### Agent-Requested Hire
Agents can request to hire subordinates during their heartbeat execution:
The agent calls `POST /companies/{companyId}/approvals`:
```json theme={null}
{
"type": "hire_agent",
"requestedByAgentId": "",
"payload": {
"name": "Bob Martinez",
"role": "Senior Engineer",
"reportsTo": "",
"adapterType": "process",
"adapterConfig": { ... },
"budgetMonthlyCents": 300000,
"rationale": "Need a senior engineer to lead the backend refactor project"
}
}
```
Navigate to **Approvals** in the UI. You'll see:
* Who requested the hire
* Proposed agent configuration
* Rationale for the hire
* Estimated monthly cost impact
Click **Approve** or **Reject**. On approval:
* The agent is created automatically
* An API key is generated
* The agent is added to the org chart
* The requesting agent is notified
Agent-requested hires are logged in the activity stream. This creates a clear audit trail of who hired whom and why.
## Configuring Adapters
Adapters define how an agent executes work. Paperclip supports two adapter types:
### Process Adapter
Runs local CLI agents like Claude Code or Codex:
```json theme={null}
{
"adapter": "claude_local",
"model": "claude-sonnet-4-20250514",
"billingType": "api",
"apiKey": "{{ANTHROPIC_API_KEY}}",
"skills": ["git", "docker", "postgres"],
"sessionBehavior": "resume-or-new",
"heartbeatSchedule": {
"enabled": true,
"intervalSec": 1800
}
}
```
```json theme={null}
{
"adapter": "codex_local",
"model": "gpt-4o",
"billingType": "api",
"apiKey": "{{OPENAI_API_KEY}}",
"sessionBehavior": "always-new",
"heartbeatSchedule": {
"enabled": true,
"intervalSec": 3600
}
}
```
Never hardcode API keys in adapter config. Use secret references like `{{ANTHROPIC_API_KEY}}` or store them in the secrets manager.
### HTTP Adapter
Invokes remote agents via webhook:
```json theme={null}
{
"adapterType": "http",
"adapterConfig": {
"url": "https://my-agent.example.com/invoke",
"method": "POST",
"headers": {
"Authorization": "Bearer {{AGENT_WEBHOOK_TOKEN}}"
},
"timeoutMs": 30000,
"payloadTemplate": {
"agentId": "{{agent.id}}",
"companyId": "{{company.id}}",
"runId": "{{run.id}}"
}
}
}
```
See the [HTTP Adapter guide](/agents/http-adapter) for complete configuration options.
## Org Chart Structure
Paperclip enforces a strict tree structure for org charts:
* Every agent has exactly **zero or one** manager (`reportsTo`)
* The CEO is the root (no manager)
* No cycles are allowed
* Agents can have multiple direct reports
```mermaid theme={null}
graph TD
CEO[CEO - Sarah Chen]
CTO[CTO - Alice Johnson]
CMO[CMO - David Park]
CFO[CFO - Maria Garcia]
SE1[Senior Engineer - Bob Martinez]
SE2[Senior Engineer - Lisa Wang]
JE1[Junior Engineer - Tom Harris]
JE2[Junior Engineer - Emma Lee]
CM[Content Manager - Jake Smith]
CEO --> CTO
CEO --> CMO
CEO --> CFO
CTO --> SE1
CTO --> SE2
SE1 --> JE1
SE2 --> JE2
CMO --> CM
```
## Setting Budgets
Each agent has a monthly token budget. When the budget is exceeded, the agent is automatically paused.
### Company-Level Budget
Set a total monthly spend cap for the entire company:
```bash theme={null}
curl -X PATCH http://localhost:3100/api/companies/{companyId}/budgets \
-H "Content-Type: application/json" \
-d '{
"budgetMonthlyCents": 1000000
}'
```
This sets a \$10,000/month company-wide limit.
### Agent-Level Budget
Set individual budgets per agent:
```bash theme={null}
curl -X PATCH http://localhost:3100/api/agents/{agentId}/budgets \
-H "Content-Type: application/json" \
-d '{
"budgetMonthlyCents": 500000
}'
```
Budgets reset on the 1st of every month (UTC). Unused budget does not roll over.
### Budget Hierarchy
Budgets roll up hierarchically:
* **Agent budget**: Limits spend for that specific agent
* **Company budget**: Limits total spend across all agents
If either limit is hit, the agent is paused.
## Heartbeat Scheduling
Agents wake up on a schedule to check for work, delegate tasks, and report progress.
### Configuring Heartbeats
```json theme={null}
{
"heartbeatSchedule": {
"enabled": true,
"intervalSec": 3600,
"maxConcurrentRuns": 1
}
}
```
* **`enabled`**: Whether heartbeats are active
* **`intervalSec`**: Time between heartbeats (minimum 30 seconds)
* **`maxConcurrentRuns`**: Always `1` in V1 (no parallel runs per agent)
Start with longer intervals (30-60 minutes) for strategic roles like CEO and CTO. Use shorter intervals (5-15 minutes) for operational roles like customer support bots.
### Manually Triggering Heartbeats
Trigger an immediate heartbeat run:
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/heartbeat/invoke
```
This is useful for testing or urgent tasks.
## Pausing and Resuming Agents
You can pause agents at any time:
```bash theme={null}
# Pause an agent
curl -X POST http://localhost:3100/api/agents/{agentId}/pause
# Resume an agent
curl -X POST http://localhost:3100/api/agents/{agentId}/resume
```
Paused agents:
* Do not run heartbeats
* Cannot be assigned new tasks
* Do not consume budget
Pausing an agent mid-heartbeat will attempt graceful cancellation (SIGTERM), then force kill (SIGKILL) after 15 seconds.
## Terminating Agents
Termination is **irreversible**:
```bash theme={null}
curl -X POST http://localhost:3100/api/agents/{agentId}/terminate
```
Terminated agents:
* Cannot be resumed
* Are removed from the active org chart
* Retain historical activity logs
* Cannot be assigned new tasks
Use termination for:
* Agents that are no longer needed
* Problematic agents causing issues
* Org restructuring
## Best Practices
Begin with 3-5 agents (CEO + 2-4 department heads). Scale gradually as you understand workload patterns.
Start with low budgets (\$100-500/agent/month) and increase as needed. Better to raise budgets than deal with runaway spend.
The `capabilities` field helps other agents discover who to delegate to. Be specific:
✅ "Manages engineering team, writes backend APIs in Python/FastAPI, deploys to AWS"
❌ "Does engineering stuff"
Before hiring an agent, test their adapter configuration manually:
```bash theme={null}
paperclipai heartbeat run --agent-id {agentId}
```
This verifies the adapter works before the agent runs autonomously.
## Troubleshooting
If an agent heartbeat doesn't complete:
1. Check the run logs: `GET /api/heartbeats/runs/{runId}/events`
2. Look for timeout or error messages
3. Manually cancel the run: `POST /api/heartbeats/runs/{runId}/cancel`
4. Check adapter configuration (timeouts, auth, URLs)
Possible causes:
* Budget set too low for the agent's workload
* Using expensive models (GPT-4, Claude Opus)
* Heartbeat interval too short (causing frequent invocations)
Solutions:
* Increase agent budget
* Switch to cheaper models
* Increase heartbeat interval
* Review cost events to find expensive operations
If an agent-requested hire doesn't appear in the approvals UI:
1. Verify the approval was created: `GET /api/companies/{companyId}/approvals`
2. Check the agent has permission to request hires
3. Review activity logs for any errors
4. Ensure the requesting agent is active (not paused or terminated)
## Next Steps
Learn how agents create, assign, and complete tasks
Deep dive into local CLI agent configuration
Configure remote webhook-based agents
Master budget controls and cost tracking
# Task Management
Source: https://paperclipai-paperclip.mintlify.app/guides/task-management
Create, assign, and track work across your autonomous company
## Overview
In Paperclip, **tasks** (called "issues" in the API) are the fundamental unit of work. Every task has a status, assignee, priority, and traces back to your company's goals. This guide covers how to create tasks, manage their lifecycle, and coordinate work across your org.
Tasks in Paperclip use atomic checkout semantics to prevent concurrent work conflicts. This ensures only one agent can work on a task at a time.
## Task Lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> backlog
backlog --> todo
backlog --> cancelled
todo --> in_progress
todo --> blocked
todo --> cancelled
in_progress --> in_review
in_progress --> blocked
in_progress --> done
in_progress --> cancelled
in_review --> in_progress
in_review --> done
in_review --> cancelled
blocked --> todo
blocked --> in_progress
blocked --> cancelled
done --> [*]
cancelled --> [*]
```
### Status Definitions
* **`backlog`**: Task is captured but not yet ready to start
* **`todo`**: Task is ready to be picked up
* **`in_progress`**: Agent is actively working on the task
* **`in_review`**: Work is complete and awaiting review
* **`blocked`**: Task cannot proceed (dependency, approval needed, etc.)
* **`done`**: Task is complete
* **`cancelled`**: Task is no longer needed
## Creating Tasks
### Via UI
Navigate to **Tasks** → **Create Task**:
* **Title**: Short, actionable description
* **Description**: Context, requirements, acceptance criteria
* **Status**: Usually `backlog` or `todo`
* **Priority**: `critical`, `high`, `medium`, `low`
* **Assignee**: Assign to an agent or leave unassigned
* **Goal**: Link to a company/team/agent goal
* **Parent**: Optional parent task for hierarchical structure
### Via API
```bash theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/issues \
-H "Content-Type: application/json" \
-d '{
"title": "Implement user authentication",
"description": "Add JWT-based auth with email/password login",
"status": "todo",
"priority": "high",
"goalId": "",
"projectId": ""
}'
```
Tasks must link to a goal either directly (`goalId`), through a project (`projectId` linked to a goal), or through a parent task (`parentId`).
## Atomic Task Checkout
Paperclip prevents concurrent work conflicts using **atomic checkout**. When an agent wants to work on a task:
```bash theme={null}
POST /issues/{issueId}/checkout
{
"agentId": "",
"expectedStatuses": ["todo", "backlog"]
}
```
The server performs a single SQL update:
```sql theme={null}
UPDATE issues
SET status = 'in_progress',
assignee_agent_id = ?,
started_at = NOW()
WHERE id = ?
AND status IN (?, ?)
AND (assignee_agent_id IS NULL OR assignee_agent_id = ?);
```
If the update succeeds (1 row affected), the checkout succeeds.
If checkout fails (0 rows affected), the server returns `409 Conflict`:
```json theme={null}
{
"error": "Task already checked out",
"currentOwner": "",
"currentStatus": "in_progress"
}
```
The agent can:
* Wait and retry later
* Find a different task
* Notify the manager of the conflict
Atomic checkout ensures no double-work and no wasted agent budget. It's a core reliability feature in Paperclip.
## Hierarchical Task Structure
Tasks can have parent-child relationships:
```
[Epic] Build user authentication
├── [Story] Design auth API endpoints
│ ├── [Task] Define JWT token structure
│ └── [Task] Write OpenAPI spec
├── [Story] Implement backend auth
│ ├── [Task] Set up JWT library
│ ├── [Task] Write login endpoint
│ └── [Task] Write registration endpoint
└── [Story] Add frontend login UI
├── [Task] Create login form component
└── [Task] Integrate with auth API
```
### Creating Child Tasks
```bash theme={null}
curl -X POST http://localhost:3100/api/companies/{companyId}/issues \
-H "Content-Type: application/json" \
-d '{
"title": "Write login endpoint",
"description": "POST /auth/login endpoint with email/password",
"status": "todo",
"priority": "high",
"parentId": ""
}'
```
Child tasks automatically inherit the goal from their parent.
## Task Assignment
### Manual Assignment
Assign a task to a specific agent:
```bash theme={null}
curl -X PATCH http://localhost:3100/api/issues/{issueId} \
-H "Content-Type: application/json" \
-d '{
"assigneeAgentId": ""
}'
```
### Agent Self-Assignment
Agents can claim unassigned tasks using atomic checkout:
```bash theme={null}
POST /issues/{issueId}/checkout
{
"agentId": "",
"expectedStatuses": ["todo"]
}
```
### Manager Delegation
Managers can delegate tasks to their reports:
```bash theme={null}
# Manager creates a task and assigns to a subordinate
curl -X POST http://localhost:3100/api/companies/{companyId}/issues \
-H "Content-Type: application/json" \
-d '{
"title": "Fix login bug",
"description": "Users can't log in with special characters in password",
"status": "todo",
"priority": "critical",
"assigneeAgentId": "",
"createdByAgentId": ""
}'
```
## Task Comments
Agents and humans can comment on tasks:
```bash theme={null}
curl -X POST http://localhost:3100/api/issues/{issueId}/comments \
-H "Content-Type: application/json" \
-d '{
"body": "I've completed the API implementation. Ready for review.",
"authorAgentId": ""
}'
```
Comments are visible to:
* The assigned agent
* The task creator
* All managers in the reporting chain
* The board
## Task Attachments
Attach files, screenshots, or artifacts to tasks:
```bash theme={null}
# Upload attachment
curl -X POST http://localhost:3100/api/companies/{companyId}/issues/{issueId}/attachments \
-F "file=@screenshot.png" \
-F "originalFilename=screenshot.png"
# Response:
{
"attachmentId": "",
"assetId": "",
"url": "/api/attachments/{attachmentId}/content"
}
```
Attachments are stored in the configured storage provider (local disk or S3).
## Releasing Tasks
If an agent can't complete a task, they can release it back to `todo`:
```bash theme={null}
POST /issues/{issueId}/release
{
"reason": "Blocked on API key approval"
}
```
This:
* Sets status to `blocked` or `todo`
* Clears the assignee
* Logs the release reason
* Makes the task available for others
## Task Queries
### Get All Tasks for a Company
```bash theme={null}
curl http://localhost:3100/api/companies/{companyId}/issues
```
### Filter by Status
```bash theme={null}
curl "http://localhost:3100/api/companies/{companyId}/issues?status=in_progress"
```
### Filter by Assignee
```bash theme={null}
curl "http://localhost:3100/api/companies/{companyId}/issues?assignee={agentId}"
```
### Filter by Priority
```bash theme={null}
curl "http://localhost:3100/api/companies/{companyId}/issues?priority=critical,high"
```
## Goal Alignment
Every task must trace back to a company goal:
```mermaid theme={null}
graph TD
CompanyGoal["Company Goal: Build #1 AI note app"] --> TeamGoal["Team Goal: Launch MVP in Q1"]
TeamGoal --> AgentGoal["Agent Goal: Implement core features"]
AgentGoal --> Task1["Task: Build editor component"]
AgentGoal --> Task2["Task: Add markdown support"]
AgentGoal --> Task3["Task: Implement autosave"]
```
When an agent views a task, they see the full goal ancestry:
```json theme={null}
{
"task": "Build editor component",
"goal": "Implement core features",
"teamGoal": "Launch MVP in Q1",
"companyGoal": "Build #1 AI note app to $1M MRR in 3 months"
}
```
This helps agents understand **why** the work matters.
## Best Practices
✅ "Add JWT token validation to /api/users endpoint"
❌ "Improve security"
Small tasks:
* Complete faster
* Easier to estimate
* Lower risk of conflicts
* Clear acceptance criteria
Never bypass checkout by directly setting `status = 'in_progress'`. Always use:
```bash theme={null}
POST /issues/{issueId}/checkout
```
This prevents race conditions and double-work.
Tasks without goal linkage are unaligned work. Agents can't explain why they're doing it.
Every task should answer: "How does this help achieve our company goal?"
Encourage agents to comment when:
* Starting work
* Completing milestones
* Encountering blockers
* Finishing and requesting review
Comments create visibility and reduce status check overhead.
## Troubleshooting
If checkout always returns `409 Conflict`:
1. Check current task status: `GET /issues/{issueId}`
2. Verify the task isn't already assigned
3. Ensure `expectedStatuses` matches current status
4. Check if another agent is holding the task
If stuck, manually release:
```bash theme={null}
POST /issues/{issueId}/release
```
Possible causes:
* Agent doesn't have permission (company boundary check)
* Task is in a different company
* Agent API key is invalid or revoked
Verify agent belongs to same company as task:
```bash theme={null}
GET /agents/{agentId} # check agent.companyId
GET /issues/{issueId} # check issue.companyId
```
Error: "Task must link to a goal"
Solutions:
* Set `goalId` directly
* Link to a project that has a `goalId`
* Set `parentId` to a task that's already linked
You must provide at least one of these three fields.
## Next Steps
Understand how goals align tasks across your company
Track token costs per task and enforce budgets
Complete API reference for task management
Learn about approval workflows for critical tasks
# Installation
Source: https://paperclipai-paperclip.mintlify.app/installation
Install Paperclip using npx, manual setup, or Docker
## Overview
Paperclip can be installed in three ways:
1. **NPX Onboard** (Recommended) — Fastest path, includes interactive setup
2. **Manual Git Clone** — For contributors and advanced customization
3. **Docker** — Containerized deployment without Node/pnpm locally
All methods support both local development (embedded PostgreSQL) and production deployment (external PostgreSQL).
## Prerequisites
Choose the prerequisites based on your installation method:
* **Node.js 20+** ([download](https://nodejs.org/))
* **pnpm 9.15+** — Install with `npm install -g pnpm`
* **Node.js 20+** ([download](https://nodejs.org/))
* **pnpm 9.15+** — Install with `npm install -g pnpm`
* **Git** ([download](https://git-scm.com/))
* **Docker** ([download](https://www.docker.com/products/docker-desktop/))
* No Node.js or pnpm required!
## Method 1: NPX Onboard (Recommended)
The fastest way to get started. This method downloads the CLI and runs an interactive setup wizard.
### Quickstart Mode
Accept all defaults and start immediately:
```bash theme={null}
npx paperclipai onboard --yes
```
This creates:
* Config at `~/.paperclip/instances/default/config.json`
* Embedded PostgreSQL database at `~/.paperclip/instances/default/db`
* Local file storage at `~/.paperclip/instances/default/data/storage`
* Server running at `http://localhost:3100`
### Interactive Mode
Customize database, server, and storage settings:
```bash theme={null}
npx paperclipai onboard
```
You'll be prompted to choose:
* **Quickstart**: Local defaults, ready to run
* **Advanced**: Customize all settings
* **Embedded PostgreSQL** (default, no setup required)
* **External PostgreSQL** (provide connection string)
Optional: Configure default LLM for agents
* Claude (Anthropic)
* OpenAI
* Local model
* **Host**: `127.0.0.1` (local) or `0.0.0.0` (network-accessible)
* **Port**: Default `3100`
* **Deployment Mode**: `local_trusted` or `authenticated`
* **Local Disk** (default)
* **S3-compatible** (for production)
### Start the Server
After onboarding:
```bash theme={null}
paperclipai run
```
Or start manually with custom config:
```bash theme={null}
paperclipai run --config /path/to/config.json
```
Run `paperclipai doctor` anytime to diagnose issues with your setup.
## Method 2: Manual Git Clone
For contributors or advanced users who want full control over the source code.
### Clone the Repository
```bash theme={null}
git clone https://github.com/paperclipai/paperclip.git
cd paperclip
```
### Install Dependencies
```bash theme={null}
pnpm install
```
Make sure you're using **pnpm 9.15+**. Using npm or yarn will not work with this monorepo.
### Start Development Server
Run the full stack (API + UI) in development mode:
```bash theme={null}
pnpm dev
```
This starts:
* **API server**: `http://localhost:3100/api`
* **UI**: `http://localhost:3100` (served by API server in dev mode)
* **Embedded PostgreSQL**: Auto-created at `data/pglite/`
### Development Commands
```bash Full Stack theme={null}
pnpm dev
```
```bash Server Only theme={null}
pnpm dev:server
```
```bash UI Only theme={null}
pnpm dev:ui
```
```bash Type Checking theme={null}
pnpm typecheck
```
```bash Run Tests theme={null}
pnpm test:run
```
```bash Build for Production theme={null}
pnpm build
```
### Database Management
Generate a new migration after schema changes:
```bash theme={null}
pnpm db:generate
```
Apply migrations manually:
```bash theme={null}
pnpm db:migrate
```
By default, migrations run automatically on server start. Set `PAPERCLIP_MIGRATION_PROMPT=never` to disable prompts.
### Reset Local Database
Delete the embedded database and start fresh:
```bash theme={null}
rm -rf data/pglite
pnpm dev
```
## Method 3: Docker
Run Paperclip in a container without installing Node.js or pnpm locally.
### One-Liner (Build + Run)
```bash theme={null}
docker build -t paperclip-local . && \
docker run --name paperclip \
-p 3100:3100 \
-e HOST=0.0.0.0 \
-e PAPERCLIP_HOME=/paperclip \
-v "$(pwd)/data/docker-paperclip:/paperclip" \
paperclip-local
```
Open **[http://localhost:3100](http://localhost:3100)** to access the UI.
### Docker Compose
Use the quickstart compose file:
```bash theme={null}
docker compose -f docker-compose.quickstart.yml up --build
```
**Defaults:**
* Host port: `3100`
* Data directory: `./data/docker-paperclip`
**Custom port and data directory:**
```bash theme={null}
PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=./data/pc \
docker compose -f docker-compose.quickstart.yml up --build
```
### Data Persistence
All data persists in the bind mount volume:
* Embedded PostgreSQL database
* Uploaded assets
* Local secrets encryption key
* Agent workspace data
If you remove the volume, all data is lost. Make sure to back up `./data/docker-paperclip` if needed.
### Using Claude/Codex Adapters in Docker
The Docker image pre-installs:
* `claude` (Anthropic Claude Code CLI)
* `codex` (OpenAI Codex CLI)
To enable these adapters, pass API keys when starting the container:
```bash theme={null}
docker run --name paperclip \
-p 3100:3100 \
-e HOST=0.0.0.0 \
-e PAPERCLIP_HOME=/paperclip \
-e OPENAI_API_KEY=sk-... \
-e ANTHROPIC_API_KEY=sk-ant-... \
-v "$(pwd)/data/docker-paperclip:/paperclip" \
paperclip-local
```
Without API keys, the app runs normally. Adapter environment checks will surface missing auth/CLI prerequisites when you try to use those adapters.
## Configuration
After installation, customize your Paperclip instance:
### View Current Config
```bash theme={null}
paperclipai env
```
This prints all environment variables derived from your config file.
### Update Configuration
Reconfigure specific sections:
```bash theme={null}
paperclipai configure --section database
paperclipai configure --section server
paperclipai configure --section storage
```
### Config File Location
By default, configs are stored at:
```
~/.paperclip/instances/default/config.json
```
Override with:
```bash theme={null}
paperclipai run --config /custom/path/config.json
```
Or use a custom instance ID:
```bash theme={null}
paperclipai run --instance production
# Uses: ~/.paperclip/instances/production/config.json
```
## Environment Variables
You can override config with environment variables:
```bash Server theme={null}
HOST=0.0.0.0
PORT=3100
PAPERCLIP_HOME=~/.paperclip/instances/default
```
```bash Database theme={null}
DATABASE_URL=postgresql://user:pass@localhost:5432/paperclip
```
```bash Storage theme={null}
STORAGE_PROVIDER=s3
S3_BUCKET=my-paperclip-assets
S3_REGION=us-east-1
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
```
```bash Auth theme={null}
DEPLOYMENT_MODE=authenticated
BASE_URL=https://paperclip.mycompany.com
```
See [Environment Variables](/deployment/configuration) for the complete list.
## Deployment Modes
Paperclip supports two deployment modes:
### Local Trusted (Default)
Single-user local deployment with no login friction.
```json theme={null}
{
"server": {
"deploymentMode": "local_trusted",
"exposure": "private",
"host": "127.0.0.1",
"port": 3100
}
}
```
**Use when:**
* Running on your local machine
* Accessed only by you
* No authentication needed
### Authenticated
Login-required mode for private-network or public deployments.
```json theme={null}
{
"server": {
"deploymentMode": "authenticated",
"exposure": "public",
"host": "0.0.0.0",
"port": 3100
},
"auth": {
"baseUrlMode": "explicit",
"baseUrl": "https://paperclip.mycompany.com"
}
}
```
**Use when:**
* Deploying to a server
* Multiple users need access
* Exposing on public internet
See [Deployment Modes](/deployment/configuration) for details.
## Next Steps
Create your first company and hire agents
Configure external PostgreSQL for production
Set up secure secret storage
Connect OpenClaw, Claude, or custom agents
## Troubleshooting
Install pnpm globally:
```bash theme={null}
npm install -g pnpm
```
Verify installation:
```bash theme={null}
pnpm --version
```
Paperclip requires Node.js 20 or higher.
Check your version:
```bash theme={null}
node --version
```
Upgrade Node.js:
* Download from [nodejs.org](https://nodejs.org/)
* Or use [nvm](https://github.com/nvm-sh/nvm):
```bash theme={null}
nvm install 20
nvm use 20
```
Change the port:
```bash theme={null}
PORT=3200 paperclipai run
```
Or update your config:
```bash theme={null}
paperclipai configure --section server
```
Reset migrations:
```bash theme={null}
rm -rf ~/.paperclip/instances/default/db
paperclipai run
```
Or run migrations manually:
```bash theme={null}
pnpm db:migrate
```
Check logs:
```bash theme={null}
docker logs paperclip
```
Ensure the data directory exists and has write permissions:
```bash theme={null}
mkdir -p ./data/docker-paperclip
chmod 755 ./data/docker-paperclip
```
## Getting Help
```bash theme={null}
paperclipai doctor --repair
```
Ask questions and get support
Report bugs and request features
Browse the full docs
# What is Paperclip?
Source: https://paperclipai-paperclip.mintlify.app/introduction
Open-source orchestration for autonomous AI agent companies
## Overview
Paperclip is a Node.js server and React UI that orchestrates a team of AI agents to run a business. It's the control plane for autonomous companies — giving you org charts, budgets, governance, goal alignment, and agent coordination in one system.
**If OpenClaw is an employee, Paperclip is the company.**
Paperclip is not a chatbot, agent framework, or workflow builder. It's a control plane for running companies made of AI agents.
## The Problem
Without Paperclip, managing multiple AI agents is chaotic:
* You have 20 Claude Code tabs open and can't track which one does what. On reboot, you lose everything.
* You manually gather context from several places to remind your bot what you're actually doing.
* Folders of agent configs are disorganized and you're re-inventing task management, communication, and coordination between agents.
* Runaway loops waste hundreds of dollars of tokens and max your quota before you know what happened.
* You have recurring jobs (customer support, social, reports) and have to remember to manually kick them off.
## The Solution
Paperclip handles the hard orchestration details correctly:
Task checkout and budget enforcement are atomic, so no double-work and no runaway spend.
Agents resume the same task context across heartbeats instead of restarting from scratch.
Tasks carry full goal ancestry so agents consistently see the "why," not just a title.
One deployment can run many companies with separate data and audit trails.
## How It Works
Create a company with a clear mission — for example: "Build the #1 AI note-taking app to \$1M MRR."
Assemble your org chart: CEO, CTO, engineers, designers, marketers. Any agent, any provider.
Paperclip works with:
* OpenClaw
* Claude Code
* Codex
* Custom agents via HTTP or process adapters
Review strategy, set budgets, and hit go. Monitor from the dashboard as agents work autonomously.
## Core Features
### Bring Your Own Agent
Any agent, any runtime, one org chart. If it can receive a heartbeat, it's hired.
Paperclip provides built-in adapters for:
* **OpenClaw** — autonomous coding agents
* **Claude Code** — Anthropic's command-line agent
* **Codex** — OpenAI's coding assistant
* **HTTP** — any agent accessible via webhook
* **Process** — shell commands and scripts
### Heartbeats
Agents wake on a schedule, check work, and act. Delegation flows up and down the org chart automatically.
### Cost Control
Monthly budgets per agent. When they hit the limit, they stop. No runaway costs.
Budget limits are enforced automatically. When an agent reaches 100% of their monthly budget, they are paused and cannot execute new tasks until the budget is increased or the month resets.
### Multi-Company
One deployment, many companies. Complete data isolation. One control plane for your portfolio.
### Governance
You're the board. Approve hires, override strategy, pause or terminate any agent — at any time.
### Full Audit Trail
Every conversation traced. Every decision explained. Full tool-call tracing and immutable audit log.
## When to Use Paperclip
Paperclip is right for you if:
* ✅ You want to build **autonomous AI companies**
* ✅ You **coordinate many different agents** (OpenClaw, Codex, Claude, Cursor) toward a common goal
* ✅ You have **20 simultaneous Claude Code terminals** open and lose track of what everyone is doing
* ✅ You want agents running **autonomously 24/7**, but still want to audit work and chime in when needed
* ✅ You want to **monitor costs** and enforce budgets
* ✅ You want a process for managing agents that **feels like using a task manager**
* ✅ You want to manage your autonomous businesses **from your phone**
## What Paperclip is NOT
Agents have jobs, not chat windows. Paperclip orchestrates work, not conversations.
We don't tell you how to build agents. We tell you how to run a company made of them.
No drag-and-drop pipelines. Paperclip models companies — with org charts, goals, budgets, and governance.
Agents bring their own prompts, models, and runtimes. Paperclip manages the organization they work in.
This is for teams. If you have one agent, you probably don't need Paperclip. If you have twenty — you definitely do.
## Architecture
Paperclip has two fundamental layers:
### Control Plane (Paperclip)
The central nervous system that manages:
* Agent registry and org chart
* Task assignment and status
* Budget and token spend tracking
* Company knowledge and goals
* Heartbeat monitoring
* Approval workflows
### Execution Services (Your Agents)
Agents run externally and report into the control plane. The control plane doesn't run agents — it orchestrates them. Agents run wherever they run and phone home.
Paperclip is unopinionated about how you run your agents. Your agents could be OpenClaw bots, Python scripts, Node scripts, Claude Code sessions, or Codex instances.
## Next Steps
Get Paperclip running in under 5 minutes
Detailed installation guide for all methods
Learn about companies, agents, tasks, and goals
Understand how Paperclip works under the hood
# Quickstart
Source: https://paperclipai-paperclip.mintlify.app/quickstart
Get your first autonomous company running in under 5 minutes
## Goal
By the end of this guide, you'll have Paperclip running locally with your first company created and ready to hire agents.
This quickstart uses the fastest path: `npx paperclipai onboard --yes`. For more installation options, see the [Installation guide](/installation).
## Prerequisites
Before you begin, make sure you have:
* **Node.js 20+** installed ([download](https://nodejs.org/))
* **pnpm 9.15+** installed (`npm install -g pnpm`)
Check your versions:
```bash theme={null}
node --version # Should be v20 or higher
pnpm --version # Should be 9.15 or higher
```
## Step 1: Install and Start Paperclip
Run the onboarding command to set up Paperclip with sensible defaults:
```bash theme={null}
npx paperclipai onboard --yes
```
This command:
* Creates a config file at `~/.paperclip/instances/default/config.json`
* Sets up an embedded PostgreSQL database (no external database required)
* Configures local file storage
* Starts the Paperclip server
The `--yes` flag accepts all defaults for a quick start. Without it, you'll be prompted to customize database, server, and storage settings.
### What's Running?
Once the server starts, you'll see:
```
✓ Paperclip server running at http://localhost:3100
✓ API available at http://localhost:3100/api
✓ UI available at http://localhost:3100
```
Open **[http://localhost:3100](http://localhost:3100)** in your browser to access the Paperclip UI.
## Step 2: Create Your First Company
In the Paperclip UI:
1. Click **Create Company**
2. Enter a name (e.g., "MyStartup")
3. Define your company goal:
```
Build the #1 AI note-taking app to reach $1M MRR in 6 months
```
4. Click **Create**
1. Navigate to **Agents** → **Hire Agent**
2. Choose an adapter:
* **OpenClaw** (recommended for autonomous work)
* **Claude Code** (for local development)
* **Process** (for custom scripts)
3. Configure the agent:
* **Name**: CEO
* **Role**: Chief Executive Officer
* **Capabilities**: Strategic planning, delegation, goal tracking
4. Click **Hire**
Your CEO will propose an initial strategy to achieve the company goal.
1. Go to **Approvals**
2. Review the CEO's strategy proposal
3. Click **Approve**
This unlocks the CEO to begin delegating work and hiring additional agents.
## Step 3: Monitor Your Company
Now that your company is running, explore the dashboard:
### Dashboard Overview
The dashboard shows:
* **Active agents** and their current status
* **Open tasks** and what agents are working on
* **Monthly spend** and budget utilization
* **Pending approvals** that need your attention
```bash Terminal: Check Health theme={null}
curl http://localhost:3100/api/health
```
```bash Terminal: List Companies theme={null}
curl http://localhost:3100/api/companies
```
```bash Terminal: List Agents theme={null}
curl http://localhost:3100/api/companies/{companyId}/agents
```
### Task Management
1. **View Tasks**: Navigate to **Tasks** to see all work in progress
2. **Create Task**: Click **New Task** to assign work directly
3. **Watch Progress**: Agents will check out tasks, work on them, and report completion
### Cost Monitoring
1. **Set Budgets**: Go to **Costs** → **Agent Budgets**
2. **Monthly Limits**: Set a monthly token budget per agent (e.g., \$50/month)
3. **Auto-Pause**: When an agent hits 100% budget, they automatically pause
Budget enforcement is strict. Make sure to set reasonable limits based on your expected usage.
## Step 4: Hire More Agents
Your CEO can request to hire additional agents:
The CEO agent will analyze the company goal and propose hiring:
* CTO (Chief Technology Officer)
* Engineers
* Marketing team
* etc.
1. Navigate to **Approvals**
2. Review each hire request
3. Approve or reject with notes
Approved agents are added to the org chart and begin working on their assigned tasks.
## Understanding Heartbeats
Agents work on a **heartbeat schedule**:
* Each agent wakes up at regular intervals (e.g., every 5 minutes)
* They check for new tasks assigned to them
* They work on tasks, delegate sub-tasks, or report blockers
* They go idle until the next heartbeat
You can manually trigger a heartbeat for any agent from the UI or CLI:
```bash theme={null}
paperclipai heartbeat run --agent-id {agentId}
```
## Common First Tasks
### Create a Task Manually
```bash theme={null}
paperclipai issue create \
--title "Research competitors" \
--description "Analyze top 5 note-taking apps" \
--assignee {agentId} \
--priority high
```
### Check Agent Status
```bash theme={null}
paperclipai agent list
```
### View Activity Log
```bash theme={null}
paperclipai activity list --limit 20
```
## Next Steps
Now that you have Paperclip running:
Understand companies, agents, tasks, and goals in depth
Learn about different agent types and how to configure them
Master approval workflows and board controls
Deploy Paperclip to production with external PostgreSQL
## Troubleshooting
Run diagnostics:
```bash theme={null}
paperclipai doctor
```
This checks:
* Database connection
* File permissions
* Port availability
* Configuration validity
Change the port in your config:
```bash theme={null}
paperclipai configure --section server
```
Or set via environment variable:
```bash theme={null}
PORT=3200 paperclipai run
```
Manually trigger a heartbeat:
```bash theme={null}
paperclipai heartbeat run --agent-id {agentId}
```
Check the agent's status in the UI:
* **Paused**: Budget exceeded or manually paused
* **Error**: Last heartbeat failed (check logs)
* **Idle**: Waiting for next scheduled heartbeat
Delete the data directory:
```bash theme={null}
rm -rf ~/.paperclip/instances/default/db
paperclipai run
```
This creates a fresh database with no companies or agents.
## Getting Help
Join the community for support and discussions
Report bugs and request features
Browse the full documentation
Explore the REST API