How to design REST API for non-CRUD "commands" like activate and deactivate of a resource?

15,599

Solution 1

How about coming up with a noun for the feature you want to modify - 'status' in this instance. This would then become a sub resource of the parent entity. So for your case I would model the URI as follows:

/api/accounts/{accountId}/status

If the 'update' semantics are idempotent then PUT would be most appropriate, else that would need to be a POST (e.g if nonces are involved and are invalidated by the service). The actual payload would include a descriptor for the new state.

Note, I pluralized 'accounts' since you can have multiple of those, but status is singular since your account can have only one state.

Solution 2

PATCH is the most appropriate method in this case. Please find more at RESTful URL for "Activate"

Solution 3

The POST method would create the resource 'account'. Active can be seen as one of the properties of the resource 'account'. Hence it should be a PUT request.

I would say even deactivate must be a PUT request as the account resource will still exist.

To activate an account you can set a property on the resource. That is:

/api/account/{accountId}?activate=true

To deactivate:

/api/account/{accountId}?activate=false

A GET request on the account would return a JSON with the activate value in it.

A DELETE request should completely delete the account resource.

Solution 4

First off, PUT is appropriate compared to POST, because you are creating a resource to an already-known location. And, I think, there's no dilemma about DELETE. So at first glance, your current approach seems to beat the alternatives.

I used to think the same way, until I implemented my own REST api, in which I wanted the admin to be able to set an account in a deactivated - yet not deleted, just "banned" - state. When I gave it a little more thought, I decided to do it vice versa.

Let me explain. I like to see the activation resource as "the option to activate the account". So if a url like /account/foo/activation exists, it could only mean that the account is not activated and the user has the right to activate it. If it doesn't exist, the account is either already activated or in a banned state.

Consequently, the only rational thing to do in order to activate the account is to try and DELETE the resource. And, in order to enable activation, an admin would have to PUT an activation resource.

Now, the question that comes to mind is how do you distinguish a banned account from an already activated one. But since a ban could be seen as a resource too, you could create a /account/foo/ban resource collection. In order to ban an account, probably for a fixed amount of time, you just POST a resource under that collection, containing all the details of the ban.

Share:
15,599
Adam Bogdan Boczek
Author by

Adam Bogdan Boczek

Updated on June 27, 2022

Comments

  • Adam Bogdan Boczek
    Adam Bogdan Boczek almost 2 years

    Before I decided to ask this question I have searched quite a long for the answer but I haven't found any satisfactory. (e.g. Examples of the best SOAP/REST/RPC web APIs? And why do you like them? And what's wrong with them?)

    And the problem is actually quite simple. I have an object/resource named Account. My REST API supports all CRUDs with GET, POST, PUT and DELETE already with proper error handling, status codes etc.

    Additionally however I want to expose an API ("command") to activate and deactivate selected Account resource. Even if the "isActive" is a property of the Account I don't want to use just the Update from my CRUD of the whole Account.

    I know it is easy to violate REST principles and make RPC style design with such design like this:

    PUT /api/account/:accountId/activate

    PUT /api/account/:accountId/deactivate

    So what is the best solution for this use case?

    My current idea is to use PUT and DELETE verbs like this (to treat it as a sub-resource) as proposed here http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api#restful:

    PUT /api/account/:accountId/isActive // for activate

    DELETE /api/account/:accountId/isActive // for deactivate

    What are your solutions?