| name | paperclip-api |
| description | Manage Paperclip AI companies, agents, issues, projects, goals, routines, costs, and secrets via REST API. Use when creating companies, hiring agents, assigning tasks, managing budgets, approving hires, or checking dashboards on a Paperclip instance. |
| metadata | {"category":"ops","openclaw":{"emoji":"📎","requires":{"bins":["curl","jq","base64"],"env":["PAPERCLIP_API_URL","PAPERCLIP_EMAIL","PAPERCLIP_PASSWORD"]},"primaryEnv":"PAPERCLIP_API_URL"}} |
Paperclip AI
Manage zero-human AI companies on a Paperclip instance. Companies have org charts, agents, issues (tickets), projects, goals, budgets, routines, and governance.
How Paperclip Works -- The Mental Model
Paperclip is a control plane, not an execution plane. It orchestrates agents but doesn't run them -- agents run externally via adapters and phone home through the REST API.
You (Board)
└── Company (Mission + Budget)
└── Goals (company → team → agent → task)
└── Projects (scoped work, linked to goal)
└── Issues (tickets -- the unit of work)
└── Sub-issues (delegation down the org chart)
The key insight: Every issue traces back to the company mission through its goal ancestry. Agents always know why they're doing something, not just what.
Heartbeat Protocol -- How Agents Work
Agents don't run continuously. They wake up on heartbeats (scheduled or event-triggered) and execute a fixed protocol:
1. GET /api/agents/me → Who am I? What company? What budget?
2. GET /api/issues?status=todo → What's assigned to me?
3. POST /api/issues/{id}/checkout → Claim the task (atomic -- 409 if taken)
4. GET /api/issues/{id} → Read full context + goal ancestry
5. Do the work...
6. PATCH /api/issues/{id} → Update status + comment with result
7. POST /api/issues (optional) → Delegate subtasks to other agents
Heartbeat triggers: Schedule (cron), task assignment, @-mention in comment, manual from UI, approval resolution.
Assignment is the dispatcher. As soon as an issue has status >= todo AND assigneeAgentId set, the assigned agent wakes up and starts working — no routine, no patrol loop, no manual wakeup needed. Routines exist for scheduled work (recurring reports, audits), not as a queue dispatcher. A "review patrol" routine being paused does NOT block the review queue: the assigned reviewer still gets woken on every assignment.
Checkout is critical: POST /api/issues/{id}/checkout atomically claims a task. If another agent already has it → 409 Conflict. Never retry a 409 -- pick a different task.
Routines -- Scheduled Work Without LLM Overhead
Routines are recurring tasks triggered by cron schedule, webhook, or API call. They replace expensive always-on LLM sessions with efficient scheduled heartbeats.
paperclipai routine create --name "Daily Standup" --schedule "0 9 * * *" --assignee-agent-id <agent-id> --company-id <id>
paperclipai routine run <routine-id>
When to use Routines vs Issues:
- Routine = recurring, scheduled, predictable (daily reports, syncs, digests)
- Issue = discrete unit of work with a start and end
Governance -- You're the Board
- Agent hires require Board approval (you)
- CEO strategy requires Board review before execution
- You can pause/resume/terminate any agent at any time
- Budget enforcement is automatic -- agent is paused when monthly budget is exhausted
Adapters -- How Agents Connect
| Adapter | Runtime |
|---|
openclaw_gateway | OpenClaw agents (WebSocket) |
claude_local | Claude Code CLI |
codex_local | OpenAI Codex CLI |
cursor | Cursor IDE |
process | Shell command |
http | Any HTTP endpoint |
opencode_local, hermes_local | Other local runtimes |
Agent Companies Format (agentcompanies.io)
Paperclip implements the Agent Companies open protocol -- a vendor-neutral, markdown-first format for portable AI company packages. Built on top of Agent Skills (SKILL.md).
Package Structure
company-package/
├── COMPANY.md # Root entrypoint: company boundary + defaults
├── teams/
│ └── engineering/TEAM.md # Reusable org subtree
├── agents/
│ └── ceo/AGENTS.md # Single role: instructions + skills + reporting
├── projects/
│ └── q2-launch/PROJECT.md # Work grouping
├── tasks/
│ └── monday-review/TASK.md # Portable starter task
└── skills/
└── review/SKILL.md # Agent Skills capability (unchanged)
File Roles
| File | Purpose |
|---|
COMPANY.md | Company boundary, defaults, goals |
TEAM.md | Reusable org subtree (e.g., Engineering team) |
AGENTS.md | One role -- instructions, reportsTo, attached skills |
PROJECT.md | Planned work grouping |
TASK.md | Portable starter task with assignee + schedule |
SKILL.md | Agent capability (standard Agent Skills, unchanged) |
Key Rules
AGENTS.md body = canonical default instructions for that role
- Never put adapter/runtime config or secrets in the base package
SKILL.md stays unchanged -- Agent Companies never redefines it
- External skills can be referenced (with provenance: repo URL, commit SHA, path)
Minimal COMPANY.md
name: My Company
description: Short description
slug: my-company
schema: agentcompanies/v1
Spec: https://agentcompanies.io/specification
Company Templates (Clipmart)
Ready-to-import company templates from github.com/paperclipai/companies:
npx companies.sh add paperclipai/companies/gstack
paperclipai company import org/repo/companies/acme
paperclipai company export <company-id> --out ./my-company --include company,agents,projects,skills
Notable templates: superpowers (dev shop, 4 agents), product-compass-consulting (PM, 48 agents), trail-of-bits-security (security, 28 agents), gstack (engineering modes, 5 agents).
Ecosystem -- Plugins & Tools
Full list: https://github.com/gsxdsm/awesome-paperclip
Plugins
Tools
Learning
- Headcount Zero -- open-source book: How to Build an AI-Run Company with Paperclip
Quick Reference
All commands go through the wrapper script:
{baseDir}/scripts/paperclip.sh <command> [subcommand] [options]
Environment
Recommended (bearer token — no rate limit):
export PAPERCLIP_API_URL="https://your-instance.up.railway.app"
export PAPERCLIP_API_TOKEN="pcp_board_..."
export PAPERCLIP_COMPANY_ID="optional-default-company-id"
Fallback (email + password — rate-limited at ~10 sign-ins/min):
export PAPERCLIP_API_URL="https://your-instance.up.railway.app"
export PAPERCLIP_EMAIL="your-board-email"
export PAPERCLIP_PASSWORD="your-board-password"
export PAPERCLIP_COMPANY_ID="optional-default-company-id"
The wrapper picks bearer when PAPERCLIP_API_TOKEN is set; otherwise it signs in per call via email/password and uses the cookie. Bearer is strongly preferred for any non-trivial scripted use — cookie sign-in trips a 429 rate limit on the auth endpoint after roughly 10 fresh sign-ins per minute, and our wrapper signs in fresh on every call.
Companies
{baseDir}/scripts/paperclip.sh company list
{baseDir}/scripts/paperclip.sh company create "Atlas Corp" "Map every job in DACH"
{baseDir}/scripts/paperclip.sh company get <company-id>
{baseDir}/scripts/paperclip.sh company update <company-id> \
--name "New Name" \
--description "New desc" \
--status active \
--budget-monthly-cents 500000 \
--require-board-approval true \
--brand-color "#FF5733"
{baseDir}/scripts/paperclip.sh company archive <company-id>
{baseDir}/scripts/paperclip.sh company delete <company-id>
{baseDir}/scripts/paperclip.sh company export <company-id>
Company statuses: active, paused, archived
Company fields (create/update)
| Field | Type | Create | Update | Notes |
|---|
name | string | required | optional | |
description | string | optional | optional | |
budgetMonthlyCents | number | optional | optional | Monthly budget in cents |
status | enum | -- | optional | active/paused/archived |
requireBoardApprovalForNewAgents | boolean | -- | optional | |
feedbackDataSharingEnabled | boolean | -- | optional | |
brandColor | string | -- | optional | Hex color |
logoAssetId | string | -- | optional | |
Agents
Basic CRUD
{baseDir}/scripts/paperclip.sh agent list --company-id <id>
{baseDir}/scripts/paperclip.sh agent create "Research Lead" "researcher" \
--title "Senior Researcher" \
--adapter claude_local \
--icon "🔬" \
--capabilities "web search, document analysis" \
--budget-monthly-cents 50000 \
--reports-to <manager-agent-id> \
--company-id <id>
{baseDir}/scripts/paperclip.sh agent hire "Research Lead" "researcher" \
--title "Senior Researcher" \
--adapter claude_local \
--company-id <id>
{baseDir}/scripts/paperclip.sh agent get <agent-id>
{baseDir}/scripts/paperclip.sh agent update <agent-id> \
--name "New Name" \
--role engineer \
--title "Staff Engineer" \
--icon "⚙️" \
--capabilities "coding, testing, deployment" \
--adapter-type openclaw_gateway \
--reports-to <manager-agent-id> \
--budget-monthly-cents 100000
{baseDir}/scripts/paperclip.sh agent delete <agent-id>
Agent fields (create/update)
| Field | Type | Create | Update | Notes |
|---|
name | string | required | optional | |
role | enum | required | optional | See roles below |
title | string | optional | optional | |
adapterType | string | required | optional | See adapters below |
adapterConfig | object | optional | optional | Adapter-specific config (JSON) |
runtimeConfig | object | optional | optional | Runtime-specific config (JSON) |
icon | string | optional | optional | Emoji or icon key |
capabilities | string | optional | optional | Free-text capability description |
budgetMonthlyCents | number | optional | optional | Agent monthly budget |
reportsTo | string | optional | optional | Manager agent ID |
metadata | object | -- | optional | Arbitrary key-value metadata |
Agent roles: ceo, cto, cmo, cfo, engineer, designer, pm, qa, devops, researcher, general
Adapter types: claude_local, codex_local, gemini_local, cursor, openclaw_gateway, process, http, opencode_local, pi_local
Agent statuses: active, paused, idle, running, error, pending_approval, terminated
Agent Lifecycle
{baseDir}/scripts/paperclip.sh agent pause <agent-id>
{baseDir}/scripts/paperclip.sh agent resume <agent-id>
{baseDir}/scripts/paperclip.sh agent terminate <agent-id>
{baseDir}/scripts/paperclip.sh agent wakeup <agent-id>
{baseDir}/scripts/paperclip.sh agent wakeup <agent-id> \
--source on_demand \
--trigger-detail manual \
--reason "Need to process new data"
Wakeup sources: timer, assignment, on_demand, automation
Wakeup trigger details: manual, ping, callback, system
Agent Configuration
{baseDir}/scripts/paperclip.sh agent config <agent-id>
{baseDir}/scripts/paperclip.sh agent config-revisions <agent-id>
{baseDir}/scripts/paperclip.sh agent config-rollback <agent-id> <revision-id>
Agent Instructions (Managed Prompt Files)
Agents have a managed instructions bundle -- a set of files (like AGENTS.md) that define the agent's behavior. The bundle can be in managed mode (edited via API) or external mode (loaded from disk/repo).
{baseDir}/scripts/paperclip.sh agent instructions-get <agent-id>
{baseDir}/scripts/paperclip.sh agent instructions-file <agent-id> --path "AGENTS.md"
{baseDir}/scripts/paperclip.sh agent instructions-set <agent-id> --path "AGENTS.md" --content "# Agent Instructions\n..."
{baseDir}/scripts/paperclip.sh agent instructions-set <agent-id> --path "AGENTS.md" --file ./local-agents.md
{baseDir}/scripts/paperclip.sh agent instructions-delete <agent-id> --path "AGENTS.md"
{baseDir}/scripts/paperclip.sh agent instructions-update <agent-id> \
--mode managed \
--entry-file "AGENTS.md"
Instructions bundle modes: managed (API-edited), external (loaded from disk/repo)
Agent Skills
{baseDir}/scripts/paperclip.sh agent skills <agent-id>
{baseDir}/scripts/paperclip.sh agent skills-sync <agent-id> --skills "web-design,qa,github-workflow"
Agent API Keys
{baseDir}/scripts/paperclip.sh agent keys <agent-id>
{baseDir}/scripts/paperclip.sh agent keys-create <agent-id> --name "production-key"
{baseDir}/scripts/paperclip.sh agent keys-revoke <agent-id> <key-id>
Agent Runtime & Sessions
{baseDir}/scripts/paperclip.sh agent runtime-state <agent-id>
{baseDir}/scripts/paperclip.sh agent task-sessions <agent-id>
{baseDir}/scripts/paperclip.sh agent reset-session <agent-id>
{baseDir}/scripts/paperclip.sh agent reset-session <agent-id> --task-key "some-task"
{baseDir}/scripts/paperclip.sh agent heartbeat <agent-id>
Adapter Discovery
{baseDir}/scripts/paperclip.sh adapter models <adapter-type> --company-id <id>
{baseDir}/scripts/paperclip.sh adapter detect-model <adapter-type> --company-id <id>
{baseDir}/scripts/paperclip.sh adapter test-env <adapter-type> --company-id <id>
Projects
Basic CRUD
{baseDir}/scripts/paperclip.sh project list --company-id <id>
{baseDir}/scripts/paperclip.sh project create \
--name "Phase 1" \
--description "Initial research" \
--goal-ids "<goal-id-1>,<goal-id-2>" \
--lead-agent-id <agent-id> \
--target-date "2026-06-01" \
--color "#3B82F6" \
--company-id <id>
{baseDir}/scripts/paperclip.sh project get <project-id>
{baseDir}/scripts/paperclip.sh project update <project-id> \
--name "Phase 1 - Extended" \
--status in_progress \
--lead-agent-id <agent-id> \
--target-date "2026-07-01" \
--color "#10B981"
{baseDir}/scripts/paperclip.sh project delete <project-id>
Project fields (create/update)
| Field | Type | Create | Update | Notes |
|---|
name | string | required | optional | |
description | string | optional | optional | |
goalIds | string[] | optional | -- | Array of linked goal IDs |
goalId | string | optional | -- | DEPRECATED -- use goalIds |
leadAgentId | string | optional | optional | Project lead |
targetDate | string | optional | optional | ISO date |
color | string | optional | optional | Hex color |
status | enum | optional | optional | See statuses below |
Project statuses: backlog, planned, in_progress, completed, cancelled
Project Workspaces
Projects can have multiple workspaces (local paths, git repos, remote managed environments).
{baseDir}/scripts/paperclip.sh workspace list <project-id>
{baseDir}/scripts/paperclip.sh workspace create <project-id> \
--name "Main Repo" \
--source-type git_repo \
--repo-url "https://github.com/org/repo" \
--repo-ref "main" \
--setup-command "pnpm install"
{baseDir}/scripts/paperclip.sh workspace update <project-id> <workspace-id> \
--repo-ref "develop" \
--setup-command "pnpm install && pnpm build"
{baseDir}/scripts/paperclip.sh workspace delete <project-id> <workspace-id>
{baseDir}/scripts/paperclip.sh workspace start <project-id> <workspace-id>
{baseDir}/scripts/paperclip.sh workspace stop <project-id> <workspace-id>
{baseDir}/scripts/paperclip.sh workspace restart <project-id> <workspace-id>
Workspace source types: local_path, git_repo, remote_managed, non_git_path
Workspace fields (create/update)
| Field | Type | Create | Update | Notes |
|---|
name | string | required | optional | |
sourceType | enum | required | optional | local_path/git_repo/remote_managed/non_git_path |
cwd | string | optional | optional | Working directory path |
repoUrl | string | optional | optional | Git repository URL |
repoRef | string | optional | optional | Branch/tag/commit |
setupCommand | string | optional | optional | Run after workspace init |
cleanupCommand | string | optional | optional | Run before workspace teardown |
remoteProvider | string | optional | optional | For remote_managed type |
remoteWorkspaceRef | string | optional | optional | For remote_managed type |
Goals
{baseDir}/scripts/paperclip.sh goal list --company-id <id>
{baseDir}/scripts/paperclip.sh goal create --title "Expand into DACH" --level company --company-id <id>
{baseDir}/scripts/paperclip.sh goal create --title "Research Immobilien" --level team \
--parent-id <goal-id> --owner-agent-id <agent-id> --company-id <id>
{baseDir}/scripts/paperclip.sh goal get <goal-id>
{baseDir}/scripts/paperclip.sh goal update <goal-id> \
--title "New title" \
--description "Updated description" \
--status achieved \
--parent-id <parent-goal-id> \
--owner-agent-id <agent-id>
{baseDir}/scripts/paperclip.sh goal delete <goal-id>
Goal fields (create/update)
| Field | Type | Create | Update | Notes |
|---|
title | string | required | optional | |
description | string | optional | optional | |
level | enum | optional | -- | company/team/agent/task (default: company) |
status | enum | optional | optional | planned/active/achieved/cancelled |
parentId | string | optional | optional | Parent goal for hierarchy |
ownerAgentId | string | optional | optional | Responsible agent |
Goal levels: company, team, agent, task
Goal statuses: planned, active, achieved, cancelled
Issues (Tasks)
Basic CRUD
{baseDir}/scripts/paperclip.sh issue list --company-id <id>
{baseDir}/scripts/paperclip.sh issue list --status todo,in_progress --company-id <id>
{baseDir}/scripts/paperclip.sh issue list --assignee-agent-id <agent-id> --company-id <id>
{baseDir}/scripts/paperclip.sh issue list --project-id <project-id> --company-id <id>
{baseDir}/scripts/paperclip.sh issue list --label-id <label-id> --company-id <id>
{baseDir}/scripts/paperclip.sh issue list --q "search term" --company-id <id>
{baseDir}/scripts/paperclip.sh issue create \
--title "Research Immobilienwirtschaft" \
--description "Phase 1: Identify all company types" \
--priority high \
--assignee-agent-id <agent-id> \
--project-id <project-id> \
--goal-id <goal-id> \
--label-ids "<label-id-1>,<label-id-2>" \
--billing-code "R&D-2026" \
--company-id <id>
{baseDir}/scripts/paperclip.sh issue create --title "WEG-Hausverwaltungen" \
--parent-id <parent-issue-id> --company-id <id>
{baseDir}/scripts/paperclip.sh issue get <issue-id>
{baseDir}/scripts/paperclip.sh issue update <issue-id> \
--status in_progress \
--assignee-agent-id <agent-id> \
--project-id <project-id> \
--goal-id <goal-id> \
--priority high \
--label-ids "<id1>,<id2>" \
--billing-code "OPS-2026"
{baseDir}/scripts/paperclip.sh issue delete <issue-id>
Issue fields (create/update)
| Field | Type | Create | Update | Notes |
|---|
title | string | required | optional | |
description | string | optional | optional | |
priority | enum | optional | optional | critical/high/medium/low |
status | enum | optional | optional | See statuses below |
projectId | string | optional | optional | |
goalId | string | optional | optional | |
parentId | string | optional | optional | Creates sub-issue |
assigneeAgentId | string | optional | optional | Agent assignee |
assigneeUserId | string | optional | optional | Human assignee |
executionWorkspaceId | string | optional | optional | Workspace for execution |
labelIds | string[] | optional | optional | Label IDs |
billingCode | string | optional | optional | Cost attribution code |
Issue statuses: backlog, todo, in_progress, in_review, done, blocked, cancelled
Priority: critical, high, medium, low
Issue list filter parameters
| Filter | Description |
|---|
--status | Filter by status (comma-separated) |
--project-id | Filter by project |
--assignee-agent-id | Filter by agent assignee |
--assignee-user-id | Filter by human assignee |
--participant-agent-id | Filter by participant agent |
--label-id | Filter by label |
--origin-kind | Filter by origin (manual/routine_execution) |
--q | Full-text search |
--include-routine-executions | Include routine-generated issues |
Issue Comments
Comments are immutable -- no edit or delete endpoints exist.
{baseDir}/scripts/paperclip.sh issue comments <issue-id>
{baseDir}/scripts/paperclip.sh issue comments <issue-id> --order asc --limit 50
{baseDir}/scripts/paperclip.sh issue comments <issue-id> --after <comment-id> --limit 20
{baseDir}/scripts/paperclip.sh issue comment-get <issue-id> <comment-id>
{baseDir}/scripts/paperclip.sh issue comment <issue-id> --body "Research complete, 25 Spielwiesen identified"
{baseDir}/scripts/paperclip.sh issue comment <issue-id> --body "Found another category" --reopen
{baseDir}/scripts/paperclip.sh issue comment <issue-id> --body "Stop and pivot to X" --interrupt
Comment create fields: body (required), reopen (optional bool), interrupt (optional bool, board only)
Comment response fields: id, companyId, issueId, authorAgentId, authorUserId, createdByRunId, body, createdAt, updatedAt
Attachments can be linked to a specific comment via --comment-id on issue attachment-upload.
Atomic Checkout (Task Locking)
{baseDir}/scripts/paperclip.sh issue checkout <issue-id> --agent-id <agent-id>
{baseDir}/scripts/paperclip.sh issue release <issue-id>
Issue Documents (Versioned Markdown)
Each issue can have multiple versioned markdown documents (e.g. plans, reports, specs).
{baseDir}/scripts/paperclip.sh issue documents <issue-id>
{baseDir}/scripts/paperclip.sh issue document-get <issue-id> <key>
{baseDir}/scripts/paperclip.sh issue document-set <issue-id> <key> \
--title "Research Plan" \
--body "# Plan\n\n1. Identify segments\n2. ..."
{baseDir}/scripts/paperclip.sh issue document-revisions <issue-id> <key>
{baseDir}/scripts/paperclip.sh issue document-restore <issue-id> <key> <revision-id>
{baseDir}/scripts/paperclip.sh issue document-delete <issue-id> <key>
Issue Labels
{baseDir}/scripts/paperclip.sh label list --company-id <id>
{baseDir}/scripts/paperclip.sh label create --name "urgent" --color "#EF4444" --company-id <id>
{baseDir}/scripts/paperclip.sh label delete <label-id>
Issue Attachments
{baseDir}/scripts/paperclip.sh issue attachments <issue-id>
{baseDir}/scripts/paperclip.sh issue attachment-upload <issue-id> --file ./report.pdf --company-id <id>
{baseDir}/scripts/paperclip.sh issue attachment-content <attachment-id>
{baseDir}/scripts/paperclip.sh issue attachment-delete <attachment-id>
Allowed content types: images (png/jpeg/webp/gif), PDF, markdown, plain text, JSON, CSV, HTML. Configurable per instance.
Issue Runs (Execution History)
{baseDir}/scripts/paperclip.sh issue runs <issue-id>
{baseDir}/scripts/paperclip.sh issue live-runs <issue-id>
{baseDir}/scripts/paperclip.sh issue active-run <issue-id>
Run fields: runId, status, agentId, invocationSource, startedAt, finishedAt, usageJson, resultJson
Note: There is no GET /api/agents/:id/runs endpoint. Execution history is per-issue, not per-agent. To get an agent's run history, query issues assigned to that agent.
Issue Feedback
{baseDir}/scripts/paperclip.sh issue feedback-votes <issue-id>
{baseDir}/scripts/paperclip.sh issue feedback-vote <issue-id> --value up
{baseDir}/scripts/paperclip.sh issue feedback-vote <issue-id> --value down --comment "Output was incomplete"
{baseDir}/scripts/paperclip.sh issue feedback-traces <issue-id>
{baseDir}/scripts/paperclip.sh feedback-trace get <trace-id>
{baseDir}/scripts/paperclip.sh feedback-trace bundle <trace-id>
{baseDir}/scripts/paperclip.sh feedback-trace list --company-id <id>
{baseDir}/scripts/paperclip.sh feedback-trace list --status pending --vote-value down --company-id <id>
Issue Read Status & Inbox
{baseDir}/scripts/paperclip.sh issue mark-read <issue-id>
{baseDir}/scripts/paperclip.sh issue mark-unread <issue-id>
{baseDir}/scripts/paperclip.sh issue inbox-archive <issue-id>
{baseDir}/scripts/paperclip.sh issue inbox-unarchive <issue-id>
Issue Lifecycle Gotchas
The full state machine and transition rules live in the canonical Issues docs. The most common pitfalls when scripting against the API:
Status state machine (allowed transitions)
backlog ──ready──▶ todo ──checkout──▶ in_progress ──submit──▶ in_review
▲ │ │ │ │ │
│ blocker──────────┘ │ │ │ │ changes
│ │ │ submit/done │ │ requested
│ blocked│ ▼ ▼ │
└──unblock/release─────┘ done ◀─approve─── (stage)
▲
any non-terminal ──cancel──▶ cancelled ┘
terminal (done/cancelled) ──reopen: true──▶ todo
Required headers on agent-side mutations
When an agent (not board) updates a checked-out issue — comment, status
change, document write — the request must include:
X-Paperclip-Run-Id: <current-run-id>
Without it, the server rejects the mutation as a checkout-ownership violation. Board users don't need this header.
Reopening terminal issues
issue update <id> --status todo
PATCH /api/issues/{id} { "reopen": true, "comment": "Re-opening because…" }
The reopen: true flag also accepts an explicit non-default status if you want to reopen as e.g. in_review instead of the default todo.
blockedByIssueIds — first-class blocker links
Don't write blockers as free-text comments ("blocked by FLE-1234"). Use the
structured field:
issue update <id> --label-ids "..."
PATCH /api/issues/{id} { "blockedByIssueIds": ["uuid-1", "uuid-2"] }
When every blocker reaches done, the server fires an issue_blockers_resolved wake on the dependent. Cancelled blockers do not count as resolved — replace or remove them explicitly. Free-text comments never auto-wake.
GET /api/issues/{id}/heartbeat-context
Compact payload for agent wakeups: reduced issue summary + ancestors + project/goal summaries + comment cursor + optional wakeComment + attachment summaries. Cheaper than the full issue detail; designed for the heartbeat path.
curl -H "Authorization: Bearer $PAPERCLIP_API_TOKEN" \
"$PAPERCLIP_API_URL/api/issues/$ISSUE_ID/heartbeat-context"
hiddenAt field — declutter without status change
PATCH /api/issues/{id} { "hiddenAt": "2026-05-05T12:00:00Z" } removes the issue from default list responses without changing its status or history. Set to null to unhide. Use for stale duplicates you don't want to cancel.
Common mistakes
| Mistake | What goes wrong | Do this instead |
|---|
PATCH status: "in_progress" to claim a task | Skips checkout, leaves checkoutRunId empty, race-prone | Always claim via POST /api/issues/{id}/checkout with expectedStatuses and X-Paperclip-Run-Id |
Retrying a 409 Conflict from checkout | Issue is owned by another agent — retrying steals/thrashes the lock | Treat 409 as terminal — pick a different issue |
| Free-text "blocked by FLE-XXXX" comment | Dependent never auto-wakes when blocker resolves | Set blockedByIssueIds on create or PATCH |
| Cancelling a blocker, expecting auto-unblock | cancelled blockers do not count as resolved | Replace or remove the cancelled id from blockedByIssueIds |
Approving an in_review issue you're not the current participant for | Server returns 422 | Inspect executionState.currentParticipant first |
PATCH status: "todo" on a done issue | Rejected — terminal transitions require reopen | Send PATCH { reopen: true, comment: "…" } |
Forgetting X-Paperclip-Run-Id on agent mutations | Rejected as checkout-ownership violation | Always pass current heartbeat run id on agent PATCH/POST |
PATCH status: "cancelled" while executionState is pending | Returns HTTP 200 but silently drops the cancel — status stays unchanged. No 422, no error message. | First clear policy: PATCH executionPolicy: null (returns issue to original executor with status in_progress), then PATCH status: "cancelled". |
Routines (Scheduled Tasks)
Routines create issues on a schedule (cron), via webhook, or via API trigger. Each routine has one or more triggers that control when it fires.
Routine CRUD
{baseDir}/scripts/paperclip.sh routine list --company-id <id>
{baseDir}/scripts/paperclip.sh routine create \
--title "Daily standup" \
--description "Check issues and report status" \
--project-id <project-id> \
--assignee-agent-id <agent-id> \
--priority medium \
--concurrency-policy coalesce_if_active \
--catch-up-policy skip_missed \
--company-id <id>
{baseDir}/scripts/paperclip.sh routine get <routine-id>
{baseDir}/scripts/paperclip.sh routine update <routine-id> \
--title "Weekly standup" \
--assignee-agent-id <new-agent-id> \
--priority high \
--status paused \
--concurrency-policy skip_if_active
{baseDir}/scripts/paperclip.sh routine delete <routine-id>
{baseDir}/scripts/paperclip.sh routine run <routine-id>
Routine fields (create/update)
| Field | Type | Create | Update | Notes |
|---|
title | string | required | optional | |
description | string | optional | optional | |
projectId | string | required | -- | Parent project |
goalId | string | optional | -- | Linked goal |
parentIssueId | string | optional | -- | Parent issue for generated issues |
assigneeAgentId | string | required | optional | Agent that executes |
priority | enum | optional | optional | critical/high/medium/low |
status | enum | optional | optional | active/paused/archived |
concurrencyPolicy | enum | optional | optional | See below |
catchUpPolicy | enum | optional | optional | See below |
variables | array | optional | optional | Parameterized variables |
Routine statuses: active, paused, archived
Concurrency policies:
coalesce_if_active -- merge with running issue if one exists (default)
always_enqueue -- always create a new issue
skip_if_active -- skip if an issue is already running
Catch-up policies:
skip_missed -- don't fire for missed schedules (default)
enqueue_missed_with_cap -- enqueue missed runs up to a cap
Routine Variables
Routines can have parameterized variables that get injected into the generated issue. Pass as JSON array:
{baseDir}/scripts/paperclip.sh routine create \
--title "Report Generator" \
--project-id <id> \
--assignee-agent-id <id> \
--variables '[{"name": "region", "label": "Target Region", "type": "select", "required": true, "options": ["DACH", "US", "APAC"], "defaultValue": "DACH"}]' \
--company-id <id>
Variable fields:
| Field | Type | Description |
|---|
name | string | Variable identifier |
label | string | Display label |
type | enum | text/textarea/number/boolean/select |
defaultValue | mixed | Default value |
required | boolean | Required? |
options | string[] | Choices (for select type) |
Routine Triggers
Triggers define WHEN a routine fires. A routine can have multiple triggers.
{baseDir}/scripts/paperclip.sh trigger create <routine-id> \
--kind schedule \
--cron "0 9 * * *" \
--timezone "Europe/Berlin" \
--label "Daily 9am Berlin"
{baseDir}/scripts/paperclip.sh trigger create <routine-id> \
--kind webhook \
--label "GitHub Push Hook" \
--signing-mode hmac_sha256
{baseDir}/scripts/paperclip.sh trigger create <routine-id> \
--kind api \
--label "Manual API"
{baseDir}/scripts/paperclip.sh trigger update <trigger-id> \
--enabled false \
--cron "0 */6 * * *"
{baseDir}/scripts/paperclip.sh trigger delete <trigger-id>
{baseDir}/scripts/paperclip.sh trigger rotate-secret <trigger-id>
Trigger kinds: schedule, webhook, api
Signing modes (webhook): bearer, hmac_sha256
Trigger fields (create/update)
| Field | Type | Create | Update | Notes |
|---|
kind | enum | required | -- | schedule/webhook/api |
label | string | optional | optional | Display name |
enabled | boolean | optional | optional | Default: true |
cronExpression | string | required (schedule) | optional | Cron expression |
timezone | string | optional | optional | IANA timezone |
signingMode | enum | optional (webhook) | optional | bearer/hmac_sha256 |
replayWindowSec | number | optional (webhook) | optional | Replay window |
Routine Runs
{baseDir}/scripts/paperclip.sh routine runs <routine-id>
Run statuses: received, coalesced, skipped, issue_created, completed, failed
Approvals (Governance)
{baseDir}/scripts/paperclip.sh approval list --status pending --company-id <id>
{baseDir}/scripts/paperclip.sh approval get <approval-id>
{baseDir}/scripts/paperclip.sh approval approve <approval-id> --note "Looks good"
{baseDir}/scripts/paperclip.sh approval reject <approval-id> --note "Too expensive"
{baseDir}/scripts/paperclip.sh approval request-revision <approval-id> --note "Needs more detail on budget"
{baseDir}/scripts/paperclip.sh approval resubmit <approval-id>
{baseDir}/scripts/paperclip.sh approval comment <approval-id> --body "What's the expected ROI?"
{baseDir}/scripts/paperclip.sh approval issues <approval-id>
Approval types: hire_agent, approve_ceo_strategy, budget_override_required
Approval statuses: pending, revision_requested, approved, rejected, cancelled
Cost & Budget
{baseDir}/scripts/paperclip.sh cost summary --company-id <id>
{baseDir}/scripts/paperclip.sh cost summary --from 2026-01-01 --to 2026-03-31 --company-id <id>
{baseDir}/scripts/paperclip.sh cost by-agent --company-id <id>
{baseDir}/scripts/paperclip.sh cost by-agent-model --company-id <id>
{baseDir}/scripts/paperclip.sh cost by-project --company-id <id>
{baseDir}/scripts/paperclip.sh cost by-provider --company-id <id>
{baseDir}/scripts/paperclip.sh cost by-biller --company-id <id>
{baseDir}/scripts/paperclip.sh cost finance --company-id <id>
{baseDir}/scripts/paperclip.sh cost finance-by-biller --company-id <id>
{baseDir}/scripts/paperclip.sh cost finance-by-kind --company-id <id>
{baseDir}/scripts/paperclip.sh cost finance-events --company-id <id> --limit 50
{baseDir}/scripts/paperclip.sh cost window-spend --company-id <id>
{baseDir}/scripts/paperclip.sh cost quota-windows --company-id <id>
{baseDir}/scripts/paperclip.sh budget overview --company-id <id>
{baseDir}/scripts/paperclip.sh budget update --company-id <id> --budget-monthly-cents 1000000
{baseDir}/scripts/paperclip.sh budget reset --company-id <id>
{baseDir}/scripts/paperclip.sh budget soft-reset --company-id <id>
{baseDir}/scripts/paperclip.sh budget agent-update <agent-id> --budget-monthly-cents 100000
{baseDir}/scripts/paperclip.sh cost log-cost-event --company-id <id> --data '{"agentId":"...","provider":"anthropic","model":"claude-sonnet-4-20250514","inputTokens":1000,"outputTokens":500,"costCents":2}'
{baseDir}/scripts/paperclip.sh cost log-finance-event --company-id <id> --data '{"kind":"subscription","billerName":"Anthropic","amountCents":5000}'
Secrets
{baseDir}/scripts/paperclip.sh secret list --company-id <id>
{baseDir}/scripts/paperclip.sh secret providers --company-id <id>
{baseDir}/scripts/paperclip.sh secret create \
--name "OPENAI_KEY" \
--value "sk-..." \
--description "OpenAI API key for research agents" \
--provider local_encrypted \
--company-id <id>
{baseDir}/scripts/paperclip.sh secret update <secret-id> \
--name "OPENAI_API_KEY" \
--description "Updated description"
{baseDir}/scripts/paperclip.sh secret rotate <secret-id> --value "sk-new..."
{baseDir}/scripts/paperclip.sh secret delete <secret-id> --company-id <id>
Secret providers: local_encrypted, aws_secrets_manager, gcp_secret_manager, vault
Secret fields
| Field | Type | Create | Update | Notes |
|---|
name | string | required | optional | Secret name |
value | string | required | -- | Secret value (use rotate to change) |
provider | enum | optional | -- | Storage backend (default: local_encrypted) |
description | string | optional | optional | |
externalRef | string | optional | optional | Reference in external secret manager |
Company Import & Export (Portability)
{baseDir}/scripts/paperclip.sh company export <company-id>
{baseDir}/scripts/paperclip.sh company export-preview <company-id>
{baseDir}/scripts/paperclip.sh company import --data '{"bundleJson":{...},"target":{"mode":"new_company"},"collisionStrategy":"merge"}'
{baseDir}/scripts/paperclip.sh company import-preview --data '{"bundleJson":{...},"target":{"mode":"new_company"}}'
{baseDir}/scripts/paperclip.sh company safe-import-preview <company-id> --data '...'
{baseDir}/scripts/paperclip.sh company safe-import <company-id> --data '...'
Import collision strategies: merge, skip, replace (replace forbidden for CEO-level safe imports)
Import target modes: new_company, existing_company
Plugin Webhooks
Plugins can receive external webhooks at a public endpoint (no auth required):
POST /api/plugins/:pluginId/webhooks/:endpointKey
Prerequisites: Plugin must be in "ready" status and declare webhooks.receive capability. Endpoint must be declared in plugin manifest. Every delivery is recorded with full payload, headers, status, and timing.
WebSocket Live Events
Real-time event stream for company-wide events:
ws(s)://PAPERCLIP_API_URL/api/companies/:companyId/events/ws
Authentication
- Agent API key:
?token=<api_key> query param or Authorization: Bearer <api_key> header
- Board session: Session cookie (browser)
- Local trusted mode: No auth required
Event Types
| Event | Description |
|---|
heartbeat.run.queued | Run entered queue |
heartbeat.run.status | Run status changed |
heartbeat.run.event | Run event occurred |
heartbeat.run.log | Log output from run |
agent.status | Agent status changed |
activity.logged | Activity event recorded |
plugin.ui.updated | Plugin UI updated |
plugin.worker.crashed | Plugin worker crashed |
plugin.worker.restarted | Plugin worker restarted |
Event payload: {"id": <num>, "companyId": "...", "type": "<event-type>", "createdAt": "...", "payload": {...}}
Server sends ping every 30s -- clients must respond with pong or connection terminates.
Dashboard, Activity & Org Chart
{baseDir}/scripts/paperclip.sh dashboard --company-id <id>
{baseDir}/scripts/paperclip.sh activity --company-id <id>
{baseDir}/scripts/paperclip.sh activity --agent-id <id> --company-id <id>
{baseDir}/scripts/paperclip.sh activity --entity-type issue --action created --company-id <id>
{baseDir}/scripts/paperclip.sh org --company-id <id>
{baseDir}/scripts/paperclip.sh health
Concepts
- Company = autonomous AI business with a mission, org chart, and budget
- Agent = AI employee (Claude, Codex, Gemini, etc.) with a role, title, adapter, instructions, and skills
- Goal = hierarchical objective (company → team → agent → task)
- Project = scoped work with a lead agent, target date, linked goals, and workspaces
- Issue = unit of work (ticket). Has status, priority, assignee, parent/children, documents, labels
- Routine = recurring task generator with triggers (cron/webhook/API), concurrency policies, and variables
- Trigger = schedule/webhook/API definition that fires a routine
- Approval = governance gate. Agent hires and CEO strategy require Board approval
- Workspace = execution environment for a project (local path, git repo, or remote managed)
- Cost event = token usage tracked per agent, per issue, per project, per goal, per biller
- Budget policy = spending limit with auto-pause enforcement
- Feedback = thumbs up/down votes and execution traces on issues for quality tracking
- Attachment = file attached to an issue (images, PDFs, etc., max 10MB)
- Plugin = extensible integration that can receive webhooks and provide UI widgets
- Live Events = real-time WebSocket stream of company-wide events (run status, agent status, activity)
Task Hierarchy
Goal (company) → Goal (team) → Project → Issue → Sub-issue
All work traces back to company goals for alignment and cost attribution.
Workflow: Creating a Company
company create -- creates the company
goal create --level company -- set the company mission goal
agent hire for the CEO -- triggers Board approval
approval approve -- Board approves the hire
project create -- create a project linked to the goal
routine create + trigger create -- set up recurring tasks
issue create -- give the CEO their first strategic task
- CEO breaks it down into sub-issues, hires team, delegates
Auth
Paperclip exposes three authentication paths (per API Authentication docs):
| Path | When to use | Header |
|---|
| Board API key (bearer) | Scripted access, CI, our wrapper | Authorization: Bearer pcp_board_<token> |
| Session cookie | Browser UI, manual curl in trusted mode | Cookie sent via -b cookies.txt |
| Agent API key / run JWT | Agent-side calls (heartbeats) | Authorization: Bearer <agent-key> |
Bearer always wins over cookies — a request with a valid bearer token is not treated as a session request, even if a cookie is also sent.
Minting a board API key
Use the official CLI device-code flow (CLI Auth docs):
paperclipai auth login
paperclipai auth login --instance-admin
paperclipai auth login --api-base https://...
The CLI opens a browser for approval and stores a pcp_board_* token. Export it as PAPERCLIP_API_TOKEN for our wrapper.
Mutation requests still need Origin
CSRF protection requires the Origin header on all mutating requests (POST/PATCH/DELETE), regardless of auth method. The wrapper sets it automatically. Without it: {"error":"Board mutation requires trusted browser origin"}.
Agent-authenticated mutations need X-Paperclip-Run-Id
When an agent posts to a checked-out issue (comment, status update), the request must include X-Paperclip-Run-Id: <current-run-id>. The server uses it to verify checkout ownership; without it, mutations are rejected as ownership violations.
Fallback: email + password sign-in
If you can't mint a bearer token (e.g. local dev, no paperclipai CLI), the wrapper falls back to POST /api/auth/sign-in/email (BetterAuth) and uses the returned session cookie. The session token returned in that response is not a board API key — it only works as a cookie, not as a bearer header. Set PAPERCLIP_EMAIL and PAPERCLIP_PASSWORD.
⚠️ Rate limit: the sign-in endpoint returns 429 after roughly 10 fresh sign-ins per minute. Since the wrapper signs in per CLI call, a tight loop (e.g. fan-out reads across many resources) will trip it almost immediately. Use the bearer path instead.
Critical Rules
- Always assign issues on creation. Set
assigneeAgentId in the POST body. An unassigned issue is invisible to agents -- they only see issues assigned to them. Never create an issue and assign it in a separate step; do it atomically in the POST.
- Set status
todo, not backlog. Only todo issues trigger agent invocation. backlog is planning-only -- the agent will never pick it up. Default is backlog, so always pass --status todo when you want the agent to act immediately.
- List agents before creating issues if you don't have the agent ID cached:
agent list --company-id <id>.
- Routines are not dispatchers.
assigneeAgentId + status>=todo already wakes the assigned agent. A paused "patrol" routine never causes a backlog of assigned issues — only schedule-triggered work (audits, digests) is affected. Don't recommend re-enabling routines to "unblock the review queue" — diagnose the assignee or status instead.
- Use a bearer token for scripted access. The cookie sign-in endpoint rate-limits at ~10 fresh sign-ins per minute, and our wrapper signs in per call. Mint a board API key via
paperclipai auth login and export it as PAPERCLIP_API_TOKEN. Cookie auth stays only as a fallback for trusted-mode local dev.
API Notes
- All responses are JSON
- All endpoints are under
/api/ prefix
- Issues support single-assignee atomic checkout (prevents double-work)
- Costs are tracked per agent, per issue, per project, per goal
- Budget policies auto-pause agents when spending limits are exceeded
- Routines use separate trigger objects (schedule/webhook/api) instead of inline cron
- Routines support concurrency policies and parameterized variables
- Agents have managed instruction bundles with versioned files
- Agents can have skills synced via the skills-sync endpoint
- Company export/import enables portability and templating
- Secret rotation creates new versions (audit trail)
- Issue execution history is per-issue (
/issues/:id/runs), not per-agent
- Real-time events available via WebSocket (
/companies/:id/events/ws)
- Feedback votes and traces track issue quality and agent performance
- Budgets can be managed at company level and per-agent level
- Plugin webhooks are public endpoints (no auth) -- plugins must declare the capability
Praktische Erkenntnisse aus echten Instanzen
Siehe references/api-notes.md für:
- Cookie-Auth vs Bearer Token (was wirklich funktioniert)
- CSRF: Origin Header Pflicht bei allen Mutations
- Skills: zweistufig (Company Catalog → Agent Assignment)
- Secrets: Company-scoped, kein Agent-Level
- Private Repos: funktionieren nicht ohne Token
- Was fehlt / nicht funktioniert