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

# Task Groups orchestration API

> Bundle pending tasks into reviewer-sized batches. Query pending tasks, create a group, hand off.

A Task Group is a batch of tasks assigned to one reviewer as a unit. You query pending tasks, decide how to group them, and post a group. Kodexa handles assignment and completion from there.

## When to use this

Tasks created in a `PENDING`-type status are held back from individual assignment. They only reach a reviewer once your script bundles them into a group. Use task groups when an external system already knows the right batching for review work (e.g. all invoices for one vendor in a given period, all claims for one policyholder, all documents from one filing).

## The loop

Run on a 5–15 minute interval.

### 1. Get pending tasks

```
GET /api/tasks?filter=project.id:'{projectId}' and taskGroupId isNull and status.statusType:'PENDING'&pageSize=200
```

Returns a paged list of tasks ready to batch. Each task has `id`, `title`, `statusSlug`, `priority`, `metadata`, and `template` — apply your batching logic to that.

### 2. Create a group

```
POST /api/task-groups
```

```json theme={null}
{
  "projectId": "edf4f516-562a-4cd7-ac74-ce70a131ccee",
  "name": "Acme liability analysis — 2026-04-16",
  "description": "Liability review for Acme invoices received today",
  "statusSlug": "in-review",
  "priority": 1,
  "teamId": "…",
  "taskIds": ["…", "…", "…"]
}
```

| Field         | Required | Description                                                                                                                                                                     |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId`   | yes      | All tasks in `taskIds` must belong to this project.                                                                                                                             |
| `name`        | yes      | Headline shown to the reviewer when they open the group.                                                                                                                        |
| `statusSlug`  | yes      | The status to apply to the group and to every task in `taskIds`. Use a status whose type is `IN_PROGRESS` (or any non-`PENDING` type) so the tasks become visible to reviewers. |
| `description` | no       | Context shown to the reviewer alongside the name.                                                                                                                               |
| `taskIds`     | no       | Initial members. Each must be in `projectId`, not already grouped, and not locked.                                                                                              |
| `priority`    | no       | Ranking hint for take-next. Lower numbers are picked up first. Defaults to `0`.                                                                                                 |
| `teamId`      | no       | Restricts the group to members of this team.                                                                                                                                    |
| `assigneeId`  | no       | Pre-assigns the group; member tasks inherit the assignee.                                                                                                                       |
| `metadata`    | no       | Arbitrary JSON object, returned verbatim on read.                                                                                                                               |

`201 Created` returns the group:

```json theme={null}
{
  "id": "14339437-030b-46a2-b2b3-5cc71786988a",
  "projectId": "edf4f516-…",
  "name": "Acme liability analysis — 2026-04-16",
  "statusSlug": "in-review",
  "priority": 1,
  "taskCount": 3,
  "completedTaskCount": 0,
  "locked": false,
  "createdOn": "2026-05-11T14:46:02.622757Z",
  "updatedOn": "2026-05-11T14:46:02.622757Z",
  "changeSequence": 0
}
```

`taskCount` and `completedTaskCount` are maintained by Kodexa — read them directly.

That's the orchestration loop. Reviewers pick up the group via `POST /api/tasks/assign-next`; when all member tasks reach a `DONE`-type status, Kodexa marks the group complete.

## Ordering (take-next / FIFO by arrival)

Take-next hands out work oldest-first, ranked by when the underlying documents *arrived* — not by when the task or group row was created. This keeps the queue fair when tasks are materialized long after their documents landed (e.g. a backlog reprocessed in bulk still drains in the order the documents originally came in).

The ordering key is a read-only column, `effectiveCreatedOn`, maintained entirely by Kodexa:

* **Task** — the earliest `createdOn` across the task's non-deleted document families, falling back to the task's own `createdOn` when it has none.
* **Group** — the earliest `effectiveCreatedOn` across the group's non-deleted member tasks, falling back to the group's own `createdOn` when it has none.

Soft-deleted document families and soft-deleted member tasks are excluded from the calculation. A brand-new task or group with no documents yet starts at its own `createdOn` and is refined as document families are attached. You never set `effectiveCreatedOn` — it's returned on read (`GET`) and ignored on write.

`POST /api/tasks/assign-next` merges two pools — unassigned groups and ungrouped, unassigned individual tasks, both restricted to statuses whose type is `OPEN` — into a single ranked list and claims the top candidate:

```
ORDER BY priority ASC NULLS LAST, effective_created_on ASC, created_on ASC
```

So lower `priority` wins first (rows with no priority sort last); within the same priority, the earliest arrival wins; `createdOn` is the final tie-breaker. `assign-next` requires a `projectId` query parameter and only ever hands out work from that project.

<Note>
  The **Received** column in the Tasks and Task Groups grids shows `effectiveCreatedOn`. The grid defaults to **newest first** (`effectiveCreatedOn` descending), which is the opposite of the oldest-first order take-next uses to hand out work. Sort the column ascending to preview the order reviewers will actually receive tasks in.
</Note>

## Status slugs

The `statusSlug` you use on a group must belong to a task status that is set up in the org and made available to the project. Statuses are usually configured once when the project is set up; if you pass an unknown slug, `POST /api/task-groups` returns `422`.

## Endpoint reference

| Endpoint                               | Method   | Purpose                                                                                                                       |
| -------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `/api/task-groups`                     | `GET`    | List with filter / sort / pagination.                                                                                         |
| `/api/task-groups/{id}`                | `GET`    | Fetch one.                                                                                                                    |
| `/api/task-groups`                     | `POST`   | Create a group and stamp member tasks.                                                                                        |
| `/api/task-groups/{id}`                | `PUT`    | Update `name`, `description`, `priority`, `teamId`, `statusSlug`, `metadata`. Assignment goes through the sub-resource below. |
| `/api/task-groups/{id}`                | `DELETE` | Soft-delete. Member tasks are ungrouped and unassigned.                                                                       |
| `/api/task-groups/{id}/assignee`       | `PUT`    | Assign. Body: `{ "assigneeId": "…" }`. Cascades to every member task.                                                         |
| `/api/task-groups/{id}/assignee`       | `DELETE` | Unassign. Cascades to member tasks.                                                                                           |
| `/api/task-groups/{id}/tasks`          | `POST`   | Add tasks. Body: `{ "taskIds": ["…"] }`. Membership only — task statuses are not changed.                                     |
| `/api/task-groups/{id}/tasks/{taskId}` | `DELETE` | Remove a task.                                                                                                                |
| `/api/task-groups/{id}/history`        | `GET`    | Audit feed for the group (created, assigned, status changed, completed, etc).                                                 |

## Constraints

* A task can be in one group at a time.
* All tasks in a group must share the group's project.
* Locked tasks can't be added to a group, and a group with locked member tasks can't be deleted.
* Tasks in a group can't be assigned individually — assignment is at the group level. Status changes per task are still allowed.
* One assignee per group.
