# Paperclip API

> 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.

- Skill: `yesterday-ai/paperclip-api` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add yesterday-ai/paperclip-api`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yesterday-ai/paperclip-api/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Yesterday-AI (https://skillmd.com/u/yesterday-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yesterday-ai/paperclip-api

---


# 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.

```bash
# Create a routine (cron-based)
paperclipai routine create --name "Daily Standup" --schedule "0 9 * * *" --assignee-agent-id <agent-id> --company-id <id>

# Trigger manually
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

```yaml
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`:

```bash
# Import a template directly from GitHub
npx companies.sh add paperclipai/companies/gstack
paperclipai company import org/repo/companies/acme

# Export your own company as template
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

| Plugin | What it does |
|---|---|
| [paperclip-plugin-acp](https://github.com/mvanhorn/paperclip-plugin-acp) | ACP runtime -- run Claude Code, Codex, Gemini CLI from any chat platform |
| [paperclip-plugin-github-issues](https://github.com/mvanhorn/paperclip-plugin-github-issues) | Bidirectional GitHub Issues sync |
| [paperclip-plugin-discord](https://github.com/mvanhorn/paperclip-plugin-discord) | Discord notifications + slash commands |
| [paperclip-plugin-slack](https://github.com/mvanhorn/paperclip-plugin-slack) | Slack notifications |
| [paperclip-plugin-telegram](https://github.com/mvanhorn/paperclip-plugin-telegram) | Telegram notifications |
| [paperclip-plugin-chat](https://github.com/webprismdevin/paperclip-plugin-chat) | Interactive AI chat copilot for tasks/agents/workspaces |
| [paperclip-plugin-avp](https://github.com/creatorrmaster-lead/paperclip-plugin-avp) | Trust layer: DID identity, EigenTrust reputation, signed attestations |
| [paperclip-plugin-company-wizard](https://github.com/yesterday-ai/paperclip-plugin-company-wizard) | AI-powered company setup assistant with presets |

### Tools

| Tool | What it does |
|---|---|
| [oh-my-paperclip](https://github.com/gsxdsm/oh-my-paperclip) | Plugin bundle -- the oh-my-zsh for Paperclip |
| [paperclip-aperture](https://github.com/tomismeta/paperclip-aperture) | Alternative Focus view -- ranks approvals, activity into now/next/ambient |
| [paperclip-discord-bot](https://github.com/rekon307/paperclip-discord-bot) | Discord community bot with GitHub OAuth and AI summaries |

### Learning

- [Headcount Zero](https://github.com/AnthonyDavidAdams/zero-employee-company-book) -- open-source book: *How to Build an AI-Run Company with Paperclip*

## Quick Reference

All commands go through the wrapper script:

```bash
{baseDir}/scripts/paperclip.sh <command> [subcommand] [options]
```

### Environment

**Recommended (bearer token — no rate limit):**

```bash
export PAPERCLIP_API_URL="https://your-instance.up.railway.app"
export PAPERCLIP_API_TOKEN="pcp_board_..."   # mint via `paperclipai auth login`
export PAPERCLIP_COMPANY_ID="optional-default-company-id"
```

**Fallback (email + password — rate-limited at ~10 sign-ins/min):**

```bash
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

```bash
# List all companies
{baseDir}/scripts/paperclip.sh company list

# Create a company
{baseDir}/scripts/paperclip.sh company create "Atlas Corp" "Map every job in DACH"

# Get company details
{baseDir}/scripts/paperclip.sh company get <company-id>

# Update company (all flags optional)
{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"

# Archive company
{baseDir}/scripts/paperclip.sh company archive <company-id>

# Delete company
{baseDir}/scripts/paperclip.sh company delete <company-id>

# Export company (portability)
{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

```bash
# List agents in a company
{baseDir}/scripts/paperclip.sh agent list --company-id <id>

# Create an agent directly
{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>

# Hire via governance (triggers Board approval)
{baseDir}/scripts/paperclip.sh agent hire "Research Lead" "researcher" \
  --title "Senior Researcher" \
  --adapter claude_local \
  --company-id <id>

# Get agent details
{baseDir}/scripts/paperclip.sh agent get <agent-id>

# Update agent (all flags optional)
{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

# Delete agent permanently
{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

```bash
{baseDir}/scripts/paperclip.sh agent pause <agent-id>
{baseDir}/scripts/paperclip.sh agent resume <agent-id>
{baseDir}/scripts/paperclip.sh agent terminate <agent-id>

# Wakeup with optional parameters
{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

```bash
# Get current configuration
{baseDir}/scripts/paperclip.sh agent config <agent-id>

# List config revisions (audit trail)
{baseDir}/scripts/paperclip.sh agent config-revisions <agent-id>

# Rollback to a specific revision
{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).

```bash
# Get instructions bundle metadata + file list
{baseDir}/scripts/paperclip.sh agent instructions-get <agent-id>

# Get a specific instructions file content
{baseDir}/scripts/paperclip.sh agent instructions-file <agent-id> --path "AGENTS.md"

# Write/update an instructions file
{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

# Delete an instructions file
{baseDir}/scripts/paperclip.sh agent instructions-delete <agent-id> --path "AGENTS.md"

# Update bundle settings (mode, root path, entry file)
{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

```bash
# Get current skills snapshot
{baseDir}/scripts/paperclip.sh agent skills <agent-id>

# Sync skills (set desired skill list)
{baseDir}/scripts/paperclip.sh agent skills-sync <agent-id> --skills "web-design,qa,github-workflow"
```

### Agent API Keys

```bash
# List keys
{baseDir}/scripts/paperclip.sh agent keys <agent-id>

# Create a new key (token shown only once!)
{baseDir}/scripts/paperclip.sh agent keys-create <agent-id> --name "production-key"

# Revoke a key
{baseDir}/scripts/paperclip.sh agent keys-revoke <agent-id> <key-id>
```

### Agent Runtime & Sessions

```bash
# Get runtime state (current execution, sessions)
{baseDir}/scripts/paperclip.sh agent runtime-state <agent-id>

# List task sessions
{baseDir}/scripts/paperclip.sh agent task-sessions <agent-id>

# Reset session
{baseDir}/scripts/paperclip.sh agent reset-session <agent-id>
{baseDir}/scripts/paperclip.sh agent reset-session <agent-id> --task-key "some-task"

# Invoke heartbeat manually
{baseDir}/scripts/paperclip.sh agent heartbeat <agent-id>
```

### Adapter Discovery

```bash
# List available models for an adapter type
{baseDir}/scripts/paperclip.sh adapter models <adapter-type> --company-id <id>

# Detect current model for adapter
{baseDir}/scripts/paperclip.sh adapter detect-model <adapter-type> --company-id <id>

# Test adapter environment
{baseDir}/scripts/paperclip.sh adapter test-env <adapter-type> --company-id <id>
```

---

## Projects

### Basic CRUD

```bash
# List projects
{baseDir}/scripts/paperclip.sh project list --company-id <id>

# Create project
{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>

# Get project details
{baseDir}/scripts/paperclip.sh project get <project-id>

# Update project (all flags optional)
{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"

# Delete project
{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).

```bash
# List workspaces for a project
{baseDir}/scripts/paperclip.sh workspace list <project-id>

# Create workspace
{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"

# Update workspace
{baseDir}/scripts/paperclip.sh workspace update <project-id> <workspace-id> \
  --repo-ref "develop" \
  --setup-command "pnpm install && pnpm build"

# Delete workspace
{baseDir}/scripts/paperclip.sh workspace delete <project-id> <workspace-id>

# Control workspace runtime services
{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

```bash
# List goals
{baseDir}/scripts/paperclip.sh goal list --company-id <id>

# Create goal (hierarchical)
{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>

# Get goal details
{baseDir}/scripts/paperclip.sh goal get <goal-id>

# Update goal (all flags optional)
{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>

# Delete goal
{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

```bash
# List issues (many filters available)
{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>

# Create an issue
# ⚠️ REQUIRED: Always set --assignee-agent-id. An issue without an assignee
# will never be picked up -- agents only process issues assigned to them.
# If unsure who to assign, list agents first: issue list + agent list.
{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>

# Create a sub-issue
{baseDir}/scripts/paperclip.sh issue create --title "WEG-Hausverwaltungen" \
  --parent-id <parent-issue-id> --company-id <id>

# Get issue details
{baseDir}/scripts/paperclip.sh issue get <issue-id>

# Update issue (all flags optional)
{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"

# Delete issue
{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.

```bash
# List comments (supports pagination)
{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

# Get a single comment
{baseDir}/scripts/paperclip.sh issue comment-get <issue-id> <comment-id>

# Add comment
{baseDir}/scripts/paperclip.sh issue comment <issue-id> --body "Research complete, 25 Spielwiesen identified"

# Comment that reopens a done/cancelled issue
{baseDir}/scripts/paperclip.sh issue comment <issue-id> --body "Found another category" --reopen

# Comment that interrupts the current agent execution
{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)

```bash
# Checkout (lock for agent -- prevents double-work)
{baseDir}/scripts/paperclip.sh issue checkout <issue-id> --agent-id <agent-id>

# Release (unlock)
{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).

```bash
# List documents on an issue
{baseDir}/scripts/paperclip.sh issue documents <issue-id>

# Get document content by key
{baseDir}/scripts/paperclip.sh issue document-get <issue-id> <key>

# Create or update document (upsert)
{baseDir}/scripts/paperclip.sh issue document-set <issue-id> <key> \
  --title "Research Plan" \
  --body "# Plan\n\n1. Identify segments\n2. ..."

# List document revisions
{baseDir}/scripts/paperclip.sh issue document-revisions <issue-id> <key>

# Restore a revision
{baseDir}/scripts/paperclip.sh issue document-restore <issue-id> <key> <revision-id>

# Delete document
{baseDir}/scripts/paperclip.sh issue document-delete <issue-id> <key>
```

### Issue Labels

```bash
# List company labels
{baseDir}/scripts/paperclip.sh label list --company-id <id>

# Create label
{baseDir}/scripts/paperclip.sh label create --name "urgent" --color "#EF4444" --company-id <id>

# Delete label
{baseDir}/scripts/paperclip.sh label delete <label-id>
```

### Issue Attachments

```bash
# List attachments on an issue
{baseDir}/scripts/paperclip.sh issue attachments <issue-id>

# Upload attachment (multipart/form-data, max 10MB)
{baseDir}/scripts/paperclip.sh issue attachment-upload <issue-id> --file ./report.pdf --company-id <id>

# Download attachment content
{baseDir}/scripts/paperclip.sh issue attachment-content <attachment-id>

# Delete attachment
{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)

```bash
# List all execution runs for an issue
{baseDir}/scripts/paperclip.sh issue runs <issue-id>

# List currently active (queued/running) runs
{baseDir}/scripts/paperclip.sh issue live-runs <issue-id>

# Get the single active run (or null)
{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

```bash
# List feedback votes on an issue
{baseDir}/scripts/paperclip.sh issue feedback-votes <issue-id>

# Submit feedback vote (thumbs up/down)
{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"

# List feedback traces on an issue
{baseDir}/scripts/paperclip.sh issue feedback-traces <issue-id>

# Get feedback trace details / bundle
{baseDir}/scripts/paperclip.sh feedback-trace get <trace-id>
{baseDir}/scripts/paperclip.sh feedback-trace bundle <trace-id>

# List company-wide feedback traces (filterable)
{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

```bash
# Mark issue as read
{baseDir}/scripts/paperclip.sh issue mark-read <issue-id>

# Mark issue as unread
{baseDir}/scripts/paperclip.sh issue mark-unread <issue-id>

# Archive from inbox
{baseDir}/scripts/paperclip.sh issue inbox-archive <issue-id>

# Unarchive from inbox
{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](https://docs.paperclip.ing/reference/api/issues.md). 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:

```text
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

```bash
# Wrong — rejected by the server
issue update <id> --status todo            # done/cancelled is terminal

# Right — only reopen unlocks terminal status
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:

```bash
issue update <id> --label-ids "..."
# Direct API call needed for blockedByIssueIds (not yet exposed via wrapper):
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.

```bash
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

```bash
# List routines
{baseDir}/scripts/paperclip.sh routine list --company-id <id>

# Create routine (title + projectId + assigneeAgentId required)
{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>

# Get routine details (includes triggers, recent runs)
{baseDir}/scripts/paperclip.sh routine get <routine-id>

# Update routine (all flags optional)
{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

# Delete routine
{baseDir}/scripts/paperclip.sh routine delete <routine-id>

# Manually trigger routine
{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:

```bash
{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.

```bash
# Create a cron schedule trigger
{baseDir}/scripts/paperclip.sh trigger create <routine-id> \
  --kind schedule \
  --cron "0 9 * * *" \
  --timezone "Europe/Berlin" \
  --label "Daily 9am Berlin"

# Create a webhook trigger
{baseDir}/scripts/paperclip.sh trigger create <routine-id> \
  --kind webhook \
  --label "GitHub Push Hook" \
  --signing-mode hmac_sha256

# Create an API trigger (callable via POST /routines/{id}/run)
{baseDir}/scripts/paperclip.sh trigger create <routine-id> \
  --kind api \
  --label "Manual API"

# Update trigger (all flags optional)
{baseDir}/scripts/paperclip.sh trigger update <trigger-id> \
  --enabled false \
  --cron "0 */6 * * *"

# Delete trigger
{baseDir}/scripts/paperclip.sh trigger delete <trigger-id>

# Rotate webhook secret
{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

```bash
# List recent runs
{baseDir}/scripts/paperclip.sh routine runs <routine-id>
```

Run statuses: `received`, `coalesced`, `skipped`, `issue_created`, `completed`, `failed`

---

## Approvals (Governance)

```bash
# List pending approvals
{baseDir}/scripts/paperclip.sh approval list --status pending --company-id <id>

# Get approval details
{baseDir}/scripts/paperclip.sh approval get <approval-id>

# Approve / reject
{baseDir}/scripts/paperclip.sh approval approve <approval-id> --note "Looks good"
{baseDir}/scripts/paperclip.sh approval reject <approval-id> --note "Too expensive"

# Request revision / resubmit
{baseDir}/scripts/paperclip.sh approval request-revision <approval-id> --note "Needs more detail on budget"
{baseDir}/scripts/paperclip.sh approval resubmit <approval-id>

# Comment on approval
{baseDir}/scripts/paperclip.sh approval comment <approval-id> --body "What's the expected ROI?"

# List linked issues
{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

```bash
# Company cost summary (optional --from/--to ISO dates)
{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>

# Cost breakdowns
{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>

# Finance reports
{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

# C

…(truncated)
