2

I started creating an application which uses strings as key, in order to have readably, non-guessable API values:

GET https://myApi.com/docs/obuxn6xhzg
GET https://myApi.com/docs/qxfj1g40xf
PUT https://myApi.com/docs/jgtw2vsqqh (--> to update the item, for example)

as opposed to having https://myApi.com/docs/1 .. https://myApi.com/docs/99

However, I'm now struggling to find a descent API endpoint to pass in actions. For example an archiveActive action could be represented by POST https://myApi.com/docs/archiveActive.

With this URL however, I'm starting to think there might be an ambiguity where the application might consider "archiveActive" as a document id.

So basically, I guess I'm asking if this is something I really should avoid doing, and if so, if there is an alternative way of approaching this when using string ids.

6
  • I don't quite get it. If a user can find all documents, why are the individual documents obscured? Or are those for different users? Commented Jan 9, 2020 at 15:25
  • Move the document identifiers down a level: /api/docs/obuxn6xhzg is a document, while /api/findAll is a verb. Commented Jan 9, 2020 at 15:26
  • 1
    In a normal REST api, rather than a POST /docs/findAll, you would use GET /docs/. Maybe you used FindAll as an example action, but then it is not a very good example. In fact, under REST rules, all actions should be indicated by the HTTP verb and the URL only indicates the (sub-)resource to take the action on. Commented Jan 9, 2020 at 15:31
  • Okay, agree, this was a bad example, perhaps consider /docs/archiveActive, which archives all active documents I have access to
    – Nsevens
    Commented Jan 9, 2020 at 15:42
  • 1
    Regarding your concern about archiveActive being confused for a document id, most frameworks evaluate explicit paths before they evaluate templated ones. If you've built your own framework, that's how you solve the problem. So there should be little reason for that naming conflict. Commented Jan 10, 2020 at 14:04

3 Answers 3

1

What you're describing would technically work and would not contradict any hard rules.

However there are some conventions. For example that the path structure reflects the object structure a bit. That means GET https://myApi.com/docs should normally list all documents, just like a directory.

Also, by convention URIs should be things (resources), not verbs, so findAll sounds off a bit.

Also, if you want to retrieve data from the server (read-only operation), those should be GET operations, not POST.

Again, these are just conventions, and you can safely ignore them if you have reason to do so. URIs can be basically anything you wish. They can contain IDs of any sort, numbers or strings, they can "overload" the ID namespace with special values (like findAll) or none of the above. It is up to the server.

5
  • findAll was indeed a bad example; consider for example a /docs/archiveActive action, which archives all active documents I have access to
    – Nsevens
    Commented Jan 9, 2020 at 15:43
  • 1
    @Nsevens I think what Robert is saying is that you the URI should be a noun, not a verb. The verbs are your operations (PUT, POST, GET, etc.) The simple solution is you add another level to your path e.g. POST '/docs/archive/active'. The framework should be able to distinguish between the two paths.
    – JimmyJames
    Commented Jan 9, 2020 at 15:51
  • @Nsevens JimmyJames is right, the URIs should be normally nouns (things). What exactly they are is irrelevant. Can be POST /docs/archiveOptions, /docs/archive, /docsarchive, /docs/archive/active. Again, it could be anything, there are no rules against any URIs. It may however indicate poor style and that some behind-the-scenes concepts may not be followed properly. Commented Jan 9, 2020 at 16:00
  • But in that case /docs/archive could indicate either the action to archive something, or a document with id archive? The chance of that being an actual id are quite low, but still...
    – Nsevens
    Commented Jan 9, 2020 at 16:03
  • 1
    @Nsevens Yes, if you would choose this URI, you would have to make sure the server doesn't assign archive as a document id. I would choose something outside of the /docs prefix frankly, but it is still quite possible and as I said, there are no hard rules against it. As the client should receive all URIs from the server it is irrelevant for the client, or in worst case it can check for it too. Commented Jan 9, 2020 at 17:15
0

I guess I'm asking if this is something I really should avoid doing

No, you don't need to avoid doing this -- a URI is an identifier; clients and intermediate components should not be trying to extract any semantic information from them (think "surrogate key" in a database).

Mark Seemann writes about avoiding hackable urls.

Pay attention to the replies, especially Dan Kubb's suggestion to use HMAC with a server side secret to distinguish server generated identifiers from others without sacrificing readability. It may be an easier fit for your use case than hashing/encrypting the entire target-uri.

There are also some edge cases you need to consider - like what you do with a URI template? Or (equivalently) a GET form in HTML?

That said, you might want to consider carefully whether opaque URI are really going to give you a competitive advantage in your domain? Is the time you are going to invest in bespoke request routing going to be paid back somewhere?

0

Several people have suggested that instead of having POST /docs/findAll to list all documents the URL schema should be GET /docs. While true, sometimes a GET request simply does not cut it due to its length limitations. In a case where you would like to have a very complex search engine on a certain entity type, e.g. the documents, accepting nested objects,... representing searching criteria, it's completely valid to have an endpoint for searching represented using POST HTTP method because it allows you to pass in large body (instead of passing it as a query parameter).

This problem aside, while following at least some good REST practices, you should never really encounter the issue you're worried about, that is unless you start generating identifier containing slashes (which I strongly recommend you not to do).

In your example, by following some basic REST principles:

  • creating a new document should be POST /api/docs,
  • getting a specific document should be GET /api/docs/(doc-id),
  • updating a specific document should be PUT /api/docs/(doc-id) or PATCH /api/docs/(doc-id) (depending on the use case).

As you can see, the endpoint with the format POST /api/docs/(something...) is still free and not conflicting with anything previously created. Therefore the mismatched resolution of endpoint you're worried about cannot happen.

In a scenario where you would e.g. allow adding attachments to documents, you would have a new endpoint, ideally looking like this: POST /api/docs/(doc-is)/attachments, so your POST /api/docs/findAll wouldn't be conflicting with this one either, because the schema differs. Unless, as I have mentioned, you actually allow generating identifiers with slashes in them and on the extremely rare occasion you would end up with an identifier looking for example like this: x6bFAK6/attachments. If you decided to read or update the document with such strange id, you could end up hitting the following endpoints:

  • GET /api/docs/x6bFAK6/attachments,
  • PUT /api/docs/x6bFAK6/attachments,

which any solid REST framework should refuse to process, because neither GET nor PUT method is acceptable by the POST /api/docs/(doc-id)/attachments endpoint, and a standalone POST /api/docs/(doc-id) shouldn't exist either (as mentioned earlier in this post).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.