Communicating API Resource Creation

Dillon Redding
CodeX
Published in
4 min readAug 2, 2021

--

After my last post, I thought I’d make another about HTTP etiquette, this time regarding a common misuse of the 201 (Created) status code in APIs. This one can be a bit tricky because, while you might be using the status code itself correctly, there is another part of the message that has implications you might not be aware of.

The way I see 201 used most is in response to a POST request for appending an item to a collection. Something like this:

We have a collection at /books and we want to add a book to it. The typical response is usually pretty simple:

In the response, we usually get some sort of server-generated identifier in the representation (id in the example above). This has nothing to do with HTTP, but what our API is typically trying to communicate is that there’s a new resource at /books/1234, however this approach creates some problems.

First, clients are required to understand the server’s URL design strategy. To later access the new resource, they have to know to take the value of the identifier and append that as a path segment to the collection’s URL. (That’s fine if your media type defines this behavior, but I’m focused on the conventional “REST” APIs that use plain JSON. More on this in a later post.) This requirement couples clients to the servers because URLs are opaque and thus an implementation detail of the server.

Second, and more to the point of this post, is an issue at the protocol level. To get a better understanding of this, let’s look at the definition of the 201 status code from the HTTP standard:

The 201 (Created) status code indicates that the request has been fulfilled and has resulted in one or more new resources being created. The primary resource created by the request is identified by either a Location header field in the response or, if no Location field is received, by the effective request URI.

Since our response above is missing a Location header, what our server is really saying is that a new resource was created at /books. Clearly, that’s not the case. That’s our collection, which already existed. So, a more appropriate response would look as follows:

This says that in response to our request a new resource was created at http://example.com/books/9781449358068. We could have sent back the absolute URL, but the HTTP standard defines the Location header as a URI reference, and when it’s a relative reference, it’s resolved against the effective request URI: http://example.com/books.

We also could have included the representation in the response, but now that the client has the URL of the new resource, they don’t need the identifier, and since that’s all we added to the representation in the response, the client doesn’t need it and we save some network bandwidth in the process. Furthermore, we loosen the coupling by reducing the clients’ need to understand the server’s URLs.

201 without a Location

We saw that not including a Location is technically valid, but when would we want to do that? One reason would be to give clients control over the URI, but why would we want to do that? Perhaps we don’t want to generate a URL for each resource.

Going back to our book example, suppose we want to allow clients to create resources that represent wish lists of books found at /wish-lists/{id}, but instead of the server generating a value for id, we let the clients choose it.

You’re probably familiar with using PUT to update resources, but according to the HTTP standard (RFC 7231), a PUT request asks that “the target resource be created or replaced [emphasis mine]”. It goes on to say that “if the target resource does not have a current representation and the PUT successfully creates one, then the origin server MUST inform the user agent by sending a 201 (Created) response.” So, if a resource doesn’t exist at particular URL, a PUT can be used to create it (assuming the server allows it).

So, a request to create a wish list might look something like this:

Assuming the resource at /wish-lists/classics doesn’t have a representation (i.e., it doesn’t exist), the server creates it and informs the client:

With no Location header, this response says the resource was successfully created at the effective request URI: http://example.com/wish-lists/classics.

Additionally, our server could allow similar requests to update the resource after it’s created. For PUT requests, “if the target resource does have a current representation and that representation is successfully modified […], then the origin server MUST send either a 200 (OK) or a 204 (No Content) response” (RFC 7231).

Summary

When accepting POST to create and append an item to a collection, respond with a 201 and include a Location header. If you can’t include a Location header (or simply don’t want to), just use a 200 (OK). There’s no value in falsely reporting the creation of a resource that already existed.

Alternatively, use PUT to create your resources, in which case don’t worry about sending a Location header at all.

I’d love to hear your thoughts, comments, and feedback on this post, especially if you’ve seen something similar. You can reach out to me on Twitter @dillon_redding.

--

--

Dillon Redding
CodeX

Software Craftsman, API Enthusiast, Philomath