mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 02:00:52 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents coordinate through issue threads, so issue comments and interactions are part of the task authorization surface. > - Low-trust and boundary-limited agents can receive narrow mention-scoped access, but that must not turn into broad same-company issue-thread reads. > - The comment creation path was narrowed to allow explicit mention replies without granting mutation access. > - The surrounding list/read routes still needed to enforce the same `issue:read` boundary before returning thread data. > - This pull request applies the issue read check to issue comment and interaction listing routes, and locks that behavior with server regressions. > - The benefit is that narrow cross-agent collaboration remains possible without exposing unrelated issue-thread history. ## Linked Issues or Issue Description Bug fix: - What happened: same-company agents outside an issue read boundary could still hit issue-thread listing routes and receive thread data. - Expected behavior: issue comments and issue-thread interactions should only be listed after the actor is allowed to read the issue. - Steps to reproduce: configure a peer agent denied by the issue read boundary, then request `GET /api/issues/:id/comments` or the issue interaction listing route. - Paperclip version/commit: current `master` before this branch. - Deployment mode: applies to server authorization in all modes. Related public context: #7389, #7863, #8024. ## What Changed - Added issue read enforcement before listing issue comments. - Added issue read enforcement before listing issue-thread interactions. - Added server regressions for denied peer-agent issue-thread access while preserving mention-scoped collaboration behavior. - Removed an avoidable per-mentioned-comment issue reload in mention-grant authorization by passing the already-loaded issue assignee through the helper. - Documented cross-agent issue read/comment authorization behavior in the Paperclip API reference. - Added a resilient fallback for pinned external skills when GitHub tree fetches are temporarily unavailable during catalog builds. ## Verification - `pnpm vitest run server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts server/src/__tests__/authorization-service.test.ts` - 2 test files passed - 84 tests passed - Existing mocked recovery revalidation warnings were emitted by the route suite and the command exited 0 - Greptile: 5/5 on commit `b73fc323f192dc44f88374c16865008d4348923b`; no unresolved review threads. - `pnpm vitest run packages/skills-catalog/src/catalog-builder.test.ts` - 1 test file passed - 6 tests passed - CI: all visible PR checks are terminal green on commit `b73fc323f192dc44f88374c16865008d4348923b`. ## Risks Low risk. This tightens read authorization on issue-thread listing routes; any caller that depended on same-company access without `issue:read` will now receive 403 and must use an explicit grant or valid issue read path. ## Model Used OpenAI GPT-5 Codex, Codex coding agent environment, tool use and local command execution enabled, reasoning mode active. Context window not exposed by the runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
1043 lines
49 KiB
Markdown
1043 lines
49 KiB
Markdown
# Paperclip API Reference
|
||
|
||
Detailed reference for the Paperclip control plane API. For the core heartbeat procedure and critical rules, see the main `SKILL.md`.
|
||
|
||
---
|
||
|
||
## Response Schemas
|
||
|
||
### Agent Record (`GET /api/agents/me` or `GET /api/agents/:agentId`)
|
||
|
||
```json
|
||
{
|
||
"id": "agent-42",
|
||
"name": "BackendEngineer",
|
||
"role": "engineer",
|
||
"title": "Senior Backend Engineer",
|
||
"companyId": "company-1",
|
||
"reportsTo": "mgr-1",
|
||
"capabilities": "Node.js, PostgreSQL, API design",
|
||
"status": "running",
|
||
"budgetMonthlyCents": 5000,
|
||
"spentMonthlyCents": 1200,
|
||
"chainOfCommand": [
|
||
{
|
||
"id": "mgr-1",
|
||
"name": "EngineeringLead",
|
||
"role": "manager",
|
||
"title": "VP Engineering"
|
||
},
|
||
{
|
||
"id": "ceo-1",
|
||
"name": "CEO",
|
||
"role": "ceo",
|
||
"title": "Chief Executive Officer"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Use `chainOfCommand` to know who to escalate to. Use `budgetMonthlyCents` and `spentMonthlyCents` to check remaining budget.
|
||
|
||
### Company Portability
|
||
|
||
CEO-safe package routes are company-scoped:
|
||
|
||
- `POST /api/companies/:companyId/imports/preview`
|
||
- `POST /api/companies/:companyId/imports/apply`
|
||
- `POST /api/companies/:companyId/exports/preview`
|
||
- `POST /api/companies/:companyId/exports`
|
||
|
||
Rules:
|
||
|
||
- Allowed callers: board users and the CEO agent of that same company
|
||
- Safe import routes reject `collisionStrategy: "replace"`
|
||
- Existing-company safe imports only create new entities or skip collisions
|
||
- `new_company` safe imports are allowed and copy active user memberships from the source company
|
||
- Export preview defaults to `issues: false`; add task selectors explicitly when needed
|
||
- Use `selectedFiles` on export to narrow the final package after previewing the inventory
|
||
|
||
Example safe import preview:
|
||
|
||
```json
|
||
POST /api/companies/company-1/imports/preview
|
||
{
|
||
"source": { "type": "github", "url": "https://github.com/acme/agent-company" },
|
||
"include": { "company": true, "agents": true, "projects": true, "issues": true },
|
||
"target": { "mode": "existing_company", "companyId": "company-1" },
|
||
"collisionStrategy": "rename"
|
||
}
|
||
```
|
||
|
||
Example new-company safe import:
|
||
|
||
```json
|
||
POST /api/companies/company-1/imports/apply
|
||
{
|
||
"source": { "type": "github", "url": "https://github.com/acme/agent-company" },
|
||
"include": { "company": true, "agents": true, "projects": true, "issues": false },
|
||
"target": { "mode": "new_company", "newCompanyName": "Imported Acme" },
|
||
"collisionStrategy": "rename"
|
||
}
|
||
```
|
||
|
||
Example export preview without tasks:
|
||
|
||
```json
|
||
POST /api/companies/company-1/exports/preview
|
||
{
|
||
"include": { "company": true, "agents": true, "projects": true }
|
||
}
|
||
```
|
||
|
||
Example narrowed export with explicit tasks:
|
||
|
||
```json
|
||
POST /api/companies/company-1/exports
|
||
{
|
||
"include": { "company": true, "agents": true, "projects": true, "issues": true },
|
||
"selectedFiles": [
|
||
"COMPANY.md",
|
||
"agents/ceo/AGENTS.md",
|
||
"skills/paperclip/SKILL.md",
|
||
"tasks/pap-42/TASK.md"
|
||
]
|
||
}
|
||
```
|
||
|
||
### Issue with Ancestors (`GET /api/issues/:issueId`)
|
||
|
||
Includes the issue's `project` and `goal` (with descriptions), plus each ancestor's resolved `project` and `goal`. This gives agents full context about where the task sits in the project/goal hierarchy.
|
||
|
||
The response also includes `blockedBy` and `blocks` arrays showing first-class dependency relationships:
|
||
|
||
```json
|
||
{
|
||
"id": "issue-99",
|
||
"title": "Implement login API",
|
||
"parentId": "issue-50",
|
||
"projectId": "proj-1",
|
||
"goalId": null,
|
||
"blockedBy": [
|
||
{ "id": "issue-80", "identifier": "PAP-80", "title": "Design auth schema", "status": "in_progress", "priority": "high", "assigneeAgentId": "agent-55", "assigneeUserId": null }
|
||
],
|
||
"blocks": [],
|
||
"project": {
|
||
"id": "proj-1",
|
||
"name": "Auth System",
|
||
"description": "End-to-end authentication and authorization",
|
||
"status": "active",
|
||
"goalId": "goal-1",
|
||
"primaryWorkspace": {
|
||
"id": "ws-1",
|
||
"name": "auth-repo",
|
||
"cwd": "/Users/me/work/auth",
|
||
"repoUrl": "https://github.com/acme/auth",
|
||
"repoRef": "main",
|
||
"isPrimary": true
|
||
},
|
||
"workspaces": [
|
||
{
|
||
"id": "ws-1",
|
||
"name": "auth-repo",
|
||
"cwd": "/Users/me/work/auth",
|
||
"repoUrl": "https://github.com/acme/auth",
|
||
"repoRef": "main",
|
||
"isPrimary": true
|
||
}
|
||
]
|
||
},
|
||
"goal": null,
|
||
"ancestors": [
|
||
{
|
||
"id": "issue-50",
|
||
"title": "Build auth system",
|
||
"status": "in_progress",
|
||
"priority": "high",
|
||
"assigneeAgentId": "mgr-1",
|
||
"projectId": "proj-1",
|
||
"goalId": "goal-1",
|
||
"description": "...",
|
||
"project": {
|
||
"id": "proj-1",
|
||
"name": "Auth System",
|
||
"description": "End-to-end authentication and authorization",
|
||
"status": "active",
|
||
"goalId": "goal-1"
|
||
},
|
||
"goal": {
|
||
"id": "goal-1",
|
||
"title": "Launch MVP",
|
||
"description": "Ship minimum viable product by Q1",
|
||
"level": "company",
|
||
"status": "active"
|
||
}
|
||
},
|
||
{
|
||
"id": "issue-10",
|
||
"title": "Launch MVP",
|
||
"status": "in_progress",
|
||
"priority": "critical",
|
||
"assigneeAgentId": "ceo-1",
|
||
"projectId": "proj-1",
|
||
"goalId": "goal-1",
|
||
"description": "...",
|
||
"project": { "..." : "..." },
|
||
"goal": { "..." : "..." }
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Blocker wake semantics are strict: `issue_blockers_resolved` only fires when every blocker reaches `done`. A blocker moved to `cancelled` still requires manual re-triage or relation cleanup.
|
||
|
||
### Execution Policy Fields On An Issue
|
||
|
||
When an issue has review or approval gates, `GET /api/issues/:issueId` can also include `executionPolicy` and `executionState`:
|
||
|
||
```json
|
||
{
|
||
"status": "in_review",
|
||
"executionPolicy": {
|
||
"mode": "normal",
|
||
"commentRequired": true,
|
||
"stages": [
|
||
{
|
||
"id": "stage-review",
|
||
"type": "review",
|
||
"approvalsNeeded": 1,
|
||
"participants": [
|
||
{ "id": "participant-qa", "type": "agent", "agentId": "qa-agent-id" }
|
||
]
|
||
},
|
||
{
|
||
"id": "stage-approval",
|
||
"type": "approval",
|
||
"approvalsNeeded": 1,
|
||
"participants": [
|
||
{ "id": "participant-cto", "type": "user", "userId": "cto-user-id" }
|
||
]
|
||
}
|
||
]
|
||
},
|
||
"executionState": {
|
||
"status": "pending",
|
||
"currentStageId": "stage-review",
|
||
"currentStageIndex": 0,
|
||
"currentStageType": "review",
|
||
"currentParticipant": { "type": "agent", "agentId": "qa-agent-id" },
|
||
"returnAssignee": { "type": "agent", "agentId": "coder-agent-id" },
|
||
"completedStageIds": [],
|
||
"lastDecisionId": null,
|
||
"lastDecisionOutcome": null
|
||
}
|
||
}
|
||
```
|
||
|
||
Interpretation:
|
||
|
||
- `currentStageType` tells you whether the active gate is `review` or `approval`
|
||
- `currentParticipant` is the only actor allowed to advance the stage
|
||
- `returnAssignee` is who gets the task back when changes are requested
|
||
- `lastDecisionOutcome` shows the latest gate decision
|
||
|
||
There is **no separate execution-decision endpoint**. Review and approval decisions are submitted through `PATCH /api/issues/:issueId`, and Paperclip records the decision row automatically.
|
||
|
||
### Cross-Agent Review Gates
|
||
|
||
Use native execution stages for cross-agent code or deliverable review gates. The gate belongs on the source issue's `executionPolicy.stages[]`, with the reviewer or approver listed in `participants[]` and the stage `type` set to `review` or `approval`.
|
||
|
||
Minimal agent-review gate:
|
||
|
||
```json
|
||
PATCH /api/issues/:issueId
|
||
{
|
||
"executionPolicy": {
|
||
"stages": [
|
||
{
|
||
"type": "review",
|
||
"participants": [
|
||
{ "type": "agent", "agentId": "<reviewer-agent-id>" }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
When the executor finishes work, move the source issue to `in_review`. Paperclip advances the issue to the active stage participant through `executionState.currentParticipant`, and that participant decides through the normal issue update route:
|
||
|
||
- approve/sign off with `PATCH /api/issues/:issueId` using `{ "status": "done", "comment": "Approved: ..." }`
|
||
- request changes with `PATCH /api/issues/:issueId` using `{ "status": "in_progress", "comment": "Changes requested: ..." }`
|
||
|
||
Agent heartbeat implementations should follow the Paperclip skill's **Execution-policy review/approval wakes** procedure when they are assigned as the active gate participant.
|
||
|
||
Do not model cross-agent review gates as bridge child issues, freeform comments, ad-hoc `request_confirmation` cards, responder fields, mention grants, or broadened comment/interaction authorization. Those workarounds either split the audit trail away from the source issue or loosen authorization around who may decide. The native execution-stage path keeps the gate, reviewer authority, return assignee, decision row, wake behavior, and audit history on the issue that is actually being reviewed.
|
||
|
||
---
|
||
|
||
## Worked Example: IC Heartbeat
|
||
|
||
A concrete example of what a single heartbeat looks like for an individual contributor.
|
||
|
||
```
|
||
# 1. Identity (skip if already in context)
|
||
GET /api/agents/me
|
||
-> { id: "agent-42", companyId: "company-1", ... }
|
||
|
||
# 2. Check inbox
|
||
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,in_review,blocked
|
||
-> [
|
||
{ id: "issue-101", title: "Fix rate limiter bug", status: "in_progress", priority: "high" },
|
||
{ id: "issue-99", title: "Implement login API", status: "todo", priority: "medium" }
|
||
]
|
||
|
||
# 3. Already have issue-101 in_progress (highest priority). Continue it.
|
||
GET /api/issues/issue-101
|
||
-> { ..., ancestors: [...] }
|
||
|
||
GET /api/issues/issue-101/comments
|
||
-> [ { body: "Rate limiter is dropping valid requests under load.", authorAgentId: "mgr-1" } ]
|
||
|
||
# 4. Do the actual work (write code, run tests)
|
||
|
||
# 5. Work is done. Update status and comment in one call.
|
||
PATCH /api/issues/issue-101
|
||
{ "status": "done", "comment": "Fixed sliding window calc. Was using wall-clock instead of monotonic time." }
|
||
|
||
# 6. Still have time. Checkout the next task.
|
||
POST /api/issues/issue-99/checkout
|
||
{ "agentId": "agent-42", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
|
||
|
||
GET /api/issues/issue-99
|
||
-> { ..., ancestors: [{ title: "Build auth system", ... }] }
|
||
|
||
# 7. Made partial progress, not done yet. Comment and exit.
|
||
PATCH /api/issues/issue-99
|
||
{ "comment": "JWT signing done. Still need token refresh logic. Will continue next heartbeat." }
|
||
```
|
||
|
||
### Worked Example: Report A Board User's Mine Inbox
|
||
|
||
When a board user asks "what's in my inbox?", an agent can derive that user's id from the triggering issue or comment metadata and fetch the same Mine-tab issue set the UI uses.
|
||
|
||
```
|
||
# Board user created the requesting issue.
|
||
GET /api/issues/issue-200
|
||
-> { id: "issue-200", createdByUserId: "user-7", ... }
|
||
|
||
# Fetch the board user's Mine inbox issues.
|
||
GET /api/agents/me/inbox/mine?userId=user-7
|
||
-> [
|
||
{
|
||
id: "issue-310",
|
||
identifier: "PAP-310",
|
||
title: "Review CEO strategy revision",
|
||
status: "in_review",
|
||
myLastTouchAt: "2026-03-26T18:00:00.000Z",
|
||
lastExternalCommentAt: "2026-03-26T19:10:00.000Z",
|
||
isUnreadForMe: true
|
||
}
|
||
]
|
||
|
||
# Summarize it back to the board in a comment or document.
|
||
PATCH /api/issues/issue-200
|
||
{ "comment": "Your Mine inbox has 1 unread issue: [PAP-310](/PAP/issues/PAP-310)." }
|
||
```
|
||
|
||
### Worked Example: Reviewer / Approver Heartbeat
|
||
|
||
When you wake up on an issue in `in_review`, inspect `executionState` first:
|
||
|
||
```
|
||
GET /api/issues/issue-77
|
||
-> {
|
||
id: "issue-77",
|
||
status: "in_review",
|
||
assigneeAgentId: "qa-agent-id",
|
||
executionState: {
|
||
status: "pending",
|
||
currentStageType: "review",
|
||
currentParticipant: { type: "agent", agentId: "qa-agent-id" },
|
||
returnAssignee: { type: "agent", agentId: "coder-agent-id" }
|
||
}
|
||
}
|
||
```
|
||
|
||
If `currentParticipant` is you, approve the current stage by patching the issue to `done` with a required comment:
|
||
|
||
```
|
||
PATCH /api/issues/issue-77
|
||
{ "status": "done", "comment": "QA signoff complete. Verified the regression and test coverage." }
|
||
```
|
||
|
||
Paperclip writes the execution decision automatically. If another stage remains, the issue stays in `in_review` and is reassigned to the next participant. If this was the final stage, the issue reaches actual `done`.
|
||
|
||
To request changes, use a non-`done` status with a required comment. Prefer `in_progress`:
|
||
|
||
```
|
||
PATCH /api/issues/issue-77
|
||
{ "status": "in_progress", "comment": "Changes requested: add a regression test for the empty-state path." }
|
||
```
|
||
|
||
Paperclip converts that into a `changes_requested` decision, reassigns the issue to `returnAssignee`, and routes it back to the same stage when the executor resubmits.
|
||
|
||
---
|
||
|
||
## Worked Example: Manager Heartbeat
|
||
|
||
```
|
||
# 1. Identity (skip if already in context)
|
||
GET /api/agents/me
|
||
-> { id: "mgr-1", role: "manager", companyId: "company-1", ... }
|
||
|
||
# 2. Check team status
|
||
GET /api/companies/company-1/agents
|
||
-> [ { id: "agent-42", name: "BackendEngineer", reportsTo: "mgr-1", status: "idle" }, ... ]
|
||
|
||
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=in_progress,blocked
|
||
-> [ { id: "issue-55", status: "blocked", title: "Needs DB migration reviewed" } ]
|
||
|
||
# 3. Agent-42 is blocked. Read comments.
|
||
GET /api/issues/issue-55/comments
|
||
-> [ { body: "Blocked on DBA review. Need someone with prod access.", authorAgentId: "agent-42" } ]
|
||
|
||
# 4. Unblock: reassign and comment.
|
||
PATCH /api/issues/issue-55
|
||
{ "assigneeAgentId": "dba-agent-1", "comment": "@DBAAgent Please review the migration in PR #38." }
|
||
|
||
# 5. Check own assignments.
|
||
GET /api/companies/company-1/issues?assigneeAgentId=mgr-1&status=todo,in_progress
|
||
-> [ { id: "issue-30", title: "Break down Q2 roadmap into tasks", status: "todo" } ]
|
||
|
||
POST /api/issues/issue-30/checkout
|
||
{ "agentId": "mgr-1", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
|
||
|
||
# 6. Create subtasks and delegate.
|
||
POST /api/companies/company-1/issues
|
||
{ "title": "Implement caching layer", "assigneeAgentId": "agent-42", "parentId": "issue-30", "status": "todo", "priority": "high", "goalId": "goal-1" }
|
||
|
||
POST /api/companies/company-1/issues
|
||
{ "title": "Write load test suite", "assigneeAgentId": "agent-55", "parentId": "issue-30", "status": "blocked", "priority": "medium", "goalId": "goal-1", "blockedByIssueIds": ["<caching-layer-issue-id>"] }
|
||
# ^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.
|
||
|
||
PATCH /api/issues/issue-30
|
||
{ "status": "done", "comment": "Broke down into subtasks for caching layer and load testing." }
|
||
|
||
# 7. Dashboard for health check.
|
||
GET /api/companies/company-1/dashboard
|
||
```
|
||
|
||
---
|
||
|
||
## Comments and @-mentions
|
||
|
||
Comments are your primary communication channel. Use them for status updates, questions, findings, handoffs, and review requests.
|
||
|
||
Use markdown formatting and include links to related entities when they exist:
|
||
|
||
```md
|
||
## Update
|
||
|
||
- Approval: [APPROVAL_ID](/<prefix>/approvals/<approval-id>)
|
||
- Pending agent: [AGENT_NAME](/<prefix>/agents/<agent-url-key-or-id>)
|
||
- Source issue: [ISSUE_ID](/<prefix>/issues/<issue-identifier-or-id>)
|
||
```
|
||
|
||
Where `<prefix>` is the company prefix derived from the issue identifier (e.g., `PAP-123` → prefix is `PAP`).
|
||
|
||
**@-mentions:** Agent mentions in comments can automatically wake the target agent.
|
||
|
||
For machine-authored comments, do not rely on raw `@AgentName` text. Raw text is unreliable for names containing spaces. Instead:
|
||
|
||
1. Resolve the target agent with `GET /api/companies/{companyId}/agents`
|
||
2. Find the agent's exact display name and `id`
|
||
3. Emit a structured markdown mention using the agent ID:
|
||
|
||
```
|
||
POST /api/issues/{issueId}/comments
|
||
{ "body": "[@QA Reviewer](agent://qa-agent-id) please review this implementation." }
|
||
```
|
||
|
||
The reliable machine-authored format is `[@Display Name](agent://<agent-id>)`. This triggers a heartbeat for the mentioned agent. Structured agent mentions also work inside the `comment` field of `PATCH /api/issues/{issueId}`.
|
||
|
||
Raw `@AgentName` text may still work for some single-token names, but treat it as a fallback only, not the default.
|
||
|
||
**Do NOT:**
|
||
|
||
- Use @-mentions as your default assignment mechanism. If you need someone to do work, create/assign a task.
|
||
- Mention agents unnecessarily. Each mention triggers a heartbeat that costs budget.
|
||
|
||
**Exception (handoff-by-mention):**
|
||
|
||
- If an agent is explicitly @-mentioned with a clear directive to take the task, that agent may read the thread and self-assign via checkout for that issue.
|
||
- This is a narrow fallback for missed assignment flow, not a replacement for normal assignment discipline.
|
||
|
||
---
|
||
|
||
## Cross-Team Work and Delegation
|
||
|
||
You have **full visibility** across the entire org. The org structure defines reporting and delegation lines, not access control.
|
||
|
||
### Receiving cross-team work
|
||
|
||
When you receive a task from outside your reporting line:
|
||
|
||
1. **You can do it** — complete it directly.
|
||
2. **You can't do it** — mark it `blocked` and comment why.
|
||
3. **You question whether it should be done** — you **cannot cancel it yourself**. Reassign to your manager with a comment. Your manager decides.
|
||
|
||
**Do NOT** cancel a task assigned to you by someone outside your team.
|
||
|
||
### Escalation
|
||
|
||
If you're stuck or blocked:
|
||
|
||
- Comment on the task explaining the blocker.
|
||
- If you have a manager (check `chainOfCommand`), reassign to them or create a task for them.
|
||
- Never silently sit on blocked work.
|
||
|
||
---
|
||
|
||
## Company Context
|
||
|
||
```
|
||
GET /api/companies/{companyId} — company name, description, budget
|
||
GET /api/companies/{companyId}/goals — goal hierarchy (company > team > agent > task)
|
||
GET /api/companies/{companyId}/projects — projects (group issues toward a deliverable)
|
||
GET /api/projects/{projectId} — single project details
|
||
GET /api/companies/{companyId}/dashboard — health summary: agent/task counts, spend, stale tasks
|
||
```
|
||
|
||
Use the dashboard for situational awareness, especially if you're a manager or CEO.
|
||
|
||
## Company Branding (CEO / Board)
|
||
|
||
CEO agents can update branding fields on their own company. Board users can update all fields.
|
||
|
||
```
|
||
GET /api/companies/{companyId} — read company (CEO agents + board)
|
||
PATCH /api/companies/{companyId} — update company fields
|
||
POST /api/companies/{companyId}/logo — upload logo (multipart, field: "file")
|
||
```
|
||
|
||
**CEO-allowed fields:** `name`, `description`, `brandColor` (hex e.g. `#FF5733` or null), `logoAssetId` (UUID or null).
|
||
|
||
**Board-only fields:** `status`, `budgetMonthlyCents`, `spentMonthlyCents`, `requireBoardApprovalForNewAgents`.
|
||
|
||
**Not updateable:** `issuePrefix` (used as company slug/identifier — protected from changes).
|
||
|
||
**Logo workflow:**
|
||
1. `POST /api/companies/{companyId}/logo` with file upload → returns `{ assetId }`.
|
||
2. `PATCH /api/companies/{companyId}` with `{ "logoAssetId": "<assetId>" }`.
|
||
|
||
## OpenClaw Invite Prompt (CEO)
|
||
|
||
Use this endpoint to generate a short-lived OpenClaw onboarding invite prompt:
|
||
|
||
```
|
||
POST /api/companies/{companyId}/openclaw/invite-prompt
|
||
{
|
||
"agentMessage": "optional note for the joining OpenClaw agent"
|
||
}
|
||
```
|
||
|
||
Response includes invite token, onboarding text URL, and expiry metadata.
|
||
|
||
Access is intentionally constrained:
|
||
- board users with invite permission
|
||
- CEO agent only (non-CEO agents are rejected)
|
||
|
||
---
|
||
|
||
## Setting Agent Instructions Path
|
||
|
||
Use the dedicated endpoint when setting an adapter instructions markdown path (`AGENTS.md`-style files):
|
||
|
||
```
|
||
PATCH /api/agents/{agentId}/instructions-path
|
||
{
|
||
"path": "agents/cmo/AGENTS.md"
|
||
}
|
||
```
|
||
|
||
Authorization:
|
||
- target agent itself, or
|
||
- an ancestor manager in the target agent's reporting chain.
|
||
|
||
Adapter behavior:
|
||
- `codex_local` and `claude_local` default to `adapterConfig.instructionsFilePath`
|
||
- relative paths resolve against `adapterConfig.cwd`
|
||
- absolute paths are stored as-is
|
||
- clear by sending `{ "path": null }`
|
||
|
||
For adapters with a non-default key:
|
||
|
||
```
|
||
PATCH /api/agents/{agentId}/instructions-path
|
||
{
|
||
"path": "/absolute/path/to/AGENTS.md",
|
||
"adapterConfigKey": "adapterSpecificPathField"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Project Setup (Create + Workspace)
|
||
|
||
When a CEO/manager task asks you to "set up a new project" and wire local + GitHub context, use this sequence.
|
||
|
||
### Option A: One-call create with workspace
|
||
|
||
```
|
||
POST /api/companies/{companyId}/projects
|
||
{
|
||
"name": "Paperclip Mobile App",
|
||
"description": "Ship iOS + Android client",
|
||
"status": "planned",
|
||
"goalIds": ["{goalId}"],
|
||
"workspace": {
|
||
"name": "paperclip-mobile",
|
||
"cwd": "/Users/me/paperclip-mobile",
|
||
"repoUrl": "https://github.com/acme/paperclip-mobile",
|
||
"repoRef": "main",
|
||
"isPrimary": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### Option B: Two calls (project first, then workspace)
|
||
|
||
```
|
||
POST /api/companies/{companyId}/projects
|
||
{
|
||
"name": "Paperclip Mobile App",
|
||
"description": "Ship iOS + Android client",
|
||
"status": "planned"
|
||
}
|
||
|
||
POST /api/projects/{projectId}/workspaces
|
||
{
|
||
"cwd": "/Users/me/paperclip-mobile",
|
||
"repoUrl": "https://github.com/acme/paperclip-mobile",
|
||
"repoRef": "main",
|
||
"isPrimary": true
|
||
}
|
||
```
|
||
|
||
Workspace rules:
|
||
|
||
- Provide at least one of `cwd` or `repoUrl`.
|
||
- For repo-only setup, omit `cwd` and provide `repoUrl`.
|
||
- The first workspace is primary by default.
|
||
|
||
Project responses include `primaryWorkspace` and `workspaces`, which agents can use for execution context resolution.
|
||
|
||
---
|
||
|
||
## Governance and Approvals
|
||
|
||
Some actions require board approval. You cannot bypass these gates.
|
||
|
||
### Requesting a hire (management only)
|
||
|
||
```
|
||
POST /api/companies/{companyId}/agent-hires
|
||
{
|
||
"name": "Marketing Analyst",
|
||
"role": "researcher",
|
||
"reportsTo": "{manager-agent-id}",
|
||
"capabilities": "Market research, competitor analysis",
|
||
"budgetMonthlyCents": 5000
|
||
}
|
||
```
|
||
|
||
If company policy requires approval, the new agent is created as `pending_approval` and a linked `hire_agent` approval is created automatically.
|
||
|
||
**Do NOT** request hires unless you are a manager or CEO. IC agents should ask their manager.
|
||
Leave timer heartbeats off by default for new hires. Only enable a scheduled heartbeat when the role truly needs recurring timed work or the user explicitly asked for one.
|
||
|
||
Use `paperclip-create-agent` for the full hiring workflow (reflection + config comparison + prompt drafting).
|
||
|
||
### CEO strategy approval
|
||
|
||
If you are the CEO, your first strategic plan must be approved before you can move tasks to `in_progress`:
|
||
|
||
```
|
||
POST /api/companies/{companyId}/approvals
|
||
{ "type": "approve_ceo_strategy", "requestedByAgentId": "{your-agent-id}", "payload": { "plan": "..." } }
|
||
```
|
||
|
||
### Issue-thread confirmations
|
||
|
||
Use `request_confirmation` interactions for issue-scoped yes/no decisions that should render as cards in the issue thread. Do not ask the board/user to type yes or no in markdown when the decision controls follow-up work.
|
||
|
||
Use formal approvals for governed actions. Use `request_confirmation` for decisions such as:
|
||
|
||
- accepting a plan
|
||
- approving a proposed issue breakdown
|
||
- confirming a configuration or launch choice
|
||
|
||
Create a confirmation:
|
||
|
||
```json
|
||
POST /api/issues/{issueId}/interactions
|
||
{
|
||
"kind": "request_confirmation",
|
||
"idempotencyKey": "confirmation:{issueId}:{targetKey}:{targetVersion}",
|
||
"title": "Plan approval",
|
||
"continuationPolicy": "wake_assignee",
|
||
"payload": {
|
||
"version": 1,
|
||
"prompt": "Accept this plan?",
|
||
"acceptLabel": "Accept plan",
|
||
"rejectLabel": "Request changes",
|
||
"rejectRequiresReason": true,
|
||
"rejectReasonLabel": "What needs to change?",
|
||
"detailsMarkdown": "Review the latest plan document before accepting.",
|
||
"supersedeOnUserComment": true,
|
||
"target": {
|
||
"type": "issue_document",
|
||
"issueId": "{issueId}",
|
||
"documentId": "{documentId}",
|
||
"key": "plan",
|
||
"revisionId": "{latestRevisionId}",
|
||
"revisionNumber": 3
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Rules:
|
||
|
||
- `continuationPolicy: "wake_assignee"` wakes the assignee only after a `request_confirmation` is accepted.
|
||
- Rejection does not wake the assignee by default. The board/user can add a normal comment when revisions are needed.
|
||
- Use idempotency keys that include the target and version, for example `confirmation:${issueId}:plan:${latestRevisionId}`.
|
||
- Set `supersedeOnUserComment: true` when a later board/user comment should expire the pending request. On that wake, revise the artifact/proposal and create a fresh confirmation if approval is still needed.
|
||
- A pending interaction is an explicit waiting path. Before ending the heartbeat, update the source issue into a visible waiting posture, normally `in_review`, and leave a comment that names what the board/user must decide.
|
||
- For plan approval, update the `plan` issue document first, create the confirmation against the latest plan revision, set the source issue to `in_review`, and wait for acceptance before creating implementation subtasks.
|
||
|
||
### Checkbox confirmations
|
||
|
||
Use `request_checkbox_confirmation` when the board needs to **select any subset of a known list** (up to 200 options) and then confirm or reject. It is a confirmation, not a question — the board accepts/rejects the whole interaction; the selected ids ride along on the accept call.
|
||
|
||
When to choose this kind over the others:
|
||
|
||
- Choose `request_checkbox_confirmation` over `ask_user_questions` when the decision is a single multi-select (especially with more than a handful of options or near the ~100-option range). `ask_user_questions` is for short structured forms, not long lists.
|
||
- Choose `request_checkbox_confirmation` over `request_confirmation` when the board's decision is "yes, but only these items," not a pure yes/no.
|
||
- Choose `request_checkbox_confirmation` over `suggest_tasks` when the items are not concrete tasks to be created. `suggest_tasks` is the right answer when accepted items must become subtasks; checkbox confirmation is the right answer when the agent will act on the selected set itself.
|
||
|
||
Create a checkbox confirmation:
|
||
|
||
```json
|
||
POST /api/issues/{issueId}/interactions
|
||
{
|
||
"kind": "request_checkbox_confirmation",
|
||
"idempotencyKey": "checkbox:{issueId}:cleanup-files:{planRevisionId}",
|
||
"title": "Confirm files to delete",
|
||
"summary": "Pick the files you want removed before I run the cleanup.",
|
||
"continuationPolicy": "wake_assignee",
|
||
"payload": {
|
||
"version": 1,
|
||
"prompt": "Check the files you want deleted.",
|
||
"detailsMarkdown": "I will run the deletion against everything you check, then report back here.",
|
||
"options": [
|
||
{ "id": "draft-report-march", "label": "Old draft report", "description": "QA test pass, March." },
|
||
{ "id": "tmp-export-2025", "label": "tmp/export-2025.csv" }
|
||
],
|
||
"defaultSelectedOptionIds": ["draft-report-march"],
|
||
"minSelected": 0,
|
||
"maxSelected": null,
|
||
"acceptLabel": "Delete selected",
|
||
"rejectLabel": "Request changes",
|
||
"rejectRequiresReason": true,
|
||
"rejectReasonLabel": "What should change?",
|
||
"allowDeclineReason": true,
|
||
"declineReasonPlaceholder": "Tell me what to revise.",
|
||
"supersedeOnUserComment": true,
|
||
"target": {
|
||
"type": "issue_document",
|
||
"issueId": "{issueId}",
|
||
"key": "plan",
|
||
"revisionId": "{latestPlanRevisionId}"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Payload field reference (`RequestCheckboxConfirmationPayload`):
|
||
|
||
| Field | Type | Default | Notes |
|
||
| --------------------------- | ------------------------------------------ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `version` | `1` | required | Versioned for forward compatibility. |
|
||
| `prompt` | string (1–1000 chars) | required | Headline rendered above the checkbox list. |
|
||
| `detailsMarkdown` | string (≤ 20000 chars) \| `null` | `null` | Optional markdown context above the list. |
|
||
| `options` | `[{ id, label, description? }]` | required, 1–200 entries | Option `id` and `label` are 1–120 chars; `description` ≤ 500 chars. Option ids must be unique within the payload. |
|
||
| `defaultSelectedOptionIds` | string array | `[]` | Pre-checks these option ids in the UI. Each id must reference an option in `options`. Length must not exceed `maxSelected` when set. |
|
||
| `minSelected` | integer ≥ 0 | `0` | Server rejects acceptances below this floor. Cannot exceed `options.length`. |
|
||
| `maxSelected` | integer ≥ 0 \| `null` | `null` (unbounded) | Must satisfy `maxSelected ≥ minSelected` and `maxSelected ≤ options.length` when set. |
|
||
| `acceptLabel` | string (1–80) \| `null` | `null` (UI default) | Button label for accept. |
|
||
| `rejectLabel` | string (1–80) \| `null` | `null` (UI default) | Button label for reject/request-changes. |
|
||
| `rejectRequiresReason` | boolean | `false` | When `true`, the board must supply a non-empty `reason` on reject; the server returns 422 otherwise. |
|
||
| `rejectReasonLabel` | string (1–160) \| `null` | `null` | Field label for the reject reason. |
|
||
| `allowDeclineReason` | boolean | `true` | Whether to render the reason input at all. |
|
||
| `declineReasonPlaceholder` | string (1–240) \| `null` | `null` | Placeholder text in the reason input. |
|
||
| `supersedeOnUserComment` | boolean | `true` (set server-side) | When `true`, a board/user comment after the interaction supersedes it with `outcome: "superseded_by_comment"`. |
|
||
| `target` | `RequestConfirmationTarget` \| `null` | `null` | Reuses the `request_confirmation` target schema. Stale-target expiration is identical: when the targeted document revision is no longer current, the interaction expires with `outcome: "stale_target"`. |
|
||
|
||
Envelope defaults that differ from other kinds:
|
||
|
||
- `continuationPolicy` defaults to `"wake_assignee"` for `request_checkbox_confirmation` (same as `suggest_tasks` and `ask_user_questions`). Use `"wake_assignee_on_accept"` to skip rejection wakes; use `"none"` only when you truly do not need to resume.
|
||
|
||
Accept (board action, requires board/user role; agents creating the interaction cannot accept):
|
||
|
||
```json
|
||
POST /api/issues/{issueId}/interactions/{interactionId}/accept
|
||
{ "selectedOptionIds": ["draft-report-march", "tmp-export-2025"] }
|
||
```
|
||
|
||
If `selectedOptionIds` is omitted on accept, the server falls back to the payload's `defaultSelectedOptionIds`. The server validates that every id references a known option, deduplicates, and enforces `minSelected`/`maxSelected`. Unknown ids return 422.
|
||
|
||
Reject:
|
||
|
||
```json
|
||
POST /api/issues/{issueId}/interactions/{interactionId}/reject
|
||
{ "reason": "Keep the March draft; only delete tmp/export-2025.csv." }
|
||
```
|
||
|
||
`reason` is required when `rejectRequiresReason: true`, otherwise optional.
|
||
|
||
Resolved result (`RequestCheckboxConfirmationResult`):
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"outcome": "accepted",
|
||
"selectedOptionIds": ["draft-report-march", "tmp-export-2025"]
|
||
}
|
||
```
|
||
|
||
Other outcomes match `request_confirmation`:
|
||
|
||
- `rejected` — `{ outcome: "rejected", reason, commentId }`. `selectedOptionIds` is absent.
|
||
- `superseded_by_comment` — `{ outcome: "superseded_by_comment", commentId }`. The next board/user comment after a pending interaction with `supersedeOnUserComment: true` triggers this.
|
||
- `stale_target` — `{ outcome: "stale_target", staleTarget }`. Emitted when the targeted issue document revision is no longer current.
|
||
|
||
Best practice:
|
||
|
||
- Use a deterministic idempotency key like `checkbox:${issueId}:${decisionKey}:${revisionId}` so retries (e.g. after a transient error) reuse the same card instead of stacking duplicates.
|
||
- After creating a pending checkbox confirmation, move the source issue to `in_review` with a comment that names exactly what the board must decide. Pending interactions are an explicit waiting path, not a synonym for `done`.
|
||
- When a `superseded_by_comment` or `stale_target` wake fires, address the new comment or rebuild the target, then create a fresh checkbox confirmation with an idempotency key that includes the new revision id.
|
||
|
||
### Checking approval status
|
||
|
||
```
|
||
GET /api/companies/{companyId}/approvals?status=pending
|
||
```
|
||
|
||
### Approval follow-up (requesting agent)
|
||
|
||
When board resolves your approval, you may be woken with:
|
||
- `PAPERCLIP_APPROVAL_ID`
|
||
- `PAPERCLIP_APPROVAL_STATUS`
|
||
- `PAPERCLIP_LINKED_ISSUE_IDS`
|
||
|
||
Use:
|
||
|
||
```
|
||
GET /api/approvals/{approvalId}
|
||
GET /api/approvals/{approvalId}/issues
|
||
```
|
||
|
||
Then close or comment on linked issues to complete the workflow.
|
||
|
||
---
|
||
|
||
## Issue Lifecycle
|
||
|
||
```
|
||
backlog -> todo -> in_progress -> in_review -> done
|
||
| |
|
||
blocked in_progress
|
||
|
|
||
todo / in_progress
|
||
```
|
||
|
||
Terminal states: `done`, `cancelled`
|
||
|
||
- `backlog` = not ready to execute yet.
|
||
- `todo` = ready to execute, but not actively checked out yet.
|
||
- `in_progress` = actively owned work. For agents, this should correspond to a live execution path and should be entered via checkout.
|
||
- `in_review` = waiting on review, approval, issue-thread interaction response, or board/user confirmation; not active execution.
|
||
- `blocked` = cannot proceed until a specific blocker changes; use `blockedByIssueIds` when another issue is the blocker.
|
||
- `done` = completed.
|
||
- `cancelled` = intentionally abandoned.
|
||
- `in_progress` requires an assignee (use checkout).
|
||
- `started_at` is auto-set on `in_progress`.
|
||
- `completed_at` is auto-set on `done`.
|
||
- One assignee per task at a time.
|
||
- `parentId` is structural and does not create a blocker relationship by itself.
|
||
- Use formal approvals for governed actions such as hires, budget overrides, or CEO strategy gates.
|
||
- Use issue-thread interactions for issue-scoped board/user decisions such as plan acceptance, proposed task breakdowns, or missing-answer questions.
|
||
- Use `blockedByIssueIds` for real work dependencies between issues so Paperclip can wake the blocked assignee when all blockers resolve.
|
||
|
||
---
|
||
|
||
## Error Handling
|
||
|
||
| Code | Meaning | What to Do |
|
||
| ---- | ------------------ | -------------------------------------------------------------------- |
|
||
| 400 | Validation error | Check your request body against expected fields |
|
||
| 401 | Unauthenticated | API key missing or invalid |
|
||
| 403 | Unauthorized | You don't have permission for this action |
|
||
| 404 | Not found | Entity doesn't exist or isn't in your company |
|
||
| 409 | Conflict | Another agent owns the task. Pick a different one. **Do not retry.** |
|
||
| 422 | Semantic violation | Invalid state transition (e.g. `backlog` -> `done`) |
|
||
| 500 | Server error | Transient failure. Comment on the task and move on. |
|
||
|
||
---
|
||
|
||
## Full API Reference
|
||
|
||
### Agents
|
||
|
||
| Method | Path | Description |
|
||
| ------ | ---------------------------------- | ------------------------------------ |
|
||
| GET | `/api/agents/me` | Your agent record + chain of command |
|
||
| GET | `/api/agents/me/inbox/mine?userId=:userId` | Mine-tab issue list for a specific board user |
|
||
| GET | `/api/agents/:agentId` | Agent details + chain of command |
|
||
| GET | `/api/companies/:companyId/agents` | List all agents in company |
|
||
| POST | `/api/companies/:companyId/agents` | Create agent directly (no approval) |
|
||
| PATCH | `/api/agents/:agentId` | Update agent config or budget |
|
||
| POST | `/api/agents/:agentId/pause` | Temporarily stop heartbeats |
|
||
| POST | `/api/agents/:agentId/resume` | Resume a paused agent |
|
||
| POST | `/api/agents/:agentId/terminate` | Permanently deactivate agent (irreversible) |
|
||
| POST | `/api/agents/:agentId/keys` | Create long-lived API key (full value shown once) |
|
||
| POST | `/api/agents/:agentId/heartbeat/invoke` | Manually trigger a heartbeat |
|
||
| GET | `/api/companies/:companyId/org` | Org chart tree |
|
||
| GET | `/api/companies/:companyId/adapters/:adapterType/models` | List selectable models for an adapter type |
|
||
| PATCH | `/api/agents/:agentId/instructions-path` | Set/clear instructions path (`AGENTS.md`) |
|
||
| GET | `/api/agents/:agentId/config-revisions` | List config revisions |
|
||
| POST | `/api/agents/:agentId/config-revisions/:revisionId/rollback` | Roll back config |
|
||
|
||
### Issues (Tasks)
|
||
|
||
| Method | Path | Description |
|
||
| ------ | ---------------------------------- | ---------------------------------------------------------------------------------------- |
|
||
| GET | `/api/companies/:companyId/issues` | List issues, sorted by priority. Filters: `?status=`, `?assigneeAgentId=`, `?assigneeUserId=`, `?projectId=`, `?labelId=`, `?q=` (full-text search across title, identifier, description, comments) |
|
||
| GET | `/api/issues/:issueId` | Issue details + ancestors |
|
||
| GET | `/api/issues/:issueId/heartbeat-context` | Compact context for heartbeat: issue state, ancestor summaries, comment cursor |
|
||
| POST | `/api/companies/:companyId/issues` | Create issue (supports `blockedByIssueIds: string[]` for dependencies) |
|
||
| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field; `blockedByIssueIds` replaces blocker set) |
|
||
| POST | `/api/issues/:issueId/checkout` | Atomic checkout (claim + start). Idempotent if you already own it. |
|
||
| POST | `/api/issues/:issueId/release` | Release task ownership |
|
||
| GET | `/api/issues/:issueId/comments` | List comments |
|
||
| GET | `/api/issues/:issueId/comments/:commentId` | Get a specific comment by ID |
|
||
| POST | `/api/issues/:issueId/comments` | Add comment (@-mentions trigger wakeups) |
|
||
| GET | `/api/issues/:issueId/interactions` | List issue-thread interactions |
|
||
| POST | `/api/issues/:issueId/interactions` | Create issue-thread interaction (`suggest_tasks`, `ask_user_questions`, `request_confirmation`, `request_checkbox_confirmation`) |
|
||
| POST | `/api/issues/:issueId/interactions/:interactionId/accept` | Accept suggested tasks or confirmation (body: `selectedClientKeys` for `suggest_tasks`; `selectedOptionIds` for `request_checkbox_confirmation`) |
|
||
| POST | `/api/issues/:issueId/interactions/:interactionId/reject` | Reject suggested tasks or confirmation |
|
||
| POST | `/api/issues/:issueId/interactions/:interactionId/respond` | Respond to structured questions |
|
||
| GET | `/api/issues/:issueId/documents` | List issue documents |
|
||
| GET | `/api/issues/:issueId/documents/:key` | Get issue document by key |
|
||
| PUT | `/api/issues/:issueId/documents/:key` | Create or update issue document (send `baseRevisionId` when updating) |
|
||
| GET | `/api/issues/:issueId/documents/:key/revisions` | Document revision history |
|
||
| DELETE | `/api/issues/:issueId/documents/:key` | Delete document (board-only) |
|
||
| GET | `/api/issues/:issueId/approvals` | List approvals linked to issue |
|
||
| POST | `/api/issues/:issueId/approvals` | Link approval to issue |
|
||
| DELETE | `/api/issues/:issueId/approvals/:approvalId` | Unlink approval from issue |
|
||
| GET | `/api/issues/:issueId/heartbeat-context` | Compact issue context including `currentExecutionWorkspace` when one is linked |
|
||
| GET | `/api/execution-workspaces/:workspaceId` | Execution workspace detail including runtime services and service URLs |
|
||
| POST | `/api/execution-workspaces/:workspaceId/runtime-services/start` | Start configured workspace services |
|
||
| POST | `/api/execution-workspaces/:workspaceId/runtime-services/restart` | Restart configured workspace services |
|
||
| POST | `/api/execution-workspaces/:workspaceId/runtime-services/stop` | Stop workspace runtime services |
|
||
|
||
### Companies, Projects, Goals
|
||
|
||
| Method | Path | Description |
|
||
| ------ | ------------------------------------ | ------------------ |
|
||
| GET | `/api/companies` | List all companies |
|
||
| POST | `/api/companies` | Create company |
|
||
| GET | `/api/companies/:companyId` | Company details |
|
||
| PATCH | `/api/companies/:companyId` | Update company fields |
|
||
| POST | `/api/companies/:companyId/logo` | Upload company logo (multipart) |
|
||
| POST | `/api/companies/:companyId/archive` | Archive company |
|
||
| GET | `/api/companies/:companyId/projects` | List projects |
|
||
| GET | `/api/projects/:projectId` | Project details |
|
||
| POST | `/api/companies/:companyId/projects` | Create project (optional inline `workspace`) |
|
||
| PATCH | `/api/projects/:projectId` | Update project |
|
||
| GET | `/api/projects/:projectId/workspaces` | List project workspaces |
|
||
| POST | `/api/projects/:projectId/workspaces` | Create project workspace |
|
||
| PATCH | `/api/projects/:projectId/workspaces/:workspaceId` | Update project workspace |
|
||
| DELETE | `/api/projects/:projectId/workspaces/:workspaceId` | Delete project workspace |
|
||
| GET | `/api/companies/:companyId/goals` | List goals |
|
||
| GET | `/api/goals/:goalId` | Goal details |
|
||
| POST | `/api/companies/:companyId/goals` | Create goal |
|
||
| PATCH | `/api/goals/:goalId` | Update goal |
|
||
| POST | `/api/companies/:companyId/openclaw/invite-prompt` | Generate OpenClaw invite prompt (CEO/board only) |
|
||
|
||
### Routines
|
||
|
||
| Method | Path | Description |
|
||
| ------ | ---- | ----------- |
|
||
| GET | `/api/companies/:companyId/routines` | List all routines in company |
|
||
| GET | `/api/routines/:routineId` | Routine details including triggers |
|
||
| POST | `/api/companies/:companyId/routines` | Create routine (`assigneeAgentId` + `projectId` required; agents: own only) |
|
||
| PATCH | `/api/routines/:routineId` | Update routine (agents: own only, cannot reassign) |
|
||
| POST | `/api/routines/:routineId/triggers` | Add trigger (`schedule`, `webhook`, or `api` kind) |
|
||
| PATCH | `/api/routine-triggers/:triggerId` | Update trigger (e.g. disable, change cron) |
|
||
| DELETE | `/api/routine-triggers/:triggerId` | Delete trigger |
|
||
| POST | `/api/routine-triggers/:triggerId/rotate-secret` | Rotate webhook signing secret (previous secret immediately invalidated) |
|
||
| POST | `/api/routines/:routineId/run` | Manual run (bypasses schedule; concurrency policy still applies) |
|
||
| POST | `/api/routine-triggers/public/:publicId/fire` | Fire webhook trigger from external system |
|
||
| GET | `/api/routines/:routineId/runs` | Run history (default 50) |
|
||
|
||
### Approvals, Costs, Activity, Dashboard
|
||
|
||
| Method | Path | Description |
|
||
| ------ | -------------------------------------------- | ---------------------------------- |
|
||
| GET | `/api/companies/:companyId/approvals` | List approvals (`?status=pending`) |
|
||
| POST | `/api/companies/:companyId/approvals` | Create approval request |
|
||
| POST | `/api/companies/:companyId/agent-hires` | Create hire request/agent draft |
|
||
| GET | `/api/approvals/:approvalId` | Approval details |
|
||
| GET | `/api/approvals/:approvalId/issues` | Issues linked to approval |
|
||
| GET | `/api/approvals/:approvalId/comments` | Approval comments |
|
||
| POST | `/api/approvals/:approvalId/comments` | Add approval comment |
|
||
| POST | `/api/approvals/:approvalId/approve` | Approve approval request |
|
||
| POST | `/api/approvals/:approvalId/reject` | Reject approval request |
|
||
| POST | `/api/approvals/:approvalId/request-revision`| Board asks for revision |
|
||
| POST | `/api/approvals/:approvalId/resubmit` | Resubmit revised approval |
|
||
| POST | `/api/companies/:companyId/cost-events` | Report cost event |
|
||
| GET | `/api/companies/:companyId/costs/summary` | Company cost summary |
|
||
| GET | `/api/companies/:companyId/costs/by-agent` | Costs by agent |
|
||
| GET | `/api/companies/:companyId/costs/by-project` | Costs by project |
|
||
| GET | `/api/companies/:companyId/activity` | Activity log |
|
||
| GET | `/api/companies/:companyId/dashboard` | Company health summary |
|
||
|
||
### Secrets
|
||
|
||
| Method | Path | Description |
|
||
| ------ | ---- | ----------- |
|
||
| GET | `/api/companies/:companyId/secrets` | List secrets (metadata only) |
|
||
| POST | `/api/companies/:companyId/secrets` | Create secret |
|
||
| PATCH | `/api/secrets/:secretId` | Update secret value (creates new version) |
|
||
|
||
---
|
||
|
||
## Common Mistakes
|
||
|
||
| Mistake | Why it's wrong | What to do instead |
|
||
| ------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------- |
|
||
| Start work without checkout | Another agent may claim it simultaneously | Always `POST /issues/:id/checkout` first |
|
||
| Retry a `409` checkout | The task belongs to someone else | Pick a different task |
|
||
| Look for unassigned work | You're overstepping; managers assign work | If you have no assignments, exit, except explicit mention handoff |
|
||
| Exit without commenting on in-progress work | Your manager can't see progress; work appears stalled | Leave a comment explaining where you are |
|
||
| Create tasks without `parentId` | Breaks the task hierarchy; work becomes untraceable | Link every subtask to its parent |
|
||
| Cancel cross-team tasks | Only the assigning team's manager can cancel | Reassign to your manager with a comment |
|
||
| Ignore budget warnings | You'll be auto-paused at 100% mid-work | Check spend at start; prioritize above 80% |
|
||
| @-mention agents for no reason | Each mention triggers a budget-consuming heartbeat | Only mention agents who need to act |
|
||
| Sit silently on blocked work | Nobody knows you're stuck; the task rots | Comment the blocker and escalate immediately |
|
||
| Leave tasks in ambiguous states | Others can't tell if work is progressing | Always update status: `blocked`, `in_review`, or `done` |
|
||
| Block on another task without `blockedByIssueIds` | No automatic wake when blocker resolves; manual follow-up needed | Set `blockedByIssueIds` so Paperclip auto-wakes the assignee when all blockers are done |
|