diff --git a/cmd/task/main.go b/cmd/task/main.go
index d7853699eacdd6406531057c77fe0b3c8670c9c7..d65bbc215762363852ff9b0e50f28c81a478e1e6 100644
--- a/cmd/task/main.go
+++ b/cmd/task/main.go
@@ -107,7 +107,7 @@ func main() {
 	)
 	{
 		taskSvc = task.New(storage, storage, cache, logger)
-		taskListSvc = tasklist.New(storage, storage, logger)
+		taskListSvc = tasklist.New(storage, storage, cache, logger)
 		healthSvc = health.New()
 	}
 
diff --git a/docs/task-list.md b/docs/task-list.md
index 5b2b4fcf9e170d9923132004f31ebbb840d56376..ac9a564cf0c2532e4d02462ac4e3e4c6bf046b9f 100644
--- a/docs/task-list.md
+++ b/docs/task-list.md
@@ -55,9 +55,70 @@ an input to the next. If one task fails to execute, all following tasks are mark
  - Parallel group execution: tasks within the group are executed in parallel and the results are dependant. If a task
 fails to execute, this does not affect the other tasks but the group is marked with failed status.
 
+### Task list State
+
 The state of the task list asynchronous execution is available later on the `result` endpoint:
 ```shell
 curl -v -X GET http://localhost:8082/v1/taskListResult/{taskListID}
+```
+The state is returned as an HTTP status code.
+- Status code `200` for `Done` state;
+- Status code `201` for `Created` state;
+- Status code `202` for `Pending` state (the task list is being executed);
+- Status code `207` for `Failed` state (at least one task within the task list has failed).
+
+Example responses:
+
+HTTP Response code `200`: Done
+```json
+{
+  "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+  "status": "done",
+  "groups": [
+    {
+      "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+      "type": "sequential",
+      "status": "done",
+      "tasks": [
+        {
+          "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+          "status": "done"
+        },
+        {
+          "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+          "status": "done"
+        }
+      ]
+    }
+  ]
+}
+
+```
+
+HTTP Response code `207`: Failed
+```json
+{
+  "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+  "status": "failed",
+  "groups": [
+    {
+      "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+      "type": "sequential",
+      "status": "failed",
+      "tasks": [
+        {
+          "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+          "status": "done"
+        },
+        {
+          "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+          "status": "failed"
+        }
+      ]
+    }
+  ]
+}
+
 ```
 
 ### Task list Executor Configuration
diff --git a/internal/listexecutor/listexecutor.go b/internal/listexecutor/listexecutor.go
index b05bad414c12af2880cfce1296ea5c4473bbe1eb..ceea140563990f60fc7c47b2320ceac787877d93 100644
--- a/internal/listexecutor/listexecutor.go
+++ b/internal/listexecutor/listexecutor.go
@@ -3,6 +3,7 @@ package listexecutor
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"io"
 	"net/http"
 	"sync"
@@ -11,6 +12,8 @@ import (
 	"go.uber.org/zap"
 
 	"code.vereign.com/gaiax/tsa/golib/errors"
+	"code.vereign.com/gaiax/tsa/golib/ptr"
+	goatasklist "code.vereign.com/gaiax/tsa/task/gen/task_list"
 	taskpkg "code.vereign.com/gaiax/tsa/task/internal/service/task"
 	"code.vereign.com/gaiax/tsa/task/internal/service/tasklist"
 )
@@ -125,13 +128,17 @@ func (l *ListExecutor) Execute(ctx context.Context, list *tasklist.TaskList) {
 	list.State = taskpkg.Pending
 	list.StartedAt = time.Now()
 
+	var state goatasklist.TaskListStatus
+
 	// execute groups sequentially
 	for i := range list.Groups {
-		if err := l.executeGroup(ctx, &list.Groups[i]); err != nil {
+		groupState, err := l.executeGroup(ctx, &list.Groups[i])
+		if err != nil {
 			logger.Error("error executing group", zap.Error(err))
 			list.Groups[i].State = taskpkg.Failed
 			list.State = taskpkg.Failed
 		}
+		state.Groups = append(state.Groups, groupState)
 	}
 
 	if list.State != taskpkg.Failed {
@@ -139,6 +146,20 @@ func (l *ListExecutor) Execute(ctx context.Context, list *tasklist.TaskList) {
 	}
 	list.FinishedAt = time.Now()
 
+	state.ID = list.ID
+	state.Status = string(list.State)
+
+	value, err := json.Marshal(state)
+	if err != nil {
+		logger.Error("error marshaling taskList state", zap.Error(err))
+	} else {
+		if err := l.cache.Set(ctx, list.ID, list.CacheNamespace, list.CacheScope, value); err != nil {
+			logger.Error("error storing taskList state in cache", zap.Error(err))
+		} else {
+			logger.Debug("taskList state is stored in cache")
+		}
+	}
+
 	if err := l.storage.SaveTaskListHistory(ctx, list); err != nil {
 		logger.Error("error saving taskList history", zap.Error(err))
 	} else {
@@ -150,7 +171,7 @@ func (l *ListExecutor) Execute(ctx context.Context, list *tasklist.TaskList) {
 	}
 }
 
-func (l *ListExecutor) executeGroup(ctx context.Context, group *tasklist.Group) error {
+func (l *ListExecutor) executeGroup(ctx context.Context, group *tasklist.Group) (*goatasklist.GroupStatus, error) {
 	switch exec := group.Execution; exec {
 	case sequential:
 		return l.executeSequential(ctx, group)
@@ -158,19 +179,22 @@ func (l *ListExecutor) executeGroup(ctx context.Context, group *tasklist.Group)
 		return l.executeParallel(ctx, group)
 	}
 
-	return errors.New("unknown type of group execution")
+	return nil, errors.New("unknown type of group execution")
 }
 
-func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Group) error {
+func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Group) (*goatasklist.GroupStatus, error) {
 	group.State = taskpkg.Pending
+	var state goatasklist.GroupStatus
 
 	tasks, err := l.storage.GetGroupTasks(ctx, group)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	req := group.Request
 	for _, task := range tasks {
+		taskState := goatasklist.TaskStatus{ID: &task.ID}
+
 		logger := l.logger.With(
 			zap.String("taskID", task.ID),
 			zap.String("taskName", task.Name),
@@ -179,6 +203,8 @@ func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Gr
 		// mark all subsequent tasks as failed if one task already failed
 		if group.State == taskpkg.Failed {
 			task.State = taskpkg.Failed
+			taskState.Status = ptr.String(taskpkg.Failed)
+			state.Tasks = append(state.Tasks, &taskState)
 			continue
 		}
 
@@ -186,12 +212,17 @@ func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Gr
 		err := l.executeTask(ctx, task)
 		if err != nil {
 			task.State = taskpkg.Failed
+			taskState.Status = ptr.String(taskpkg.Failed)
+			state.Tasks = append(state.Tasks, &taskState)
 			group.State = taskpkg.Failed
 			logger.Error("error executing task", zap.Error(err))
 			continue
 		}
 		logger.Debug("task execution completed successfully")
 
+		taskState.Status = ptr.String(string(task.State))
+		state.Tasks = append(state.Tasks, &taskState)
+
 		// pass the response from current task as an input to the next task
 		req = task.Response
 
@@ -223,21 +254,29 @@ func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Gr
 		group.State = taskpkg.Done
 	}
 
-	return nil
+	state.ID = &group.ID
+	state.Status = ptr.String(string(group.State))
+
+	return &state, nil
 }
 
-func (l *ListExecutor) executeParallel(ctx context.Context, group *tasklist.Group) error {
+func (l *ListExecutor) executeParallel(ctx context.Context, group *tasklist.Group) (*goatasklist.GroupStatus, error) {
 	group.State = taskpkg.Pending
+	var state goatasklist.GroupStatus
 
 	tasks, err := l.storage.GetGroupTasks(ctx, group)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	var wg sync.WaitGroup
 	for _, task := range tasks {
 		wg.Add(1)
 		go func(t *taskpkg.Task) {
+			taskState := goatasklist.TaskStatus{
+				ID: &t.ID,
+			}
+
 			defer wg.Done()
 			logger := l.logger.With(
 				zap.String("taskID", t.ID),
@@ -248,12 +287,17 @@ func (l *ListExecutor) executeParallel(ctx context.Context, group *tasklist.Grou
 
 			if err := l.executeTask(ctx, t); err != nil {
 				t.State = taskpkg.Failed
+				taskState.Status = ptr.String(taskpkg.Failed)
+				state.Tasks = append(state.Tasks, &taskState)
 				group.State = taskpkg.Failed
 				logger.Error("error executing task", zap.Error(err))
 				return
 			}
 			logger.Debug("task execution completed successfully")
 
+			taskState.Status = ptr.String(string(t.State))
+			state.Tasks = append(state.Tasks, &taskState)
+
 			if err := l.cache.Set(
 				ctx,
 				t.ID,
@@ -286,7 +330,10 @@ func (l *ListExecutor) executeParallel(ctx context.Context, group *tasklist.Grou
 		group.State = taskpkg.Done
 	}
 
-	return nil
+	state.ID = &group.ID
+	state.Status = ptr.String(string(group.State))
+
+	return &state, nil
 }
 
 func (l *ListExecutor) executeTask(ctx context.Context, task *taskpkg.Task) error {
diff --git a/internal/service/tasklist/service.go b/internal/service/tasklist/service.go
index 5542a7d24eace9e0d4782eea171151a4a46b4603..4356879964cb08ce18c8bcd5667332c77ebde735 100644
--- a/internal/service/tasklist/service.go
+++ b/internal/service/tasklist/service.go
@@ -1,6 +1,7 @@
 package tasklist
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"time"
@@ -9,34 +10,45 @@ import (
 	"go.uber.org/zap"
 
 	"code.vereign.com/gaiax/tsa/golib/errors"
+	"code.vereign.com/gaiax/tsa/golib/ptr"
 	goatasklist "code.vereign.com/gaiax/tsa/task/gen/task_list"
 	"code.vereign.com/gaiax/tsa/task/internal/service/task"
 )
 
 //go:generate counterfeiter . Storage
 //go:generate counterfeiter . Queue
+//go:generate counterfeiter . Cache
 
 // Storage for retrieving predefined task templates.
 type Storage interface {
 	TaskListTemplate(ctx context.Context, taskListName string) (*Template, error)
 	TaskTemplates(ctx context.Context, names []string) (map[string]*task.Task, error)
+	TaskList(ctx context.Context, taskListID string) (*TaskList, error)
+	TaskListHistory(ctx context.Context, taskListID string) (*TaskList, error)
+	GetGroupTasks(ctx context.Context, group *Group) ([]*task.Task, error)
 }
 
 type Queue interface {
 	AddTaskList(ctx context.Context, taskList *TaskList, tasks []*task.Task) error
 }
 
+type Cache interface {
+	Get(ctx context.Context, key, namespace, scope string) ([]byte, error)
+}
+
 type Service struct {
 	storage Storage
 	queue   Queue
+	cache   Cache
 
 	logger *zap.Logger
 }
 
-func New(template Storage, queue Queue, logger *zap.Logger) *Service {
+func New(template Storage, queue Queue, cache Cache, logger *zap.Logger) *Service {
 	return &Service{
 		storage: template,
 		queue:   queue,
+		cache:   cache,
 		logger:  logger,
 	}
 }
@@ -105,6 +117,59 @@ func (s *Service) Create(ctx context.Context, req *goatasklist.CreateTaskListReq
 	}, nil
 }
 
+// TaskListResult retrieves a taskList result containing all tasks' unique IDs
+// and statuses from the Cache service.
+func (s *Service) TaskListResult(ctx context.Context, req *goatasklist.TaskListResultRequest) (res *goatasklist.TaskListStatus, err error) {
+	if req.TaskListID == "" {
+		return nil, errors.New(errors.BadRequest, "missing taskListID")
+	}
+
+	logger := s.logger.With(zap.String("taskListID", req.TaskListID))
+
+	var list *TaskList
+	list, err = s.storage.TaskListHistory(ctx, req.TaskListID)
+	if err != nil && !errors.Is(errors.NotFound, err) {
+		logger.Error("error getting taskList from history collection", zap.Error(err))
+		return nil, err
+	}
+
+	if list == nil {
+		list, err = s.storage.TaskList(ctx, req.TaskListID)
+		if err != nil {
+			if errors.Is(errors.NotFound, err) {
+				return nil, errors.New("taskList is not found", err)
+			}
+			logger.Error("error getting taskList from taskLists collection", zap.Error(err))
+			return nil, err
+		}
+	}
+
+	var result *goatasklist.TaskListStatus
+	if list.State != task.Done && list.State != task.Failed {
+		// taskList is not executed yet
+		result, err = s.calculateState(ctx, list)
+		if err != nil {
+			logger.Error("error calculating taskList state", zap.Error(err))
+			return nil, err
+		}
+	} else {
+		// taskList is already executed
+		var value []byte
+		value, err = s.cache.Get(ctx, list.ID, list.CacheNamespace, list.CacheScope)
+		if err != nil {
+			logger.Error("error getting taskList result from cache", zap.Error(err))
+			return nil, err
+		}
+
+		if err := json.NewDecoder(bytes.NewReader(value)).Decode(&result); err != nil {
+			logger.Error("error decoding result from cache", zap.Error(err))
+			return nil, errors.New("error decoding result from cache", err)
+		}
+	}
+
+	return result, nil
+}
+
 func createGroups(t *Template, req []byte) []Group {
 	var groups []Group
 	for _, group := range t.Groups {
@@ -163,6 +228,36 @@ func createTasks(t *TaskList, templates map[string]*task.Task) ([]*task.Task, er
 	return tasks, nil
 }
 
+func (s *Service) calculateState(ctx context.Context, list *TaskList) (*goatasklist.TaskListStatus, error) {
+	result := &goatasklist.TaskListStatus{
+		ID:     list.ID,
+		Status: string(list.State),
+	}
+
+	for i := range list.Groups {
+		groupState := goatasklist.GroupStatus{
+			ID:     &list.Groups[i].ID,
+			Status: ptr.String(string(list.Groups[i].State)),
+		}
+
+		tasks, err := s.storage.GetGroupTasks(ctx, &list.Groups[i])
+		if err != nil {
+			return nil, err
+		}
+		for j := range tasks {
+			taskState := goatasklist.TaskStatus{
+				ID:     &tasks[j].ID,
+				Status: ptr.String(string(tasks[j].State)),
+			}
+			groupState.Tasks = append(groupState.Tasks, &taskState)
+		}
+
+		result.Groups = append(result.Groups, &groupState)
+	}
+
+	return result, nil
+}
+
 // taskNamesFromTaskListTemplate returns the names of all tasks within
 // one taskList template
 func taskNamesFromTaskListTemplate(template *Template) []string {
diff --git a/internal/service/tasklist/service_test.go b/internal/service/tasklist/service_test.go
index 9dd3b39e39b25076f55f206661264627f2917c07..a14b0e1133d01ccda8cf485f3fa4ea79feac46f0 100644
--- a/internal/service/tasklist/service_test.go
+++ b/internal/service/tasklist/service_test.go
@@ -15,7 +15,7 @@ import (
 )
 
 func TestNew(t *testing.T) {
-	svc := tasklist.New(nil, nil, zap.NewNop())
+	svc := tasklist.New(nil, nil, nil, zap.NewNop())
 	assert.Implements(t, (*goatasklist.Service)(nil), svc)
 }
 
@@ -120,7 +120,7 @@ func Test_Create(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			svc := tasklist.New(test.storage, test.queue, zap.NewNop())
+			svc := tasklist.New(test.storage, test.queue, nil, zap.NewNop())
 			res, err := svc.Create(context.Background(), test.req)
 			if err != nil {
 				assert.NotEmpty(t, test.errtext)
@@ -138,3 +138,181 @@ func Test_Create(t *testing.T) {
 		})
 	}
 }
+
+func Test_TaskListResult(t *testing.T) {
+	tests := []struct {
+		name    string
+		req     *goatasklist.TaskListResultRequest
+		storage *tasklistfakes.FakeStorage
+		queue   *tasklistfakes.FakeQueue
+		cache   *tasklistfakes.FakeCache
+
+		errkind errors.Kind
+		errtext string
+	}{
+		{
+			name:    "missing taskList ID",
+			req:     &goatasklist.TaskListResultRequest{},
+			errkind: errors.BadRequest,
+			errtext: "missing taskListID",
+		},
+		{
+			name: "error getting taskList form history collection",
+			req:  &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"},
+			storage: &tasklistfakes.FakeStorage{
+				TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return nil, errors.New("some error")
+				},
+			},
+			errkind: errors.Unknown,
+			errtext: "some error",
+		},
+		{
+			name: "taskList not found",
+			req:  &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"},
+			storage: &tasklistfakes.FakeStorage{
+				TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return nil, errors.New(errors.NotFound)
+				},
+				TaskListStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return nil, errors.New(errors.NotFound)
+				},
+			},
+			errkind: errors.NotFound,
+			errtext: "taskList is not found",
+		},
+		{
+			name: "error getting taskList from taskLists collection",
+			req:  &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"},
+			storage: &tasklistfakes.FakeStorage{
+				TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return nil, errors.New(errors.NotFound)
+				},
+				TaskListStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return nil, errors.New("some error")
+				},
+			},
+			errkind: errors.Unknown,
+			errtext: "some error",
+		},
+		{
+			name: "error calculating taskList state",
+			req:  &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"},
+			storage: &tasklistfakes.FakeStorage{
+				TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return pendingTaskList, nil
+				},
+				GetGroupTasksStub: func(ctx context.Context, group *tasklist.Group) ([]*task.Task, error) {
+					return nil, errors.New("some error")
+				},
+			},
+			errkind: errors.Unknown,
+			errtext: "some error",
+		},
+		{
+			name: "error getting taskList from cache",
+			req:  &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"},
+			storage: &tasklistfakes.FakeStorage{
+				TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return doneTaskList, nil
+				},
+			},
+			cache: &tasklistfakes.FakeCache{
+				GetStub: func(ctx context.Context, key, namespace, scope string) ([]byte, error) {
+					return nil, errors.New("some cache error")
+				},
+			},
+			errkind: errors.Unknown,
+			errtext: "some cache error",
+		},
+		{
+			name: "successfully get taskList state on pending task",
+			req:  &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"},
+			storage: &tasklistfakes.FakeStorage{
+				TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return pendingTaskList, nil
+				},
+				GetGroupTasksStub: func(ctx context.Context, group *tasklist.Group) ([]*task.Task, error) {
+					return []*task.Task{}, nil
+				},
+			},
+		},
+		{
+			name: "successfully get taskList state on executed task",
+			req:  &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"},
+			storage: &tasklistfakes.FakeStorage{
+				TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+					return doneTaskList, nil
+				},
+			},
+			cache: &tasklistfakes.FakeCache{
+				GetStub: func(ctx context.Context, key, namespace, scope string) ([]byte, error) {
+					return doneTaskState, nil
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			svc := tasklist.New(test.storage, test.queue, test.cache, zap.NewNop())
+			res, err := svc.TaskListResult(context.Background(), test.req)
+			if err != nil {
+				assert.NotEmpty(t, test.errtext)
+				e, ok := err.(*errors.Error)
+				assert.True(t, ok)
+				assert.Equal(t, test.errkind, e.Kind)
+				assert.Contains(t, e.Error(), test.errtext)
+				assert.Nil(t, res)
+			} else {
+				assert.Empty(t, test.errtext)
+				assert.NotNil(t, res)
+				assert.NotEmpty(t, res.ID)
+				assert.NotEmpty(t, res.Status)
+				assert.NotEmpty(t, res.Groups)
+			}
+		})
+	}
+}
+
+//nolint:gosec
+var pendingTaskList = &tasklist.TaskList{
+	ID:    "16996cd-1977-42a9-90b2-b4548a35c1b4",
+	State: "pending",
+	Groups: []tasklist.Group{
+		{
+			ID:    "074076d5-c995-4d2d-8d38-da57360453d4",
+			Tasks: []string{"createdTask", "createdTask2"},
+			State: "created",
+		},
+	},
+}
+
+//nolint:gosec
+var doneTaskList = &tasklist.TaskList{
+	ID:    "16996cd-1977-42a9-90b2-b4548a35c1b4",
+	State: "done",
+}
+
+//nolint:gosec
+var doneTaskState = []byte(`{
+  "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+  "status": "done",
+  "groups": [
+	{
+	  "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+	  "type": "sequential",
+	  "status": "done",
+	  "tasks": [
+		{
+		  "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+		  "status": "done"
+		},
+		{
+		  "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502",
+		  "status": "done"
+		}
+	  ]
+	}
+  ]
+}`)
diff --git a/internal/service/tasklist/tasklistfakes/fake_cache.go b/internal/service/tasklist/tasklistfakes/fake_cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..2f636bdb059b5e8cc84bd827b91bbc65e67a8d37
--- /dev/null
+++ b/internal/service/tasklist/tasklistfakes/fake_cache.go
@@ -0,0 +1,123 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package tasklistfakes
+
+import (
+	"context"
+	"sync"
+
+	"code.vereign.com/gaiax/tsa/task/internal/service/tasklist"
+)
+
+type FakeCache struct {
+	GetStub        func(context.Context, string, string, string) ([]byte, error)
+	getMutex       sync.RWMutex
+	getArgsForCall []struct {
+		arg1 context.Context
+		arg2 string
+		arg3 string
+		arg4 string
+	}
+	getReturns struct {
+		result1 []byte
+		result2 error
+	}
+	getReturnsOnCall map[int]struct {
+		result1 []byte
+		result2 error
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *FakeCache) Get(arg1 context.Context, arg2 string, arg3 string, arg4 string) ([]byte, error) {
+	fake.getMutex.Lock()
+	ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)]
+	fake.getArgsForCall = append(fake.getArgsForCall, struct {
+		arg1 context.Context
+		arg2 string
+		arg3 string
+		arg4 string
+	}{arg1, arg2, arg3, arg4})
+	stub := fake.GetStub
+	fakeReturns := fake.getReturns
+	fake.recordInvocation("Get", []interface{}{arg1, arg2, arg3, arg4})
+	fake.getMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3, arg4)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeCache) GetCallCount() int {
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	return len(fake.getArgsForCall)
+}
+
+func (fake *FakeCache) GetCalls(stub func(context.Context, string, string, string) ([]byte, error)) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = stub
+}
+
+func (fake *FakeCache) GetArgsForCall(i int) (context.Context, string, string, string) {
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	argsForCall := fake.getArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
+}
+
+func (fake *FakeCache) GetReturns(result1 []byte, result2 error) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = nil
+	fake.getReturns = struct {
+		result1 []byte
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeCache) GetReturnsOnCall(i int, result1 []byte, result2 error) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = nil
+	if fake.getReturnsOnCall == nil {
+		fake.getReturnsOnCall = make(map[int]struct {
+			result1 []byte
+			result2 error
+		})
+	}
+	fake.getReturnsOnCall[i] = struct {
+		result1 []byte
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeCache) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *FakeCache) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ tasklist.Cache = new(FakeCache)
diff --git a/internal/service/tasklist/tasklistfakes/fake_storage.go b/internal/service/tasklist/tasklistfakes/fake_storage.go
index 9ece29b9180862d8281bd5759b4104ff9a731e67..4dd7cf097593ba40b96d8e9ea7731d96914d1a19 100644
--- a/internal/service/tasklist/tasklistfakes/fake_storage.go
+++ b/internal/service/tasklist/tasklistfakes/fake_storage.go
@@ -10,6 +10,48 @@ import (
 )
 
 type FakeStorage struct {
+	GetGroupTasksStub        func(context.Context, *tasklist.Group) ([]*task.Task, error)
+	getGroupTasksMutex       sync.RWMutex
+	getGroupTasksArgsForCall []struct {
+		arg1 context.Context
+		arg2 *tasklist.Group
+	}
+	getGroupTasksReturns struct {
+		result1 []*task.Task
+		result2 error
+	}
+	getGroupTasksReturnsOnCall map[int]struct {
+		result1 []*task.Task
+		result2 error
+	}
+	TaskListStub        func(context.Context, string) (*tasklist.TaskList, error)
+	taskListMutex       sync.RWMutex
+	taskListArgsForCall []struct {
+		arg1 context.Context
+		arg2 string
+	}
+	taskListReturns struct {
+		result1 *tasklist.TaskList
+		result2 error
+	}
+	taskListReturnsOnCall map[int]struct {
+		result1 *tasklist.TaskList
+		result2 error
+	}
+	TaskListHistoryStub        func(context.Context, string) (*tasklist.TaskList, error)
+	taskListHistoryMutex       sync.RWMutex
+	taskListHistoryArgsForCall []struct {
+		arg1 context.Context
+		arg2 string
+	}
+	taskListHistoryReturns struct {
+		result1 *tasklist.TaskList
+		result2 error
+	}
+	taskListHistoryReturnsOnCall map[int]struct {
+		result1 *tasklist.TaskList
+		result2 error
+	}
 	TaskListTemplateStub        func(context.Context, string) (*tasklist.Template, error)
 	taskListTemplateMutex       sync.RWMutex
 	taskListTemplateArgsForCall []struct {
@@ -42,6 +84,201 @@ type FakeStorage struct {
 	invocationsMutex sync.RWMutex
 }
 
+func (fake *FakeStorage) GetGroupTasks(arg1 context.Context, arg2 *tasklist.Group) ([]*task.Task, error) {
+	fake.getGroupTasksMutex.Lock()
+	ret, specificReturn := fake.getGroupTasksReturnsOnCall[len(fake.getGroupTasksArgsForCall)]
+	fake.getGroupTasksArgsForCall = append(fake.getGroupTasksArgsForCall, struct {
+		arg1 context.Context
+		arg2 *tasklist.Group
+	}{arg1, arg2})
+	stub := fake.GetGroupTasksStub
+	fakeReturns := fake.getGroupTasksReturns
+	fake.recordInvocation("GetGroupTasks", []interface{}{arg1, arg2})
+	fake.getGroupTasksMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeStorage) GetGroupTasksCallCount() int {
+	fake.getGroupTasksMutex.RLock()
+	defer fake.getGroupTasksMutex.RUnlock()
+	return len(fake.getGroupTasksArgsForCall)
+}
+
+func (fake *FakeStorage) GetGroupTasksCalls(stub func(context.Context, *tasklist.Group) ([]*task.Task, error)) {
+	fake.getGroupTasksMutex.Lock()
+	defer fake.getGroupTasksMutex.Unlock()
+	fake.GetGroupTasksStub = stub
+}
+
+func (fake *FakeStorage) GetGroupTasksArgsForCall(i int) (context.Context, *tasklist.Group) {
+	fake.getGroupTasksMutex.RLock()
+	defer fake.getGroupTasksMutex.RUnlock()
+	argsForCall := fake.getGroupTasksArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeStorage) GetGroupTasksReturns(result1 []*task.Task, result2 error) {
+	fake.getGroupTasksMutex.Lock()
+	defer fake.getGroupTasksMutex.Unlock()
+	fake.GetGroupTasksStub = nil
+	fake.getGroupTasksReturns = struct {
+		result1 []*task.Task
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeStorage) GetGroupTasksReturnsOnCall(i int, result1 []*task.Task, result2 error) {
+	fake.getGroupTasksMutex.Lock()
+	defer fake.getGroupTasksMutex.Unlock()
+	fake.GetGroupTasksStub = nil
+	if fake.getGroupTasksReturnsOnCall == nil {
+		fake.getGroupTasksReturnsOnCall = make(map[int]struct {
+			result1 []*task.Task
+			result2 error
+		})
+	}
+	fake.getGroupTasksReturnsOnCall[i] = struct {
+		result1 []*task.Task
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeStorage) TaskList(arg1 context.Context, arg2 string) (*tasklist.TaskList, error) {
+	fake.taskListMutex.Lock()
+	ret, specificReturn := fake.taskListReturnsOnCall[len(fake.taskListArgsForCall)]
+	fake.taskListArgsForCall = append(fake.taskListArgsForCall, struct {
+		arg1 context.Context
+		arg2 string
+	}{arg1, arg2})
+	stub := fake.TaskListStub
+	fakeReturns := fake.taskListReturns
+	fake.recordInvocation("TaskList", []interface{}{arg1, arg2})
+	fake.taskListMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeStorage) TaskListCallCount() int {
+	fake.taskListMutex.RLock()
+	defer fake.taskListMutex.RUnlock()
+	return len(fake.taskListArgsForCall)
+}
+
+func (fake *FakeStorage) TaskListCalls(stub func(context.Context, string) (*tasklist.TaskList, error)) {
+	fake.taskListMutex.Lock()
+	defer fake.taskListMutex.Unlock()
+	fake.TaskListStub = stub
+}
+
+func (fake *FakeStorage) TaskListArgsForCall(i int) (context.Context, string) {
+	fake.taskListMutex.RLock()
+	defer fake.taskListMutex.RUnlock()
+	argsForCall := fake.taskListArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeStorage) TaskListReturns(result1 *tasklist.TaskList, result2 error) {
+	fake.taskListMutex.Lock()
+	defer fake.taskListMutex.Unlock()
+	fake.TaskListStub = nil
+	fake.taskListReturns = struct {
+		result1 *tasklist.TaskList
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeStorage) TaskListReturnsOnCall(i int, result1 *tasklist.TaskList, result2 error) {
+	fake.taskListMutex.Lock()
+	defer fake.taskListMutex.Unlock()
+	fake.TaskListStub = nil
+	if fake.taskListReturnsOnCall == nil {
+		fake.taskListReturnsOnCall = make(map[int]struct {
+			result1 *tasklist.TaskList
+			result2 error
+		})
+	}
+	fake.taskListReturnsOnCall[i] = struct {
+		result1 *tasklist.TaskList
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeStorage) TaskListHistory(arg1 context.Context, arg2 string) (*tasklist.TaskList, error) {
+	fake.taskListHistoryMutex.Lock()
+	ret, specificReturn := fake.taskListHistoryReturnsOnCall[len(fake.taskListHistoryArgsForCall)]
+	fake.taskListHistoryArgsForCall = append(fake.taskListHistoryArgsForCall, struct {
+		arg1 context.Context
+		arg2 string
+	}{arg1, arg2})
+	stub := fake.TaskListHistoryStub
+	fakeReturns := fake.taskListHistoryReturns
+	fake.recordInvocation("TaskListHistory", []interface{}{arg1, arg2})
+	fake.taskListHistoryMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeStorage) TaskListHistoryCallCount() int {
+	fake.taskListHistoryMutex.RLock()
+	defer fake.taskListHistoryMutex.RUnlock()
+	return len(fake.taskListHistoryArgsForCall)
+}
+
+func (fake *FakeStorage) TaskListHistoryCalls(stub func(context.Context, string) (*tasklist.TaskList, error)) {
+	fake.taskListHistoryMutex.Lock()
+	defer fake.taskListHistoryMutex.Unlock()
+	fake.TaskListHistoryStub = stub
+}
+
+func (fake *FakeStorage) TaskListHistoryArgsForCall(i int) (context.Context, string) {
+	fake.taskListHistoryMutex.RLock()
+	defer fake.taskListHistoryMutex.RUnlock()
+	argsForCall := fake.taskListHistoryArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeStorage) TaskListHistoryReturns(result1 *tasklist.TaskList, result2 error) {
+	fake.taskListHistoryMutex.Lock()
+	defer fake.taskListHistoryMutex.Unlock()
+	fake.TaskListHistoryStub = nil
+	fake.taskListHistoryReturns = struct {
+		result1 *tasklist.TaskList
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeStorage) TaskListHistoryReturnsOnCall(i int, result1 *tasklist.TaskList, result2 error) {
+	fake.taskListHistoryMutex.Lock()
+	defer fake.taskListHistoryMutex.Unlock()
+	fake.TaskListHistoryStub = nil
+	if fake.taskListHistoryReturnsOnCall == nil {
+		fake.taskListHistoryReturnsOnCall = make(map[int]struct {
+			result1 *tasklist.TaskList
+			result2 error
+		})
+	}
+	fake.taskListHistoryReturnsOnCall[i] = struct {
+		result1 *tasklist.TaskList
+		result2 error
+	}{result1, result2}
+}
+
 func (fake *FakeStorage) TaskListTemplate(arg1 context.Context, arg2 string) (*tasklist.Template, error) {
 	fake.taskListTemplateMutex.Lock()
 	ret, specificReturn := fake.taskListTemplateReturnsOnCall[len(fake.taskListTemplateArgsForCall)]
@@ -180,6 +417,12 @@ func (fake *FakeStorage) TaskTemplatesReturnsOnCall(i int, result1 map[string]*t
 func (fake *FakeStorage) Invocations() map[string][][]interface{} {
 	fake.invocationsMutex.RLock()
 	defer fake.invocationsMutex.RUnlock()
+	fake.getGroupTasksMutex.RLock()
+	defer fake.getGroupTasksMutex.RUnlock()
+	fake.taskListMutex.RLock()
+	defer fake.taskListMutex.RUnlock()
+	fake.taskListHistoryMutex.RLock()
+	defer fake.taskListHistoryMutex.RUnlock()
 	fake.taskListTemplateMutex.RLock()
 	defer fake.taskListTemplateMutex.RUnlock()
 	fake.taskTemplatesMutex.RLock()
diff --git a/internal/storage/storage.go b/internal/storage/storage.go
index cc16df1cd4392949218e17a91b7ab8995395864d..4a8d1ffaf7089f11273e316f45336238d833579e 100644
--- a/internal/storage/storage.go
+++ b/internal/storage/storage.go
@@ -315,3 +315,45 @@ func (s *Storage) SaveTaskListHistory(ctx context.Context, taskList *tasklist.Ta
 	b := backoff.WithContext(backoff.NewExponentialBackOff(), ctx)
 	return backoff.Retry(insert, b)
 }
+
+// TaskList retrieves a tasklist.TaskList from taskLists collection by ID
+func (s *Storage) TaskList(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+	result := s.taskLists.FindOne(ctx, bson.M{
+		"id": taskListID,
+	})
+
+	if result.Err() != nil {
+		if strings.Contains(result.Err().Error(), "no documents in result") {
+			return nil, errors.New(errors.NotFound, "taskList not found")
+		}
+		return nil, result.Err()
+	}
+
+	var list tasklist.TaskList
+	if err := result.Decode(&list); err != nil {
+		return nil, err
+	}
+
+	return &list, nil
+}
+
+// TaskListHistory retrieves a tasklist.TaskList from taskListHistory collection by ID
+func (s *Storage) TaskListHistory(ctx context.Context, taskListID string) (*tasklist.TaskList, error) {
+	result := s.taskListHistory.FindOne(ctx, bson.M{
+		"id": taskListID,
+	})
+
+	if result.Err() != nil {
+		if strings.Contains(result.Err().Error(), "no documents in result") {
+			return nil, errors.New(errors.NotFound, "taskList not found")
+		}
+		return nil, result.Err()
+	}
+
+	var list tasklist.TaskList
+	if err := result.Decode(&list); err != nil {
+		return nil, err
+	}
+
+	return &list, nil
+}