485

We are launching a new REST API and I wanted some community input on best practices around how we should have input parameters formatted:

Right now, our API is very JSON-centric (only returns JSON). The debate of whether we want/need to return XML is a separate issue.

As our API output is JSON centric, we have been going down a path where our inputs are a bit JSON centric and I've been thinking that may be convenient for some but weird in general.

For example, to get a few product details where multiple products can be pulled at once we currently have:

http://our.api.com/Product?id=["101404","7267261"]

Should we simplify this as:

http://our.api.com/Product?id=101404,7267261

Or is having JSON input handy? More of a pain?

We may want to accept both styles but does that flexibility actually cause more confusion and head aches (maintainability, documentation, etc.)?

A more complex case is when we want to offer more complex inputs. For example, if we want to allow multiple filters on search:

http://our.api.com/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}

We don't necessarily want to put the filter types (e.g. productType and color) as request names like this:

http://our.api.com/Search?term=pumas&productType=["Clothing","Bags"]&color=["Black","Red"]

Because we wanted to group all filter input together.

In the end, does this really matter? It may be likely that there are so many JSON utils out there that the input type just doesn't matter that much.

I know our JavaScript clients making AJAX calls to the API may appreciate the JSON inputs to make their life easier.

Henke
  • 4,445
  • 3
  • 31
  • 44
whatupwilly
  • 4,997
  • 3
  • 16
  • 5

6 Answers6

394

A Step Back

First and foremost, REST describes a URI as a universally unique ID. Far too many people get caught up on the structure of URIs and which URIs are more "restful" than others. This argument is as ludicrous as saying naming someone "Bob" is better than naming him "Joe" – both names get the job of "identifying a person" done. A URI is nothing more than a universally unique name.

So in REST's eyes arguing about whether ?id=["101404","7267261"] is more restful than ?id=101404,7267261 or \Product\101404,7267261 is somewhat futile.

Now, having said that, many times how URIs are constructed can usually serve as a good indicator for other issues in a RESTful service. There are a couple of red flags in your URIs and question in general.

Suggestions

  1. Multiple URIs for the same resource and Content-Location

    We may want to accept both styles but does that flexibility actually cause more confusion and head aches (maintainability, documentation, etc.)?

    URIs identify resources. Each resource should have one canonical URI. This does not mean that you can't have two URIs point to the same resource but there are well defined ways to go about doing it. If you do decide to use both the JSON and list based formats (or any other format) you need to decide which of these formats is the main canonical URI. All responses to other URIs that point to the same "resource" should include the Content-Location header.

    Sticking with the name analogy, having multiple URIs is like having nicknames for people. It is perfectly acceptable and often times quite handy, however if I'm using a nickname I still probably want to know their full name – the "official" way to refer to that person. This way when someone mentions someone by their full name, "Nichloas Telsa", I know they are talking about the same person I refer to as "Nick".

  2. "Search" in your URI

    A more complex case is when we want to offer more complex inputs. For example, if we want to allow multiple filters on search...

    A general rule of thumb of mine is, if your URI contains a verb, it may be an indication that something is off. URI's identify a resource, however they should not indicate what we're doing to that resource. That's the job of HTTP or in restful terms, our "uniform interface".

    To beat the name analogy dead, using a verb in a URI is like changing someone's name when you want to interact with them. If I'm interacting with Bob, Bob's name doesn't become "BobHi" when I want to say Hi to him. Similarly, when we want to "search" Products, our URI structure shouldn't change from "/Product/..." to "/Search/...".

Answering Your Initial Question

  1. Regarding ["101404","7267261"] vs 101404,7267261: My suggestion here is to avoid the JSON syntax for simplicity's sake (i.e. don't require your users do URL encoding when you don't really have to). It will make your API a tad more usable. Better yet, as others have recommended, go with the standard application/x-www-form-urlencoded format as it will probably be most familiar to your end users (e.g. ?id[]=101404&id[]=7267261). It may not be "pretty", but Pretty URIs does not necessary mean Usable URIs. However, to reiterate my initial point though, ultimately when speaking about REST, it doesn't matter. Don't dwell too heavily on it.

  2. Your complex search URI example can be solved in very much the same way as your product example. I would recommend going the application/x-www-form-urlencoded format again as it is already a standard that many are familiar with. Also, I would recommend merging the two.

Your URI...

/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}    

Your URI after being URI encoded...

/Search?term=pumas&filters=%7B%22productType%22%3A%5B%22Clothing%22%2C%22Bags%22%5D%2C%22color%22%3A%5B%22Black%22%2C%22Red%22%5D%7D

Can be transformed to...

/Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red

Aside from avoiding the requirement of URL encoding and making things look a bit more standard, it now homogenizes the API a bit. The user knows that if they want to retrieve a Product or List of Products (both are considered a single "resource" in RESTful terms), they are interested in /Product/... URIs.

nategood
  • 11,807
  • 4
  • 36
  • 44
  • 91
    Wanted to follow up and note that the `[]` syntax isn't always supported (and despite it being common, may even violate the URI spec). Some HTTP servers and programming languages will prefer just repeating the name (e.g. `productType=value1&productType=value2`). – nategood Aug 26 '13 at 19:09
  • 1
    The initial question with this query.. "/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}" translates to... (productType==clothing || productType==bags) && (color==black || color==red) BUT YOUR SOLUTION: /Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red seems to translate to... Either (productType==clothing || productType==bags || color==black || color==red) or Either (productType==clothing && productType==bags && color==black && color==red) Which seems to be a bit different to me. Or did I misunderstood? – Thomas Cheng Sep 04 '15 at 06:55
  • @ThomasCheng He is using a JSON object containing multiple lists. It could be used in multiple ways, but it would usually then be parsed into a matching object on the server. – MondayPaper Mar 10 '16 at 15:38
  • 2
    What about inputs in the post request ? I wanted to know if we are updating a resource, then is it a bad practice to send the query/filter and data in the body in a standard format. for eg. if i want to change the data related to user using the api `/user/` and in the body, I'll send `{ q:{}, d: {} }` with `q` as query by with the user will be queried in the DB and `d` as modified data. – molecule Jan 24 '17 at 11:19
  • 1
    What do you do when the list may be very large? The URI is limited in length depending on the browser. I have typically switched to a post request and sent the list in the body. Any suggestions there? – Troy Cosentino Mar 31 '17 at 19:51
  • 5
    It would have to be VERY large (see http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers), but yes, in the most extreme cases, you may need to use the body of the request. POSTing queries for data retrieval is one of those things that RESTafarians love to debate. – nategood Apr 12 '17 at 20:28
  • @nategood , One doubt, if the query string parameter is greater than 2000 character , isn't better to change the `API` to `POST` verb to pass via request body ? Do you know any best practice on number of characters in `API` query string ? – Shaiju T Jan 22 '19 at 10:30
  • Great answer, but as you noted the characters `[` and `]` violate the URI specification (cf. [RFC 3986, Appendix A](https://www.rfc-editor.org/rfc/rfc3986#appendix-A)). – Géry Ogam Oct 05 '22 at 18:04
310

The standard way to pass a list of values as URL parameters is to repeat them:

http://our.api.com/Product?id=101404&id=7267261

Most server code will interpret this as a list of values, although many have single value simplifications so you may have to go looking.

Delimited values are also okay.

If you are needing to send JSON to the server, I don't like seeing it in in the URL (which is a different format). In particular, URLs have a size limitation (in practice if not in theory).

The way I have seen some do a complicated query RESTfully is in two steps:

  1. POST your query requirements, receiving back an ID (essentially creating a search criteria resource)
  2. GET the search, referencing the above ID
  3. optionally DELETE the query requirements if needed, but note that they requirements are available for reuse.
Kathy Van Stone
  • 25,531
  • 3
  • 32
  • 40
  • 9
    Thanks Kathy. I think I'm with you and don't really like seeing JSON in the URL as well. However, I'm not a fan of doing a post for a search which is an inherent GET operation. Can you point to an example of this? – whatupwilly Apr 08 '10 at 17:39
  • 1
    If the queries can work as simple parameters, do that. The source was from the rest-discuss mailing list: http://tech.groups.yahoo.com/group/rest-discuss/message/11578 – Kathy Van Stone Apr 08 '10 at 18:36
  • 2
    If you just want to show two resources, James Westgate's answer is more typical – Kathy Van Stone Apr 08 '10 at 18:38
  • This is the correct answer. In the near future I'm sure we will see some filter=id in(a,b,c,...) supported by OData or something like that. – Bart Calixto Jan 10 '14 at 02:16
  • This is the way Akka HTTP works – Joan Feb 23 '17 at 10:21
  • After trying all methods. This is the only way Go(golang) likes the slices aka lists or arrays. – Edwinner Mar 21 '17 at 20:33
  • Side question, let's assume I am doing GET for 2 ids, like in this answer. What should happen if one of the ids is invalid? Should I return 400 or 200 with data available for correct id? What is best practice here? or just depends on a usecase? – Lukasz Dec 08 '20 at 14:55
  • @Lukasz If it is like a search I would return a 200 with the data for the found id. If the result is a key/value map by id, I might return an error result for the id. Note that for a single id I would probably return 404 rather than 400 (since you are looking for something that is not there). – Kathy Van Stone Jan 13 '21 at 18:55
25

First:

I think you can do it 2 ways

http://our.api.com/Product/<id> : if you just want one record

http://our.api.com/Product : if you want all records

http://our.api.com/Product/<id1>,<id2> :as James suggested can be an option since what comes after the Product tag is a parameter

Or the one I like most is:

You can use the the Hypermedia as the engine of application state (HATEOAS) property of a RestFul WS and do a call http://our.api.com/Product that should return the equivalent urls of http://our.api.com/Product/<id> and call them after this.

Second

When you have to do queries on the url calls. I would suggest using HATEOAS again.

1) Do a get call to http://our.api.com/term/pumas/productType/clothing/color/black

2) Do a get call to http://our.api.com/term/pumas/productType/clothing,bags/color/black,red

3) (Using HATEOAS) Do a get call to `http://our.api.com/term/pumas/productType/ -> receive the urls all clothing possible urls -> call the ones you want (clothing and bags) -> receive the possible color urls -> call the ones you want

Diego Dias
  • 21,634
  • 6
  • 33
  • 36
  • 1
    I was put in a similar situation few days ago, Having to tune a (HATEOAS) rest api to get a filtered (large) list of objects and I just chose your second solution. Isn't recalling the api over and over for each one a bit overkill? – Samson Mar 04 '13 at 19:06
  • It really depends on your system.... If it is a simple one with few "options" it should probably be overkill. However, if you have some really large lists it can become really troublesome to do it all in one big call, besides if your API is public it can become complicated to the users (if it is a private one it should be easier... just teach the users you know). As an alternative, you could implement both style, the HATEOAS and a "non-restful array" call for advanced users – Diego Dias Mar 05 '13 at 19:49
  • I am building a restful api webservice in rails and I need to follow the same url structure as above(http://our.api.com/term/pumas/productType/clothing/color/black). But I am not sure how to configure the routes accordingly. – rubyist Apr 17 '14 at 15:13
  • are term, productType and color your controllers? If so, you just need to do: resources :term do resources :productType do resources :color end end – Diego Dias Apr 17 '14 at 17:34
  • productType and color are the params. So the params of productType is clothing and params of clothing is black – rubyist Apr 17 '14 at 19:30
  • In my opinion, the second example breaks URIs. There is nothing returned at http://our.api.com/term or http://our.api.com/term/pumas/productType or http://our.api.com/term/pumas/productType/clothing/color. – Alex Oct 12 '15 at 17:31
17

You might want to check out RFC 6570. This URI Template spec shows many examples of how urls can contain parameters.

ᄂ ᄀ
  • 5,669
  • 6
  • 43
  • 57
Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • 1
    Section 3.2.8 seems to be what is applicable. Although it's worth noting that this just a proposed standard and doesn't seem to have moved beyond that point. – Mike Post Sep 11 '15 at 16:37
  • 3
    @MikePost Now that IETF has moved to a two step maturity process for "standards track" documents, I expect 6570 to stay like this for a few more years before moving to a "Internet Standard". https://tools.ietf.org/html/rfc6410 The spec is extremely stable, has many implementations and is widely used. – Darrel Miller Sep 11 '15 at 18:18
  • Ah I was not aware of that change. (Or, TIL IETF is now more reasonable.) Thanks! – Mike Post Sep 14 '15 at 16:01
9

First case:

A normal product lookup would look like this

http://our.api.com/product/1

So Im thinking that best practice would be for you to do this

http://our.api.com/Product/101404,7267261

Second Case

Search with querystring parameters - fine like this. I would be tempted to combine terms with AND and OR instead of using [].

PS This can be subjective, so do what you feel comfortable with.

The reason for putting the data in the url is so the link can pasted on a site/ shared between users. If this isnt an issue, by all means use a JSON/ POST instead.

EDIT: On reflection I think this approach suits an entity with a compound key, but not a query for multiple entities.

James Westgate
  • 11,306
  • 8
  • 61
  • 68
  • 3
    Of course, in both examples, the trailing `/` shouldn't be there since the URI addresses a resource, not a collection. – Lawrence Dol May 01 '15 at 00:01
  • 2
    I always though the HTTP verbs, in a REST usage was to do specific actions, and this was the guidance line : GET : retrieve/read object, POST create object, PUT update existing object and DELETE delete an object. So I wouldn't use a POST to retrieve something. If I want a list of object in particular (filter), I'd do a GET with a list in url parameters (separated by a comma seems good) – Alex Mar 09 '16 at 16:17
2

I will side with nategood's answer as it is complete and it seemed to have please your needs. Though, I would like to add a comment on identifying multiple (1 or more) resource that way:

http://our.api.com/Product/101404,7267261

In doing so, you:

Complexify the clients by forcing them to interpret your response as an array, which to me is counter intuitive if I make the following request: http://our.api.com/Product/101404

Create redundant APIs with one API for getting all products and the one above for getting 1 or many. Since you shouldn't show more than 1 page of details to a user for the sake of UX, I believe having more than 1 ID would be useless and purely used for filtering the products.

It might not be that problematic, but you will either have to handle this yourself server side by returning a single entity (by verifying if your response contains one or more) or let clients manage it.

Example

I want to order a book from Amazing. I know exactly which book it is and I see it in the listing when navigating for Horror books:

  1. 10 000 amazing lines, 0 amazing test
  2. The return of the amazing monster
  3. Let's duplicate amazing code
  4. The amazing beginning of the end

After selecting the second book, I am redirected to a page detailing the book part of a list:

--------------------------------------------
Book #1
--------------------------------------------
    Title: The return of the amazing monster
    Summary:
    Pages:
    Publisher:
--------------------------------------------

Or in a page giving me the full details of that book only?

---------------------------------
The return of the amazing monster
---------------------------------
Summary:
Pages:
Publisher:
---------------------------------

My Opinion

I would suggest using the ID in the path variable when unicity is guarantied when getting this resource's details. For example, the APIs below suggest multiple ways to get the details for a specific resource (assuming a product has a unique ID and a spec for that product has a unique name and you can navigate top down):

/products/{id}
/products/{id}/specs/{name}

The moment you need more than 1 resource, I would suggest filtering from a larger collection. For the same example:

/products?ids=

Of course, this is my opinion as it is not imposed.

gmolaire
  • 1,091
  • 10
  • 19