# Get a plan

Source: https://docs.coderhelm.com/api/reference/plans/get-plan

`GET /api/plans/{plan_id}`

Returns one plan with its tasks in order. Once the plan runs, each task shows the ticket it became and the run working on it (`run_id`), which you can follow with `GET /api/runs/{run_id}`.

## Authentication

A team API key: `Authorization: Bearer chk_...`. The key needs the `view` scope. See [Authentication](https://docs.coderhelm.com/api/authentication).

## Parameters

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `plan_id` | string | yes | The plan's id. |

## Responses

### 200

The plan and its tasks.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `chat_id` | string or null | no | The plan chat it came from. |
| `created_at` | string or null | no | When it was made (RFC 3339). |
| `description` | string or null | no | More about it, in Markdown. |
| `destination` | string or null | no | `github` or `jira`: where its tasks become tickets. |
| `executed_at` | string or null | no | When it started running (RFC 3339). |
| `executed_by` | string or null | no | Who started it running. |
| `mcp_servers` | array of string or null | no | MCP servers used while planning. |
| `plan_id` | string or null | no | The plan's id. |
| `repo` | string or null | no | The repository its tasks work in, `owner/name`. |
| `status` | string or null | no | `draft`, `executing` or `completed`. |
| `task_count` | integer | yes | How many tasks it has. |
| `title` | string or null | no | What the plan is for. |
| `tokens_in` | integer or null | no | Input tokens its runs used so far. |
| `tokens_out` | integer or null | no | Output tokens its runs used so far. |
| `updated_at` | string or null | no | When it last changed (RFC 3339). |
| `tasks` | array of PlanTask | yes | The plan's tasks, in order. |
| `tasks[].acceptance_criteria` | string or null | no | How to tell it is done. |
| `tasks[].approved_at` | string or null | no | When it was approved (RFC 3339). |
| `tasks[].approved_by` | string or null | no | Who approved it. |
| `tasks[].created_at` | string or null | no | When it was made (RFC 3339). |
| `tasks[].depends_on` | string or null | no | The task it waits for, when it depends on one. |
| `tasks[].description` | string or null | no | How, in Markdown. |
| `tasks[].destination` | string or null | no | `github` or `jira`. |
| `tasks[].issue_number` | integer or null | no | The GitHub issue it became. |
| `tasks[].issue_url` | string or null | no | That issue's address. |
| `tasks[].jira_project` | string or null | no | The Jira project it is filed in. |
| `tasks[].jira_ticket_key` | string or null | no | The Jira ticket it became. |
| `tasks[].jira_ticket_url` | string or null | no | That ticket's address. |
| `tasks[].order` | integer | yes | Its place in the plan, from 0. |
| `tasks[].plan_id` | string or null | no | The plan it belongs to. |
| `tasks[].pr_url` | string or null | no | The pull request that run opened. |
| `tasks[].rejected_at` | string or null | no | When it was rejected (RFC 3339). |
| `tasks[].rejected_by` | string or null | no | Who rejected it. |
| `tasks[].repo` | string or null | no | Its repository, when not the plan's. |
| `tasks[].run_id` | string or null | no | The run working on it. |
| `tasks[].status` | string or null | no | `draft`, `approved`, `rejected`, `queued`, `waiting` (on a task it depends on), `running`, `completed` or `failed`. |
| `tasks[].task_id` | string or null | no | The task's id. |
| `tasks[].title` | string or null | no | What to do. |

Headers: `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`.

### 400

The id is not a plan id.

### 401

The key is missing, malformed, revoked or expired, or the person who made it left the team.

### 403

The key lacks the scope this endpoint needs (`missing_scope`).

### 404

No plan with this id in the team.

### 429

Over the key's rate limit (`rate_limited`), or, for endpoints that start work, over the team's monthly token limit (`token_limit_reached`).

Headers: `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `Retry-After`.

### 500

Something failed on CoderHelm's side. Safe to retry reads.

## Example request

cURL:

```bash
curl -X GET 'https://api.coderhelm.com/api/plans/01JB8V0K3M5P7R9T1W3Y5A7C9E' \
  -H "Authorization: Bearer $CODERHELM_API_KEY"
```

TypeScript:

```ts
const res = await fetch("https://api.coderhelm.com/api/plans/01JB8V0K3M5P7R9T1W3Y5A7C9E", {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CODERHELM_API_KEY}`,
  },
});
if (!res.ok) {
  const { error, code } = await res.json();
  throw new Error(`${res.status} ${code}: ${error}`);
}
const data = await res.json();
```

Python:

```python
import os
import requests

headers = {
    "Authorization": f"Bearer {os.environ['CODERHELM_API_KEY']}",
}
res = requests.request("GET", "https://api.coderhelm.com/api/plans/01JB8V0K3M5P7R9T1W3Y5A7C9E", headers=headers)
if not res.ok:
    raise RuntimeError(f"{res.status_code}: {res.json().get('error')}")
data = res.json()
```

## Example response

```json
{
  "chat_id": "01JB8TZ6C8E0G2J4L6N8Q0S2U4",
  "created_at": "2026-10-22T13:40:00+00:00",
  "description": "Brute-force attempts are showing up in the auth logs.",
  "destination": "github",
  "executed_at": "2026-10-22T13:55:00+00:00",
  "executed_by": "octocat",
  "mcp_servers": [
    "docs-search"
  ],
  "plan_id": "01JB8V0K3M5P7R9T1W3Y5A7C9E",
  "repo": "acme/web",
  "status": "draft",
  "task_count": 3,
  "title": "Rate-limit the login form",
  "tokens_in": 412300,
  "tokens_out": 38120,
  "updated_at": "2026-10-22T13:55:00+00:00",
  "tasks": [
    {
      "acceptance_criteria": "The 11th attempt within 15 minutes gets 429 and a Retry-After header.",
      "approved_at": "2026-10-22T13:54:10+00:00",
      "approved_by": "octocat",
      "created_at": "2026-10-22T13:40:00+00:00",
      "depends_on": null,
      "description": "Allow 10 attempts per IP per 15 minutes; answer 429 after that.",
      "destination": "github",
      "issue_number": 412,
      "issue_url": "https://github.com/acme/web/issues/412",
      "jira_project": null,
      "jira_ticket_key": null,
      "jira_ticket_url": null,
      "order": 0,
      "plan_id": "01JB8V0K3M5P7R9T1W3Y5A7C9E",
      "pr_url": "https://github.com/acme/web/pull/318",
      "rejected_at": null,
      "rejected_by": null,
      "repo": "acme/web",
      "run_id": "01JB8X6F4M2C9QZ3T7V5W1K0HA",
      "status": "queued",
      "task_id": "01JB8V0P8Q2S4U6W8Y0A2C4E6G",
      "title": "Limit login attempts per IP"
    }
  ]
}
```
