The short, direct answer
Since the request is to execute the list of tasks, then if the task group has been executed (that is, regardless of execution result), then semantically speaking, the response status should be 200 OK
because the request to execute the group succeeded. Otherwise, if there was a problem in fulfilling the request to execute the task group, then the response status should denote that error.
The long answer
You are experiencing this dilemma because of an incoherence between what you are doing and what HTTP was designed for. When a URI (Unified Resource Identifier) diverts from being an identifier for a resource, and looks more like a name of a function, you are likely doing something wrong.
The short and direct answer is the right one if you were posting tasks to a queue, however it is not entirely coherent with the implied approach that your implementation has toward its HTTP API.
With the above being said, and without courage to turn this answer into a long opinionated guide, the following is an example for a URI scheme that came to mind with the intention of sparking inspiration:
/tasks
GET
responds with a list of all tasks, likely paginated via Query StringPOST
will add a single task to a queue, returning the task ID/tasks/task/[id]
GET
responds with the task's status, perhaps information about when it was queued and executed, its type and the rest of the propertiesPUT
if no task with that ID exists, then adds a single task with that ID, same request body as aPOST
to/tasks
. If one exists, responds with appropriate status codeDELETE
if the task was not already executed, then cancel it. If it was executed, responds with appropriate status code/tasks/groups
GET
responds with a list of all task groups, likely paginated via Query StringPOST
receives an object with an array of the same body that/tasks
would, along with other group-specific properties (or just that array property if no other properties). Responds with an object that contains the ID of the group, along with an array of the IDs of the individual tasks./tasks/groups/group/[id]
GET
responds with an object denoting the execution status for the entire group (i.e. whether execution finished for all tasks, or if there are still tasks executing), each individual task's status in an array, and more properties regarding that group if any (like when it was requested, when it was complete, etc). A request with?awaitCompletion=true
query string will respond when the group finishes execution.PUT
if no group with that ID exists, adds a group with that ID. Otherwise, responds with appropriate error status.DELETE
if there are still tasks executing in the group, cancels them.
Executing a task group and tracking progress:
POST /tasks/groups
with the group of tasks to executeGET /tasks/groups/group/[groupId]
until response objectcompleted
property has value, showing individual task status (3 tasks completed out of 5, for example)- Denote completion with last request results (Executed 5 tasks, 1 task failed)
Requesting an execution for a task group and waiting for its completion:
POST /tasks/groups
with the group of tasks to executeGET /tasks/groups/group/[groupId]?awaitCompletion=true
until responds with result that denotes completion (there's likely a timeout on how long this will wait)
Why it's hard to accept this
The above scheme requires that the HTTP server communicates with other services to execute tasks rather than executing them in the request handler. This isn't a shallow implementation, it's a change in direction.
You generally don't want to block very long in an HTTP request handler. Much like UI, you want to be responsive. In a timescale that is a few orders of magnitude slower because you are dealing with IO.