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
{
"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:
{
"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.
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.
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.