HTTP Status Codes – What Code to Return for Multiple Actions with Different Statuses

apihttp

I am building an API where the user can ask the server to perform multiple actions in one HTTP request. The result is returned as a JSON array, with one entry per action.

Each of these actions might fail or succeed independently of each other. For instance, the first action might succeed, the input to the second action might be poorly formatted and fail to validate and the third action might cause an unexpected error.

If there was one request per action, I would return status codes 200, 422 and 500 respectively. But now when there is only one request, what status code should I return?

Some options:

  • Always return 200, and give more detailed information in the body.
  • Maybe follow the above rule only when there is more than one action in the request?
  • Maybe return 200 if all requests succeed, otherwise 500 (or some other code)?
  • Just use one request per action, and accept the extra overhead.
  • Something completely different?

Best Answer

The short, direct answer

Since the request speaks of executing the list of tasks (tasks are the resource that we're speaking of here), then if the task group has been moved forward to execution (that is, regardless of execution result), then it would be sensible that the response status will be 200 OK. Otherwise, if there was a problem that would prevent execution of the task group, such as failing validation of the task objects, or some required service isn't available for example, then the response status should denote that error. Past that, when execution of the tasks commences, seeing as the tasks to perform are listed in the request body, then I would expect that the execution results will be listed in the response body.


The long, philosophical answer

I suspect that you are experiencing this dilemma because you are diverting from what HTTP was designed for. I suspect that you are attempting to use it as means of RMI (Remote Method Invocation) rather than as means to manage resources.

The RMI perspective is that you would design your URI scheme as you would functions in an application, and upon request, these would execute an action, and then return its result. Although these types of implementations are relatively common still, these often produce situations HTTP is in the way, rather than making things easy.

(Just to note, RMI through HTTP has its merits in some instances, though you'd still be wise to implement these methods in a non-blocking manner. You could maybe offer startTask and getTaskStatus for instance, both of which would return instantly.)

The design of HTTP is asking you to use it to manage resources instead. It wants to express things like "add a task" (via POST), "get a task" (via GET), "delete a task" (via DELETE) and so on. Designing our URI scheme in that way, we rarely find ourselves in conflict with what HTTP has to offer.

To provide an example of what I mean by a URI scheme that conforms to resource management (vs RMI), here's a layout that might work for your case:

  • /task?complete=[true/false]&start=[start_timestamp]&end=[end_timestamp] ...
    • GET searches for tasks according to querystring
    • POST adds a single task
  • /task/[id]
    • GET responds with a single task's state object
  • /task/[id]/cancellation_request
    • POST adds a cancellation request for the task.
  • /task/[id]/[property_name]
    • GET returns the value of the property of a task of the specified id
  • /task_group?complete=[true/false]&start=[start_timestamp]&end=[end_timestamp] ...
    • GET searches for task groups according to querystring
    • POST adds a group of tasks
  • /task_group/[id]
    • GET responds with a task group object, which includes a list of task objects of all of the tasks in the group.

... and so on

As you may have guessed, task execution in this scheme would be an asynchronous thing -- POST to /task would not wait until the task has completed, or even until it actually started running -- It would simply queue it for execution and then respond that it succeeded to add that task, or that it failed, if the queue is full, for instance.

Note how the URIs have no verbs in them -- They represent resources or collections of resources. The only verbs in this entire scheme are the HTTP methods that are invoked upon these URIs (GET/POST in this case).

Just to hammer this in a little more, URI stands for "Unified Resource Identifier".


Examples of how the above URI scheme would be used

Executing a single task and tracking progress:

  • POST /task with the task to execute
  • GET /task/[id] until response object complete has positive value while showing current status/progress. You can also implement updates with websocket if you want to avoid polling.

Executing a task group and tracking progress:

  • POST /task_group with the group of tasks to execute
  • GET /task_group/[groupId] until response object complete property has positive value, showing individual task status (3 tasks completed out of 5, for example)