Using GET for sending information to server in REST APIs - rest

Until now, I used to think the only difference between GET and POST is the format on data trasmission.
In other examples I have seen that in REST Api, the only command to send information is POST, while for getting information is GET...
But what if I want to use GET also for information sending? I have to send a number of dataset names to be processed (usually a short number...)... I don't understand why GET doesn't fits this operation. Or it's only a "semantic" reason?
EDIT: I don't want answer about the general differences between GET or POST... I need to know if GET should not be used to update info of the server IN ALL CASES, in particular regarding my specific case, or there are some exceptions.

I don't understand why GET doesn't fits this operation. Or it's only a "semantic" reason?
Yes, but semantics are the entire point.
One of the important constraints of the REST architectural style is that of a uniform interface; the idea that all components understand messages the same way. What that gives us is the ability to achieve useful work using general purpose components.
In HTTP (an application built within the constraints of this style), that means that we can use browsers, and caches, and reverse proxies, and server toolkits, and so forth, mixing and matching as we go. It all works, because all of these components understand that a request should be interpreted as described in RFC 7230, and that the request method token is "the primary source of request semantics", and so on.
The semantics of GET have been defined, and we all share in that definition. The wording of the definition has evolved somewhat from its earliest specifications, but the basic semantics have been constant: that GET is effectively read-only, and that the message body (of the request) is of no semantic interest.
If you want to use an HTTP method where the message body is semantically significant, then you need to either relax some of the constraints of GET (for instance, by using POST), choose a different standardized method that better fits your use case (see the IANA HTTP Method Registry), or by defining (and potentially standardizing) your own HTTP method.
The core problem with trying to define a payload for GET - while your bespoke client may know what to do, and your bespoke resource may know what to do, general-purpose intermediates are likely to do the wrong thing (like caching responses without capturing the information in the request body, or discarding the body as unnecessary).
Note that information that is encoded into the target-uri works just fine; HTML forms using the GET method and other variations of URI templates can be used to construct a target-uri from local information that the server can interpret (of course, the defacto limits on target-uri length are much shorter than the defacto limits on payload size; it's not a universal solution).

Related

REST API: Does validation on identifiers break encapsulation? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 months ago.
Improve this question
I figured I'd post here to get some ideas/feedback on something I've come up against recently. The API I've developed has validation on an identifier that's passed through as a path parameter:
e.g. /resource/resource_identifier
There are some specific business rules as to what makes an idenfier valid and my API has validation which enforces these rules and returns a 400 when that's violated.
Now the reason I'm writing this is that I've been doing this sort of thing in every REST (ish) API I've ever written. It's kind of ingrained in me now but ecently I've been told that this is 'bad' and that it breaks encapsulation. Furthermore, it does this by forcing a consumer to have knowledge about the format of an identifier. I'm told that I should be returning a 404 instead and simply accept anything as an idenfier.
We've had some pretty heated debates about this and what encapsulation actually means in the context of REST. I've found numerous definitions but they aren't specific. As with any REST contention it's hard to substantiate an argument for either.
If StackOverflow would allow me, I'd like to try and gain a concensus on this and why APIs like Spotify for example, use 400 in this scenario.
I've been doing this sort of thing in every REST (ish) API I've ever written. It's kind of ingrained in me now but recently I've been told that this is 'bad'
In the context of HTTP, it is an "anti-pattern", yes.
I'm told that I should be returning a 404 instead
And that is the right pattern when you want the advantages of responding like a general purpose web server.
Here's the point: if you want general purpose components in the HTTP application to be able to do sensible things with your response messages, then you need to provide them with the appropriate meta data.
In the case of a target resource identifier that satisfies the request-target production rules defined in RFC 9112 but is otherwise unsatisfactory; you can choose any response semantics you want (400? 403? 404? 499? 200?).
But if you choose 404, then general purpose components will know that the response is an error that can be re-used for other requests (under appropriate conditions - see RFC 9111).
why APIs like Spotify for example, use 400 in this scenario.
Remember: engineering is about trade offs.
The benefits of caching may not outweigh more cost effective request processing, or more efficient incident analysis, or ....
It's also possible that it's just habit - it's done that way because that's the way that they have always done it; or because they were taught it as a "best practice", or whatever. One of the engineering trade offs we need to consider is whether or not to invest in analyzing a trade off!
An imperfect system that ships earns more market share than a perfect solution that doesn't.
While it may sound natural to expose the resource internal ID as ID used in the URI, remember that the whole URI itself is the identifier of a resource and not only the last bit of the URI. Clients are usually also not interested in the characters that form the URI (or at least they shouldn't care about it) but only in the state they receive upon requesting that from the API/server.
Further, if you think long-term, which should be the reason why you want to build your design on top of a REST architecture, is there a chance that the internal identifier of a resource could ever change? If so, introducing an indirection could make more sense then i.e. by using UUIDs instead of product IDs in the URI and then have a further table/collection to perform a mapping from UUID to domain object ID. Think of a resource that exposes some data of a product. It may sound like a good idea to use the product ID at the end of the URI as they identify the product in your domain model clearly. But what happens if you company undergoes a merge with an other company that happens to have an overlap on product but then uses different identifiers than you? I've seen such cases in reality, unfortunately, and almost all of them wanted to avoid change for their clients and so had to support multiple URIs for the same products in the end.
This is exactly why Mike Amundsen said
... your data model is not your object model is not your resource model ... (Source)
REST is full of such indirection mechanisms to allow such systems to avoid coupling. I.e. besides above mentioned mechanism, you also have link-relations to allow servers to switch URIs when needed while clients can still lookup the URI via the exposed relation name, or its focus on negotiated media types and its representation formats rather than forcing clients to speak their API-specific RPC-like, plain-JSON slang.
Jim Webber further coined the term domain application protocol to describe that HTTP is an application protocol for exchanging documents and any business rules we infer are just side effects of the actual document management performed by HTTP. So all we do in "REST" is basically to send documents back and forth and infer some business logic to act upon receiving certain documents.
In regards to encapsulation, this isn't the scope of REST nor HTTP. What data you return depends on your business needs and/or on the capabilities of the representation formats exchanged. If a certain media-type isn't able to express a certain capability, providing such data to clients might not make much sense.
In general, I'd would recommend not to use domain internal IDs as part of URIs for the above mentioned reasons. Usually that information should be part of the exchanged payload to give users/customers the option to refer to that resources on other channels like e/mail, telephone, ... Of course, that depends on the resource and its purpose at hand. As a user I'd prefer to refer to myself with my full name rather than some internal user- or customer ID or the like.
edit: sorry, missed the validation aspect ...
If you expect user/client input on the server/API side, you should always validate the data before starting to process it. Usually though, URIs are provided by the server and might only trigger business activities if the URI requested matches one of your defined rules. In general, most frameworks will respond with 400 Bad Request responses when they couldn't map the URI to a concrete action, giving the client a chance to correct its mistake and reissue the updated request. As URIs shouldn't be generated or altered by clients anyways, validating such parameters might be unnecessary overhead unless they might introduce security risks. Here it might be a better approach then to toughen-up the mapping rules of URIs to actions then and let those frameworks respond with a 400 message when clients use stuff they aren't supposed to.
Encapsulation makes sense when we want to hide data and implementation behind an interface. Here we want to expose the structure of the data, because it is for communication, not for storage and the service certainly needs this communication in order to function. Validation of data is a very basic concept, because it makes the service reliable and because is protects against hacking attempts. The id here is a parameter and checking its structure is just parameter validation, which should return 400 if failed. So this is not restricted to the body of the request, the problem can be anywhere in the HTTP message as you can read below. Another argument against 404 that the requested resource cannot possibly exist, because we are talking about a malformed id and so a malformed URI. It is very important to validate every user input, because a malformed parameter can be used for injections e.g. for SQL injection if it is not validated.
The HyperText Transfer Protocol (HTTP) 400 Bad Request response status
code indicates that the server cannot or will not process the request
due to something that is perceived to be a client error (for example,
malformed request syntax, invalid request message framing, or
deceptive request routing).
vs
The HTTP 404 Not Found response status code indicates that the server
cannot find the requested resource. Links that lead to a 404 page are
often called broken or dead links and can be subject to link rot.
A 404 status code only indicates that the resource is missing: not
whether the absence is temporary or permanent. If a resource is
permanently removed, use the 410 (Gone) status instead.
In the case of REST we describe the interface using the HTTP protocol, URI standard, MIME types, etc. instead of the actual programming language, because they are language independent standards. As of your specific case it would be nice to check the uniform interface constraints including the HATEOAS constraint, because if your service makes the URIs as it should, then it is clear that a malformed id is something malicious. As of Spotify and other APIs, 99% of them are not REST APIs, maybe REST-ish. Read the Fielding dissertation and standards instead of trying to figure it out based on SO answers and examples. So this a classic RTFM situation.
In the context of REST a very simple example of data hiding is storing a number something like:
PUT /x {"value": "111"} "content-type:application/vnd.example.binary+json"
GET /x "accept:application/vnd.example.decimal+json" -> {"value": 7}
Here we don't expose how we store the data. We just send the binary and decimal representations of it. This is called data hiding. In the case of id it does not make sense to have an external id and convert it to an internal id, it is why you use the same in your database, but it is fine to check if its structure is valid. Normally you validate it and convert it into a DTO.
Implementation hiding is more complicated in this context, it is sort of avoiding micromanagement with the service and rather implement new features if it happens frequently. It might involve consumer surveys about what features they need and checking logs and figuring out why certain consumers send way too many messages and how to merge them into a single one. For example we have a math service:
PUT /x 7
PUT /y 8
PUT /z 9
PUT /s 0
PATCH /s {"add": "x"}
PATCH /s {"add": "y"}
PATCH /s {"add": "z"}
GET /s -> 24
vs
POST /expression {"sum": [7,8,9]} -> 24
If you want to translate between structured programming, OOP and REST, then it is something like this:
Number countCartTotal(CartId cartId);
<=>
interface iCart {
Number countTotal();
}
<=>
GET api/cart/{cartid}/total -> {total}
So an endpoint represents an exposed operation something like verbNoun(details) e.g. countCartTotal(cartId), which you can split into verb=countTotal, noun=cart, details=cartId and build the URI from it. The verb must be transformed into a HTTP method. In this case using GET makes the most sense, because we need data instead of sending data. The rest of the verb must be transformed into a noun, so countTotal -> GET totalCount. Then you can merge the two nouns: totalCount + cart -> cartTotal. Then you can build an URI template based on the resulting noun and the details: cartTotal + cartId -> cart/{cartid}/total and you are done with the endpoint design GET {root}/cart/{cartid}/total. Now you can bind it to the countCartTotal(cartId) or to the repo.resource(iCart, cartId).countTotal().
So I think if the structure of the id does not change, then you can even add it to the API documentation if you want to. Though it is not necessary to do so.
From security perspective you can return 404 if the only possible reason to send such a request is a hacking attempt, so the hacker won't know for certain why it failed and you don't expose details of the protection. In this situation it would be overthinking the problem, but in certain scenarios it makes sense e.g. where the API can leak data. For example when you send a password reset link, then a web application usually asks for an email address and most of them send an error message if it is not registered. This can be used to check if somebody is registered on the site, so better to hide this kind of errors. I guess in your case the id is not something sensitive and if you have proper access control, then even if a hacker knows the id, they cannot do much with that information.
Another possible aspect is something like what if the structure of the id changes. Well we write a different validation code, which allows only the new structure or maybe both structures and make a new version of the API with v2/api and v2/docs root and documentation URIs.
So I fully support your point of view and I think the other developer you mentioned does not even understand OOP and encapsulation, not to mention webservices and REST APIs.

GET Vs POST in REST

According to Rest, we should use GET if we have to retrieve some data, and use POST if it's creating a resource.
But, if we have to take multiple parameters (more than 7-8), or list of UUIDs let's say, then shouldn't we use POST rather than GET?
To avoid:
The complexity of the URL
Future scope to incorporate any new field
URL length which may limit us in future
If we are not encoding our URL then we may risk of exposing params (least significant point though)
Thanks.
GET Vs POST in REST
Use GET when the semantics of GET fit the use case; in other words, when you are trying to retrieve a copy of the latest representation of some resource.
Use POST when no other standardized method supports the semantics you need.
You can use POST for everything, but you give up the advantages of using more specialized methods -- the ability of general purpose components to do intelligent things because they understand the semantics of the request. For instance, GET has safe semantics, which means that a general purpose component knows that it can pre-fetch a representation of the resource before the user needs it, or automatically repeat a request if it doesn't get a response from the server.
What HTTP gives you is a extendable collections of refinements of the general purpose POST method, adding more specific constraints so that general purpose components can leverage the resulting properties.
The complexity of the URL
Complexity in the URL really isn't a big deal, as far as general purpose components are concerned a URL is just a opaque sequence of bytes that happens to abide by certain production rules. For the most part, the effective target-uri of a web request is treated as an atomic unit, so what might seem "complex to a human being doesn't bother the machines at all (for instance, take a look at the URL used when you submit a search from the google home page).
URL length which may limit us in future
We care a bit about URL length. RFC 3986 doesn't restrict the length of the URI, but some implementations of general purpose components will fail if the length is far outside the norm. So you probably don't want to include a url encoded copy of the unabridged works of Shakespeare in the query part of your request.
Future scope to incorporate any new field
Again, there's not a lot of difference here. Adding new optional elements to a URI template is really no different than adding new optional elements to a message template.
we may risk of exposing params
We also want to be careful about sensitive information - as far as the machines are concerned, the URI is an identifier; there's no particular reason to worry about a specific sequence of bytes. Which means that the URI may be exposed at rest (in a clients history, or list of bookmarks, in the servers access logs). Restricting sensitive information to the body of a message reduces the chance of the data escaping beyond its intended use.
Note that REST, and leveraging the different HTTP methods, isn't the only way to get useful work done. SOAP (and more recently gRPC) decided that a different collection of trade offs was better -- in effect, reducing HTTP to transport, rather than an application in itself.
According to Rest, we should ... use POST if it's creating a resource.
This is an incorrect interpretation of REST. It's a very common interpretation, but incorrect. The semantics of POST are defined by RFC 7231; it means that, and not something else.
The suggestion that POST should only be used for create is a misleading over simplification. The earliest references I've been able to find to it is a blog post by Paul Prescod in 2002; and of course it became very popular with the arrival of Ruby on Rails.
But recall: REST is the architectural style of the world wide web. HTML, the most common hypertext media type in use on the web, has native support for only two HTTP methods; GET, used to fetch resource representations from a server, and POST which does everything else.
You should also use POST if you have sensitive data such as username and or password which are best encoded as form parameters (key value pairs)

What is the best practice to HTTP GET only the list of objects that I require

I am in a situation where in I want to REST GET only the objects that I require by addressing them with one of the parameters that I know about those objects.
E.g., I want to GET all the USERS in my system with ids 111, 222, 333.
And the list can be bigger, so I don't think it is best way to append the URL with what is required, but use payload with json.
But I am skeptical using JSON payload in a GET request.
Please suggest a better practice in the REST world.
I am skeptical using JSON payload in a GET request.
Your skepticism is warranted; here's what the HTTP specification has to say about GET
A payload within a GET request message has no defined semantics
Trying to leverage Undefined Behavior is a Bad Idea.
Please suggest a better practice in the REST world.
The important thing to recognize is that URI are identifiers; the fact that we sometimes use human readable identifiers (aka hackable URI) is a convenience, not a requirement.
So instead of a list of system ids, the URI could just as easily be a hash digest of the list of system ids (which is probably going to be unique).
So your client request would be, perhaps
GET /ea3279f1d71ee1e99249c555f3f8a8a8f50cd2b724bb7c1d04733d43d734755b
Of course, the hash isn't reversible - if there isn't already agreement on what that URI means, then we're stuck. So somewhere in the protocol, we're going to need to make a request to the server that includes the list, so that the server can store it. "Store" is a big hint that we're going to need an unsafe method. The two candidates I would expect to see here are POST or PUT.
A way of thinking about what is going on is that you have a single resource with two different representations - the "query" representation and the "response" representation. With PUT and POST, you are delivering to the server the query representation, with GET you are retrieving the response representation (for an analog, consider HTML forms - we POST application/x-www-form-urlencoded representations to the server, but the representations we GET are usually friendlier).
Allowing the client to calculate a URI on its own and send a message to it is a little bit RPC-ish. What you normally do in a REST API is document a protocol with a known starting place (aka a bookmark) and a sequence of links to follow.
(Note: many things are labeled "REST API" that, well, aren't. If it doesn't feel like a human being navigating a web site using a browser, it probably isn't "REST". Which is fine; not everything has to be.)
But I believe POST or PUT are for some requests that modify the data. Is it a good idea to use query requests with them ?
No, it isn't... but they are perfect for creating new resources. Then you can make safe GET calls to get the current representation of the resource.
REST (and of course HTTP) are optimized for the common case of the web: large grain hypermedia, caching, all that good stuff. Various use cases suffer for that, one of which is the case of a transient message with safe semantics and a payload.
TL;DR: if you don't have one of the use cases that HTTP is designed for, use POST -- and come to terms with the fact that you aren't really leveraging the full power of HTTP. Or use a different application - you don't have to use HTTP if its a bad fit.

Is it RESTful to include parameters in a GET request’s body rather than the URI?

I have a complicated schema that requires complicated API calls. For many resource retrievals, the user would want to specify several parameters to filter the results. Including all of these parameters in the URI seems like it would be messy and difficult for front-end developers to craft, so I’ve opted to put the parameters into the request body as JSON. Unfortunately, this doesn’t seem to sit well with the web back-end I’m using (Django-Rest Framework). Is this RESTful, or am I making a mistake?
As a follow-up question, if I should put the parameters in the URI, how would I represent complex pieces of data, like lists of strings, and the relationships between pieces of data?
Is this RESTful, or am I making a mistake?
It sounds to me as though you are making a mistake. The authority in this case is RFC 7231
A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.
My interpretation is this: caching is an important part of the web; for caching to work as people would expect it requires compliant caches to be able to manage that message body as part of the key.
An HTTP method that may serve your needs better is SEARCH.
The SEARCH method plays the role of transport mechanism for the query and the result set. It does not define the semantics of the query. The type of the query defines the semantics.
SEARCH is a safe method; it does not have any significance other than executing a query and returning a query result.
If that doesn't fit your needs, you could look through the HTTP method registry to see if one of the other standards fits your use case.
how would I represent complex pieces of data, like lists of strings, and the relationships between pieces of data?
The real answer is "any way you want" -- the origin server has control of its URI space, and any information encoded into it is done so at the server's convenience for its own use.
You could, for instance, consider using one of the Base64 encodings defined in RFC 4648
From what I read about RESTful, you can only use GET, POST, PUT, PATCH, and DELETE.
The GET and DELETE are not expected to include a body. As #VoiceOfUnreason mentioned, this is mainly because caches can have difficulties handling a body along a GET. That being said, if your results are never cached, it should not be a concern at all. (i.e. return Cache: no-cache and other similar HTTP header from your server.)
There is no real convention on the Query String and supporting lists or JSON and such. If you want to keep a GET, you could use an encoded JSON string, though. There is no problem with that, except the length of the URL.
http://www.example.com/?query=<encoded-json>
(encoded just means that you have to properly escape URI special characters, what the JavaScript encodeURICompent() function does.)
The length of the URL should be kept under 1Kb to be 100% safe. You can do some research on it, I think that the browser with the most stringent limit is around 2k.
If you want to use a bigger query, then you should revert your queries to using a POST and not a GET. Then the buffer is normal in that situation and the reply is not expected to be cached.
Real World Use (but Not An Excuse)
If you look into Elasticsearch, you will see that all their queries accept a JSON. You can send a DSL query using a GET or a POST. Either one accept a JSON in their body.
They offer the POST because most browsers will not accept to attach a body to a GET method. So GET queries would not work at all from a browser.
Example of Arrays in Query Strings
There has been various libraries, and at least PHP, that added support for arrays in parameters. In most cases this is done by supporting the array syntax in the parameter name. For example:
path/?var[1]=123&var[2]=456&var[3]=789
In this case, those languages will convert the value in an array. $_GET['var'][1] would then return 123.
This is not a convention, just an extension of that specific environment.

Correct HTTP request method for nullipotent action that takes binary data

Consider a web API method that has no side effects, but which takes binary data as a parameter. An example would be a method that tells the user whether or not their image is photoshopped, but does not permanently store the image or the result on its servers.
Should such a method be a GET or a POST?
GET doesn't seem to have a recommended way of sending data outside of URL parameters, but the behavior of the method implies a GET, which according to the HTTP spec is for safe, stateless responses. This becomes particularly constraining under the semantics of REST, which imply that POST methods create a new object on the server.
This becomes particularly constraining under the semantics of REST, which imply that POST methods create a new object on the server.
While a POST request means that the entity sent will be treated "as a new subordinate of the resource identified by the Request-URI", there is no requirement that this result in the creation of a new permanent object or that any such new object be identified by a URI (so no new object as far as the client knows). An object can be transient, representing the results of e.g. "Providing a block of data, such as the result of submitting a form, to a data-handling process" and not persisting after the entity representing that object has been sent.
While this means that a POST can create a new resource, and is certainly the best way to do so when it is the server that will give that new resource its URI (with PUT being the more appropriate method when the client dictates the new URI) it is can also be used for cases that delete objects (though again if it's a deletion of a single* resource identifiable by a URI then DELETE is far more appropriate), both create and delete objects, change multiple objects, it can mean that your kitchen light turns on but that the response is the same whether that worked or failed because the communication from the webserver to the kitchen light doesn't allow for feedback about success. It really can do anything at all.
But, your instincts are good in wanting this to be a GET: While the looseness of POST means we can make a case for it for just about every request (as done by approaches that use HTTP for an RPC-like protocol, essentially treating HTTP as if it was a transport protocol), this is inelegant in theory, inefficient in practice and clumsy in definition. You have an idempotent function that depends on solely on what the client is interested in, and that maps most obviously the GET in a few ways.
If we could fit everything in a URI then GET would be easy. E.g we can define a simple integer addition with something like http://example.net/addInts?x=1;y=2 representing the addition of 71 and 2 and hence being a permanent immutable resource representing the number 3 (since the results of GET can vary with changes to a resource over time, but this resource never changes) and then use a mechanism like HTML's <form> or javascript to allow the server to inform the client as to how to construct the URIs for other numbers (to maintain the HATEOS and/or COD constraints). Simples!
Your problem here is that you do not have input that can be represented as concisely as the numbers 1 and 2 can above. In theory you could do something like http://example.net/photoshoppedCheck?image=data:image/png;base64,iVBORw0KGgoAAAANSU… and hence create a URI that represents the resource of the results of the check. This URI will though will have 4 characters for every 3 bytes in the image. While there's no absolute limit on URI length both the theory and the practice allow this to fail (in theory HTTP allows for proxies and servers to set a limit on URI length, and in practice they do).
An argument could be made for using GET and sending a request body the same way as you would with a POST, and some webservers will even allow you to do this. However, GET is defined as returning an entity describing the resource identified in the URI with headers restricting how that entity does that describing: Since the request body is not part of that definition it must be ignored by your code! If you were tempted to bend this rule then you must consider that:
Some webservers will refuse the request or strip the body, so you may not be able to.
If your webserver does allow it, the fact that its not specified means you can't be sure an upgrade won't "fix" this and so break your code.
Some proxies will refuse or strip the request.
Some client libraries will most certainly refuse to allow developers to send a request body along with a GET.
So it's a no-no in both theory and practice.
About the only other approach we could do apart from POST is to have a URI that we consider as representing an image that was not photoshopped. Hence if you GET that you get an entity describing the image (obviously it could be the actual image, though it could also be something else if we stretch the concept of content-negotiation) and then PUT will check the image and if its deemed to not be photoshopped it responds with the same image and a 200 or just a 204 while if it is deemed to be photoshopped it responds with a 400 because we've tried to PUT a photoshopped image as a resource that can only be a non-photoshopped image. Because we respond immediately, there's no race-condition with simultaneous requests.
Frankly, this would be darn-right horrible. While I think I've made a case for it by the letter of the specs, it's just nasty: REST is meant to help us design clear APIs, not obtuse APIs we can offer a too-clever-for-its-own-good justification of.
No, in all the way to go here is to POST the image to a fixed URI which then returns a simple entity describing the analysis.
It's perfectly justifiable as REST (the POST creates a transient object based on that image, and then responds with an entity describing that object, and then that object disappears again). It's straight-forward. It's about as efficient as it could be (we can't do any HTTP caching† but most of the network delay is going to be on the upload rather than the download anyway). It also fits into the general use-case of "process something" that POST was first invented for. (Remember that first there was HTTP, then REST described why it worked so well, and then HTTP was refined to better play to those strengths).
In all, while the classic mistake that moves a web application away from REST is to abuse POST into doing absolutely everything when GET, PUT and DELETE (and perhaps the WebDAV methods) would be superior, don't be afraid to use its power when those don't meet the bill, and don't think that the "new subordinate of the resource" has to mean a full long-lived resource.
*Note that a "single" resource here might be composed of several resources that may have their own URIs, so it can be easy to have a single DELETE that deletes multiple objects, but if deleting X deletes A, B & C then it better be obvious that you can't have A, B or C if you don't have X or your API is not going to be understandable. Generally this comes down to what is being modelled, and how obvious it is that one thing depends on another.
†Strictly speaking we can, as we're allowed to send cache headers indicating that sending an identical entity to the same URI will have the same results, but there's no general-purpose web-software that will do this and your custom client can just "remember" the opinion about a given image itself anyway.
It's a difficult one. Like with many other scenarios there is no absolutely correct way of doing it. You have to try to interpret RESTful principles in terms of the limitations of the semantics of HTTP. (Incidentally, I don't think it's right to think of REST having semantics, REST is an architectural style which is commonly used with HTTP services, but can be used for any type of interface.)
I've faced a similar situation in my current project. We chose to use a POST but with the response code being a 200 (OK) rather than the 201 (Resource Created) usually returned by RESTful Web APIs.