Ultimately, I think your question has roots in the common misuse of HTTP verbs.
It's common to equate a PUT
to an UPDATE
in an RDBMs but it's actually more like an 'UPSERT' (i.e. update or insert if not present).
So as pointed out by Robert Harvey in comments on another answer, at first glance, it doesn't make sense to return a 404 not found in the case that you understand what kind of resource is being updated (or it's parent). Consider the following URI:
https://domain.com/{root}/{parent}/{child}
So given the above it makes sense to return a 404 if you try to PUT
against a root
or parent
that don't exist and if these are fixed, then that's likely what a decent framework will do. If child
doesn't exist, then we can create it. However, there are some caveats.
Allow me an aside: Suppose you do allow clients to create resources with PUT
as described above. But lets suppose multiple users get to create off of the same root/parent path. What should happen if one user tried to PUT
to a resource created by another (unaffiliated) user? There are two possibilities here: they have made an error such as trying to create an new resource or they are purposely trying to do something bad. In either case, you don't want to allow that so you can return a 403
(forbidden) error. But you are also allowed to return a 404
in the case that you don't want to give away info about whether that path exists. But if you only return a 201
when a path doesn't exist, you again leak information. This suggests that 404
could be used in this kind of scenario. It also brings into question whether such a scheme is a good idea. If you make a user/client identifier part of the path, you can avoid such accidental collision in the first place. I won't say it never makes sense to go about things this way but it does potentially make it harder to distinguish between error scenarios on the client.
But what if I don't want to let users create their own resources with PUT
?
No problem: you don't have to. Just because the spec says PUT
can create or update doesn't mean you are forced to create any URI they send on a PUT
. It should be apparent that you can create rules around what URIs can be created with PUT
against a path and one valid rule is 'none'. Some error codes that might work for that are 400
or 422
. And in the scenario above I made the argument that a 403
or 404
might also make sense. Personally, I would avoid 404
here and use a 'bad input' response to make it clear that you aren't allowing creation of resources with PUT
.
There's also a bit of confusion around POST
. While it's somewhat aligned with the concept of an INSERT
, there's one aspect that I think a lot of people miss: the path in a POST
request doesn't refer to the resource being created. It's the path of the parent resource. That is when you POST
to
https://domain.com/{root}/{parent}
you are asking to create a new resource
https://domain.com/{root}/{parent}/{child}
and the identifier child
is created by the server (although nothing says you can't have the client specify that in the content of the request.)
So in the context of your question, it doesn't make sense to return a 404
when the child doesn't exist because you are asking the server to create it and it shouldn't be part of the URI anyway. However, if the parent resource doesn't exist, it makes perfect sense to return a 404
in that case as the URI would be asking to create a child of a non-existent resource.
To conclude, I would suggest avoiding the use of 404
with PUT
and POST
to indicate the lack of a child entity under here because it a little confusing at best and potentially just plain wrong. It does make sense to use 404
to indicate that the parent path is non-existent or not accessible by the client.
409 Conflict
if that's a concern.