# List task stages

> `GET` `/v1/task-stages`

URL: https://www.agencytitan.com/docs/tag/task-stages/GET/v1/task-stages

Operation ID: `task_stages_list`

Tag: [task_stages](https://www.agencytitan.com/docs/tag/task-stages.md)

## Description

Lists task stages, the actionable work items inside a task (for example Plan, Write, Review).

**Two modes.** (1) Pass `task_id` for the stage tree (including nested substages) on one known task. (2) Omit `task_id` for a stage-grain work list across tasks (assigned/"my" work).

**Response shapes.**
- **Mode A** (`task_id` set): a stage *tree*. Each top-level row can include nested `substages`. Tree rows emphasize stage fields (`name`, `order`, `substages`); some parent-task context fields used in Mode B may be omitted.
- **Mode B** (`task_id` omitted): a flat, paginated stage-grain list across tasks. Rows include parent-task context such as `process_name`, `client_name`, `is_substage`, and `primary_field_value`. This is the path for assigned/"my" work.

**Choosing filters (mode 2).** Same dual-grain filters as [`GET /v1/tasks`](/docs/tag/tasks/GET/v1/tasks): current-stage (`status_scope`, `statuses`, `assigned_to`, `due_date_*`, `start_date_*`) vs parent-task (`process_status_scope`, `process_statuses`, `process_assigned_to`, `process_due_date_*`). Clients: `client_ids` (OR; one-element array for a single client). Priorities/tags: `priority_level` / `tags`, or `filter_group` for custom fields.

**Combining filters.** Within one multi-value param, values are OR. Across different params, filters are AND. Example: `status_scope=active` plus `client_ids` with two ids means active rows linked to either client.

Exact `statuses` keys can collide across processes; prefer also passing `process_definition_id` and resolve values from [`GET /v1/tasks/options`](/docs/tag/tasks/GET/v1/tasks/options).

Default sort is `due_date` ascending (stage-grain). With `task_id`, prefer `sort=order` for display order. Tree-only sorts (`order` / `name` / `started_at` / `completed_at`) are remapped for stage-grain mode.

**Common scenarios.**
- My open work: omit `task_id`, set `assigned_to` to the user id, `status_scope=active`.
- Stages on one known job: pass `task_id`; prefer `sort=order` for display order.
- Browse overall jobs (not stage work): use [`GET /v1/tasks`](/docs/tag/tasks/GET/v1/tasks) instead.

**Pagination.** `limit` is 1–200 (default 50); `offset` starts at 0.

**Empty pages.** `data: []` with `pagination.total: 0` means no rows matched your filters, or assigned-data scope has nothing visible. That is success, not an error. Callers without `processes.view` still receive an authorization error. With assigned-data scope, inaccessible rows are omitted (empty page), not returned as 403.

In Mode A (`task_id`), a missing or inaccessible task returns **404**, not an empty page.

**Lean rows.** Stage-grain list rows carry system fields only; `custom_field_values` is empty by default. Request custom field values per key with the `fields` parameter.

## Parameters

- `assigned_to` (query, optional, `array<string>`) — Current-stage assignees (OR). Use for "who is working the active stage". For parent-task assignees, use `process_assigned_to`. See [`GET /v1/users`](/docs/tag/…
- `client_ids` (query, optional, `array<string>`) — Only return rows linked to any of these clients (OR). Pass a one-element array to filter to a single client. Optional filter. Omit unless the user asked for it…
- `completed_at_from` (query, optional, `string`) — Completed-at lower bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `completed_at_to` (query, optional, `string`) — Completed-at upper bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `created_at_from` (query, optional, `string`) — Created-at lower bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `created_at_to` (query, optional, `string`) — Created-at upper bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `due_date_from` (query, optional, `string`) — Current-stage due date lower bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `due_date_to` (query, optional, `string`) — Current-stage due date upper bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `fields` (query, optional, `array<string>`) — Custom field keys to include in `custom_field_values` on each stage-grain list row (mode 2). List rows return an empty `custom_field_values` by default. Comma-…
- `filter_group` (query, optional, `anyOf`) — Advanced filter tree for task columns and custom fields. Prefer first-class query params (`priority_level`, `tags`, `status_scope`, assignees, dates) when they…
- `group_by` (query, optional, `string`) — Narrow this page to one grouped/kanban bucket. Requires group_value. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `group_value` (query, optional, `string`) — Bucket value for group_by. Use __none__ for an empty bucket, or not_started for an empty task status bucket. Optional filter. Omit unless the user asked for it…
- `hide_blocked` (query, optional, `boolean`) — When true, omit rows whose current stage is dependency-blocked. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `hide_future_start` (query, optional, `boolean`) — When true, omit rows whose current stage has a future start date. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `instance_ids` (query, optional, `array<string>`) — Only return these task ids. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `limit` (query, optional, `integer`) — Maximum number of items to return (1-200).
- `offset` (query, optional, `integer`) — Number of items to skip before collecting the result set.
- `order` (query, optional, `string`) — Sort direction. Default `asc`.
- `priority_level` (query, optional, `array<string>`) — Only return rows whose current-stage priority_level matches one of these values (OR). Resolve values from [`GET /v1/tenant-context`](/docs/tag/tenant_context/G…
- `process_assigned_to` (query, optional, `array<string>`) — Parent-task assignees (OR). Use for overall job ownership. For the current stage's assignees, use `assigned_to`. See [`GET /v1/users`](/docs/tag/users/GET/v1/u…
- `process_definition_id` (query, optional, `string`) — Only return rows for tasks created from this process definition. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `process_due_date_from` (query, optional, `string`) — Parent-task due date lower bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `process_due_date_to` (query, optional, `string`) — Parent-task due date upper bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `process_status_scope` (query, optional, `string`) — Filter by parent-task status scope (active / past_due / completed / all). Use when you care about the overall job state, not the current stage. For exact paren…
- `process_statuses` (query, optional, `array<string>`) — Exact parent-task status values (OR). Prefer with `process_definition_id`. Resolve values with [`GET /v1/tasks/options`](/docs/tag/tasks/GET/v1/tasks/options) …
- `search` (query, optional, `string`) — Free-text match over process name, client name, stage name, and the parent task primary field value (stage-grain list when task_id is omitted).
- `sort` (query, optional, `string`) — Field to sort by. Default `due_date`.
- `start_date_from` (query, optional, `string`) — Current-stage start date lower bound (inclusive). This is the stage start_date field, not `started_at`. Omit for no bound. Optional filter. Omit unless the use…
- `start_date_to` (query, optional, `string`) — Current-stage start date upper bound (inclusive). This is the stage start_date field, not `started_at`. Omit for no bound. Optional filter. Omit unless the use…
- `started_at_from` (query, optional, `string`) — Started-at lower bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `started_at_to` (query, optional, `string`) — Started-at upper bound (inclusive). Omit for no bound. Optional filter. Omit unless the user asked for it; never guess identifiers.
- `status_scope` (query, optional, `string`) — Filter by current-stage status scope (active / past_due / completed / all). Use this for cross-process "open work". For exact stage status keys, use `statuses`…
- `statuses` (query, optional, `array<string>`) — Exact current-stage status values (OR). Keys can mean different things across processes; prefer also passing `process_definition_id`, and resolve values with […
- `tags` (query, optional, `array<string>`) — Only return rows whose task tags include at least one of these values (OR). Resolve values from [`GET /v1/tenant-context`](/docs/tag/tenant_context/GET/v1/tena…
- `task_id` (query, optional, `string`) — When provided, return the stage tree for this one task. When omitted, return a paginated stage-grain list (assigned/"my" work) using the same filters as tasks.…
- `task_ids` (query, optional, `array<string>`) — Return current-stage substages for up to 100 tasks in one bounded batch. Mutually exclusive with task_id. Optional filter. Omit unless the user asked for it; n…
- `watched_by` (query, optional, `string`) — Only return task or stage rows watched by this user. Optional filter. Omit unless the user asked for it; never guess identifiers.

## Request body

_None_

## Responses

- `200` (`application/json`): Successful response.
  - Type: `object`
  - Properties:
    - `data` (`array<object>`, required)
    - `pagination` (`object`, required)
- `400` (`application/json`): Request validation failed (unknown or out-of-range parameters/properties are rejected).
  - Type: `object`
  - Properties:
    - `error` (`object`, required)
- `401` (`application/json`): Missing, invalid, expired, or revoked bearer token.
  - Type: `object`
  - Properties:
    - `error` (`string`, required)
    - `error_description` (`string`, required)
- `403` (`application/json`): The authenticated user does not have permission for this operation.
  - Type: `object`
  - Properties:
    - `error` (`object`, required)
- `404` (`application/json`): The requested resource was not found.
  - Type: `object`
  - Properties:
    - `error` (`object`, required)
- `429` (`application/json`): Per-tenant rate limit exceeded (600 requests/minute across all /v1 REST endpoints). The Retry-After header indicates how many seconds to wait.
  - Type: `object`
  - Properties:
    - `error` (`object`, required)
- `500` (`application/json`): An unexpected internal error occurred.
  - Type: `object`
  - Properties:
    - `error` (`object`, required)

## Authentication

- `bearerAuth` — http (bearer) — All /v1 endpoints (except `/v1/openapi.json` and `/v1/llms.txt`) require a bearer token. Two equal first-class paths: (1) an OAuth 2.0 access token from the au…

## Useful links

- Interactive page: https://www.agencytitan.com/docs/tag/task-stages/GET/v1/task-stages
- API reference home: https://www.agencytitan.com/docs/
- Tag page: https://www.agencytitan.com/docs/tag/task-stages.md
- This operation as Markdown: https://www.agencytitan.com/docs/tag/task-stages/GET/v1/task-stages.md
- OpenAPI JSON: https://www.agencytitan.com/docs/openapi.json
- Full API Markdown: https://api.agencytitan.com/v1/llms.txt
- API keys: https://app.agencytitan.com/settings/api-mcp
