303 response on POSTing resource without conflicts

I’m making service-wide address dictionary.
Users will be able to add addresses in it by making POST /addresses request with only text attribute in it. This endpoint returns 201 OK status code if address was added.
If address with same text field already exists in database I want to return it’s identifier instead of creating duplicated resource. This required because all addresses are going through moderation process and I cannot allow to find this address via GET /addresses collection until it’s being moderated.
201 code is not appropriate in this case because record wasn’t created.

There are 2 possible solutions which I see at this moment:
303 code fits well but will add +1 GET /addresses/{id} to fetch created resource (because response will be without body).
200 code is good, but its breaking REST rules where POST response should not be idempotent and must return created resources with unique IDs all the time even if attributes are the same.

What would you prefer? Break REST rules and use 200 OK or strictly follow it with returning 303 See Other and make additional GET request?

Note: 409 Conflict code isn’t reasonable because there is no conflicts with IDs or attributes here.

I don’t understand your note at the bottom. A 409 sounds like the appropriate in-band solution to me, because you’re creating an immutable dictionary of addresses. I’m assuming by your description the address resource isn’t ‘owned’ by any other resource, and instead an update to the address fields will simply point to a different address resource.

However, if you have a problem with this or would like callers to know an asynchronous process has been started, you could always return 202 Accepted and a Location header to the Address resource.

Nothing about the semantics of POST get in the way of this process. The first and the 50th call to post “xyz” will return 202, and a caller will inspect the resource itself to determine its current status. This simply would imply to a caller some asynchronous process has been kicked off and the resource will be available at the location you return.

Thank you for joining the discussion, @michaelhibay!

You are right. Address is independent resource which not owned by any other resources, only related to them.
If user updates his address, he need to find address resource in dictionary via GET /addresses?filter[text]={address-string} and then attach it to user’s resource addresses relationship.

But there are cases if address not found in dictionary and client dont’t know why:

  • address not exist in database at all (best case, because we just return 201);
  • address not moderated yet or declined by moderators (we cannot display them in dictionary because its bad or possibly bad content and will overflow dictionary responses with jibberish resources a lot).

If user try to add already moderated content we wouldn’t create it once again - and nobody will see it in public dictionary, but it will be able to get this resource by id and read a reason of moderation for example.
We decided to let users to blindly to add address resource using POST /addresses with text attribute because they don’t know know the reason why it’s not in dictionary until they will try to add prohibited content themselves.

Accordingly 409 code I’ve rejected to use it because in specs its used for conflict client generated ids and wrong resource types. 4xx responses should not be represented as resource, but I need to let client get resource to understand the reason why it’s prohibited or understand that this resource could be attached to another resource, but wouldn’t be visible until moderation process ends.

With the additional asynchronous process information, I would agree 409 is less appropriate.

Can you tell me if you have considered using 204 to show no address meeting the filters or simply 200 OK with empty representation? If you don’t intend to have the status be part of the /address resource representation, you could always return 410 for items which have failed moderation, and 404 for items pending. Remember you’ll be providing a dereferenceable URL to the client in the form of a link in the Location header or otherwise, so

To be honest though, this sounds like you’re thinking your way around using JSON:API errors or representation semantics to convey this information. Having a status of pending, rejected, accepted on the resource would seem to solve all of your problems without too much bespoke interpretations of the HTTP status codes.

Simply tagging this information with a status, and only allowing the accepted contents to be displayed externally would go a long way towards solving your problems within the stated constraints without adding consumer complexity.

If user will try to get moderated (banned) resource by it’s ID GET /addresses/{id} it will return 200 OK and will contain a status of the moderation. Moderated and not yet moderated resources will be excluded from the collection only.

Hmmm… I have an idea to not display resources in GET /addressed conllection with pending and moderated statuses until it’s 100% has same text string.
GET /addresses?filter[text]=Ba wouldn’t find banned resource with text Bad, but GET /addresses?filter[text]=Bad will display it.

This increments complexity of the database queries, but simplifies client implementation.

And in this case POST /addresses with attribute text: "Bad" will raise a 409 Conflict then.

@michaelhibay thank you for helping me move to the right direction.

My pleasure, glad I was able to help.

Generally, I would suggest unless you have a really good reason to compromise, you should always lean towards the simple interface.