> ## 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 task tree, start a review, trigger an automation.

Four 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 task tree

Agents fan work out to task subagents, which either share the parent's machine or run on a fresh one. The task endpoints are read-only observation: a task is driven through its thread, so if you want a task 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/tasks" | jq -r '.items[] | "\(.taskPath)  \(.status)  \(.title)"'
```

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

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

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

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

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