> ## Documentation Index
> Fetch the complete documentation index at: https://docs.capy.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Common flows

> End-to-end curl sequences: drive a thread, walk its subagent tree, start a review, organize a member's board, trigger an automation

Six flows you'll actually build, each complete enough to paste into a script. All of them assume:

```bash theme={null}
export CAPY_API_KEY="capy_..."
export API="https://api.capy.ai/api/v1"
auth() { curl -sS -H "Authorization: Bearer $CAPY_API_KEY" "$@"; }
```

## Drive a thread end to end

Create with a caller-minted `requestId` so a timed-out create can be retried without starting a second run:

```bash theme={null}
THREAD_ID=$(auth -X POST "$API/threads" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "'"$(uuidgen)"'",
    "projectId": "'"$PROJECT_ID"'",
    "message": "Upgrade the CI pipeline to Node 24 and open a PR."
  }' | jq -r .id)
```

Poll until the thread leaves its working states:

```bash theme={null}
while :; do
  STATUS=$(auth "$API/threads/$THREAD_ID" | jq -r .status)
  echo "$STATUS"
  case "$STATUS" in active|waiting) sleep 10 ;; *) break ;; esac
done
```

`pending_user` means the agent asked you something; `ready_for_review` means it delivered. Either way, the answer is in the transcript:

```bash theme={null}
auth "$API/threads/$THREAD_ID/messages" | jq -r '.items[] | "\(.source): \(.text)"'
```

Assistant entries carry the agent's replies, including the PR link when it opened one. Tool entries are one-line activity summaries, never raw arguments or results. Page with `after` set to the response's `cursor` until it's null.

Reply, and the loop starts again:

```bash theme={null}
auth -X POST "$API/threads/$THREAD_ID/message" \
  -H "Content-Type: application/json" \
  -d '{"text": "CI is green but the lockfile changed; explain why."}'
```

A message interrupts the agent's current work by default. Pass `"delivery": "queue"` to let the current work finish first, and the receipt's `id` is what you'd pass to `POST $API/threads/$THREAD_ID/messages/{id}/cancel` if you change your mind while it's still waiting.

## Inspect a thread's subagent tree

Agents fan work out to subagents, which either share the parent's machine or run on a fresh one. The subagent endpoints are read-only observation: a subagent is driven through its thread, so if you want a subagent to change course, message the thread.

List the tree; pages walk depth-first, so parents always precede children and every page prefix is a coherent subtree:

```bash theme={null}
auth "$API/threads/$THREAD_ID/subagents" | jq -r '.items[] | "\(.subagentPath)  \(.status)  \(.title)"'
```

`subagentPath` is the dotted address from the thread root: `1.2` is the second subagent under the first subagent. `status` is the run state: `working`, `waiting`, `idle`, `done`, or `failed`. Each subagent's `usage` is its own subtree's spend, so you can see where the credits went.

Read one subagent and its transcript with the same `Message` shape threads use:

```bash theme={null}
auth "$API/subagents/$SUBAGENT_ID"
auth "$API/subagents/$SUBAGENT_ID/messages" | jq -r '.items[] | "\(.source): \(.text)"'
```

The same reads are served at `/threads/{threadId}/tasks`, `/tasks/{taskId}`, and `/tasks/{taskId}/messages`, the API's original names for these routes. Those responses carry `taskPath` in place of `subagentPath`, and a missing id answers `404 capy/TaskNotFound` instead of `capy/SubagentNotFound`.

## Start a review on a pull request

`POST /reviews` starts a Capy review round on any PR in a repository your organization's GitHub installation covers:

```bash theme={null}
auth -X POST "$API/reviews" \
  -H "Content-Type: application/json" \
  -d '{
    "repo": "acme/checkout",
    "prNumber": 481,
    "idempotencyKey": "release-gate-481"
  }'
```

```json theme={null}
{
  "reviewId": "rev_...",
  "requestId": "release-gate-481",
  "threadId": "jam_01...",
  "headSha": "b1c2d3...",
  "adopted": false
}
```

A review round is keyed to the PR's exact head and base commits, so retriggering the same code answers the existing round with `adopted: true` and runs nothing new. Push a commit and the next call opens a fresh round. Omit `idempotencyKey` and this scope keying is the whole dedup; pass `forceRefresh: true` for a confirmed manual re-run of an already-completed round.

If one of your threads should hear the verdict, pass its id as `sourceThreadId`: the round's outcome lands in that thread as an ordinary input and wakes it, instead of you polling. The response's `sourceRecorded` tells you whether that notification was actually recorded (false when the adopted round already reports elsewhere or already finished; read the findings in the app instead of waiting).

A closed or draft PR, or a PR GitHub won't let Capy read, answers `capy/ReviewRefused` (422) with the reason.

## Create, inspect, and pause automations

Create an enabled schedule automation and retain its id. Keep the same request id when retrying an ambiguous response:

```bash theme={null}
REQUEST_ID=$(uuidgen)
AUTOMATION_ID=$(auth -X POST "$API/automations" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "'"$REQUEST_ID"'",
    "projectId": "'"$PROJECT_ID"'",
    "name": "Daily dependency audit",
    "prompt": "Audit dependencies for security advisories and open a PR for required upgrades.",
    "triggers": [{ "type": "schedule", "cron": "0 3 * * *", "timezone": "UTC" }]
  }' | jq -r .id)
```

If you create an `incoming_webhook` trigger, save the response's `webhookUrl` at this point. The capability URL is returned only by the first successful request; an idempotent replay returns the existing automation with `webhookUrl: null`, and later reads and mutations omit the field.

A human key can create automations. A service-user key needs `read_write` or `admin`, and its project allowlist applies to the requested `projectId`; `read_only` receives `403`.

List the automations visible to the API-key principal. Pass `projectId` when the integration should inspect one project only:

```bash theme={null}
auth "$API/automations?projectId=$PROJECT_ID" \
  | jq -r '.items[] | "\(.id)  \(if .enabled then "enabled" else "disabled" end)  \(.name)"'
```

The response is cursor-paginated. Pass a non-null `cursor` back unchanged to read the next page.

Pause the automation that's causing unwanted runs:

```bash theme={null}
auth -X POST "$API/automations/$AUTOMATION_ID/disable" \
  | jq '{id, name, enabled}'
```

The response's `enabled` field is `false` after the schedules and provider routes have been removed. Resume it with the inverse transition:

```bash theme={null}
auth -X POST "$API/automations/$AUTOMATION_ID/enable" \
  | jq '{id, name, enabled}'
```

Enabling revalidates the stored run-as principal before rebuilding schedules and provider routes. Human API keys and service-user keys with `read_write` or `admin` can change state; a `read_only` service key can inspect automations but receives `403` for either mutation.

Delete and restore use the same authorization and return the resulting `deleted` state:

```bash theme={null}
auth -X POST "$API/automations/$AUTOMATION_ID/delete"
auth -X POST "$API/automations/$AUTOMATION_ID/restore"
```

## Create a thread for a teammate and put it on their board

A service-user key can attribute work to a member so it shows up as theirs in the app. First resolve the member: `GET /users` lists your organization's active members, and every id it returns is a valid attribution target:

```bash theme={null}
MEMBER_USER_ID=$(auth "$API/users" | jq -r '.items[] | select(.email == "jordan@example.com") | .id')
```

Create the thread with `authorId`, then pin it to that member's board with the same id:

```bash theme={null}
THREAD_ID=$(auth -X POST "$API/threads" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "'"$(uuidgen)"'",
    "projectId": "'"$PROJECT_ID"'",
    "message": "Triage the overnight Sentry alerts and summarize what needs a human.",
    "authorId": "'"$MEMBER_USER_ID"'"
  }' | jq -r .id)

auth -X POST "$API/threads/pin" \
  -H "Content-Type: application/json" \
  -d '{"threadIds": ["'"$THREAD_ID"'"], "userId": "'"$MEMBER_USER_ID"'"}'
```

Both calls validate the id against your organization's active members and answer `400` `capy/InvalidRequest` for anyone else. The thread now lists under the member's threads, they're seeded as a participant, and the attribution reads back as `Thread.authorId`; the kickoff message stays attributed to your key's principal. A human key can omit `userId` to pin as itself; a service key must pass it, because a service principal has no board of its own. Pinned is per-member and private, so your service key can write to it but not read it back: confirm through the mutation's `outcomes` (`ok` per thread).

Shared folders are readable and writable by any key. List what your key can see, file the thread, and page the folder's threads in board order:

```bash theme={null}
auth "$API/folders" | jq '.items[] | {id, name, kind, visibility}'

auth -X POST "$API/folders/$FOLDER_ID/threads" \
  -H "Content-Type: application/json" \
  -d '{"threadIds": ["'"$THREAD_ID"'"]}'

auth "$API/folders/$FOLDER_ID/threads?limit=50" | jq '{ids: [.items[].id], cursor}'
```

Filing answers one outcome per thread: `ok` (re-filing an already-filed thread is also `ok`), `not_found` (absent, another organization's, or outside a service key's project access, deliberately indistinguishable), or `not_allowed` (a personal thread refused by a shared folder). A `read_only` service key can list and page folders but receives `403` for every mutation. Folders are created and managed in the app; the API files into them.

## Trigger an automation webhook

An [automation](/automations) with an incoming-webhook trigger exposes a capability URL. Note this one is **not** under `/api/v1` and takes no bearer header, because the URL itself is the credential:

```bash theme={null}
curl -sS -X POST "https://api.capy.ai/webhooks/automations/$WEBHOOK_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: incident-7231" \
  -d '{"service": "checkout", "alert": "p99 latency 4x baseline", "since": "2026-08-11T14:02:00Z"}'
```

`Idempotency-Key` is your retry safety: deliveries with the same key start one run, so your alerting system can fire the hook at-least-once without fanning out runs. A `202` body of `accepted` means a run was admitted, `duplicate` means your key already ran, and `no-match` means the automation's trigger filters rejected this body: a valid delivery that starts no run, so don't retry it. `404` means the URL is wrong or the automation is disabled, deliberately indistinguishable.

The body reaches the agent as quoted, untrusted event context under the automation's stored prompt: send data for the agent to act on, not instructions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.