JSON API best practices

360 views
Skip to first unread message

Steve H

unread,
May 31, 2012, 5:13:03 PM5/31/12
to rails-...@googlegroups.com
Do you have any preferences on good or bad responses from APIs? Or links to discussions on this subject.

I'm in the beginning stages of building v2 of an API with feedback from what we've learned doesn't work from v1, and want to pick up any other thoughts of what might turn out to be a mistake, or things you wish you had added or removed from your own APIs after you've built them.

Considerations:

* A common format for error messages
* Return multiple groups of results in the same response
* Return metadata about the result set along with the result (e.g. totals)

I'm currently leaning towards something like:

{
  "status":"ok",
  "latest_posts":{
    "total":42,
    "page":1,
    "per_page":10,
    "results":[
      {
        "type":"post",
        "id":123,
        ...
      }
    ]
  },
  "popular_posts":{
    ...
  }
}

{
  "status":"error",
  "message":"..."
}


Julio Cesar Ody

unread,
May 31, 2012, 5:19:57 PM5/31/12
to rails-...@googlegroups.com
In the absence of a blog post, a few comments:

"status":"ok"

No need for that. That's what HTTP status codes are for.

"page":1, "per_page"

`per_page` should be up to the client. Your API should support
pagination though for retrieving collections, and in which case, it
should accept a range value, or two values, one for range beginning
and range end. The gist here being that some clients might find it
suitable to fetch more, others less, and your API should simply let
those clients decide that. Probably up to a certain number of posts,
of course.

`total` should also be extrapolated from the array length once parsed.
The exception here being if you'd like to supply a separate "metadata"
request for collections which simply returns that kind of info but
omitting the collection itself. That can be handy for some UI cases
(e.g.: showing upfront how many items a collection has before actually
fetching).

That's all from me for now :)
> --
> You received this message because you are subscribed to the Google Groups
> "Ruby or Rails Oceania" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/rails-oceania/-/tVcxIVy7Q_kJ.
> To post to this group, send email to rails-...@googlegroups.com.
> To unsubscribe from this group, send email to
> rails-oceani...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/rails-oceania?hl=en.

Steve Hoeksema

unread,
May 31, 2012, 5:24:20 PM5/31/12
to rails-...@googlegroups.com
Thanks for your comments.

"per_page" and "status" are indeed probably redundant as they are respectively provided by the client or can be inferred by HTTP status.

I think total is important though, assuming you aren't retrieving the entire set in one response.
Fetching the collection metadata separately is probably more semantically correct, but this API is for a mobile app so getting everything in as few requests as possible is a high priority.

Ivan Vanderbyl

unread,
May 31, 2012, 6:56:04 PM5/31/12
to rails-...@googlegroups.com
Hi Steve,

I found the best way to figure out how you need to structure your payload by working back from what will be consuming it. I recently built the API for OrionVM and we decided that because we would later be consuming it from a javascript frontend built in something like Ember.js, we should make it as easy to consume as possible with that in mind. As it turned out this is also roughly the same structure expected by most clients e.g. ActiveResource, RestClient, and Ember.js Datastore.

Here's a few things we did:

- Only send the object/collection in the body
- Send metadata as X- headers
- Use HTTP status codes everywhere to indicate status
- Use root element without object root (see below)

For example, collection response:

Date: Thu, 31 May 2012 22:32:15 GMT
Content-Type: application/json
Status: 200 OK
Content-Length: 154
X-Pagination-Total-Items: 2
X-Pagination-Pages: 1

{
  "posts": [
    {
      "id": 123,
    },
    {
      "id": 124,
    }
  ]
}

Error response:

HTTP/1.1 422 Unprocessable Entity

 {
   "message": "Validation Failed",
   "errors": [
     {
       "resource": "Post",
       "field": "title",
       "message": "cannot be blank"
     }
   ]
 }

Notice that the collection response is always an object, with a root element of 'posts' and no object root element on the individual items as the type is denoted by the root element.

The reason we moved all the pagination metadata to the header was to allow HEAD requests over HTTP which return only the headers, useful for extremely fast lookups just for counts etc.

As for as embedding the popular_posts collection, IMO this should just be another resource endpoint you hit. If done right the API response should be extremely light fast, and if needed implement a rate limit — see Github V3 API.

— Ivan

Anthony Richardson

unread,
May 31, 2012, 6:58:51 PM5/31/12
to rails-...@googlegroups.com
> "per_page" and "status" are indeed probably redundant as they are
> respectively provided by the client or can be inferred by HTTP status.
>

We still return per_page in our API calls as we have limits on what
the client can request. So although a client my request a page size of
200 for load management and performance reasons we may only accept
values of 50 (for example).

Julio Cesar Ody

unread,
May 31, 2012, 7:07:54 PM5/31/12
to rails-...@googlegroups.com
> We still return per_page in our API calls as we have limits on what
> the client can request. So although a client my request a page size of
> 200 for load management and performance reasons we may only accept
> values of 50 (for example).

I quote myself:

> and in which case, it should accept a range value, or two values, one for range beginning and range end. The
> gist here being that some clients might find it suitable to fetch more, others less, and your API should simply
> let those clients decide that. Probably up to a certain number of posts, of course.

As for what Ivan suggested, working backwards from what you'll need
works, yes, and probably if said API will be consumed first and
foremost by JS, then go for that and make changes as you need them
should mobiles or whatever come next. I assume you have a budget/time,
so that helps that.

Oh and about "semantics" in APIs, the fact is an array is a structure
where the length equals the number of entities it carries. So turning
a collection into an object just so it can carry metadata which can
obviously be extracted from this characteristic of the collection
doesn't make anything more semantic, but just repetitive.

Samuel Richardson

unread,
May 31, 2012, 7:13:18 PM5/31/12
to rails-...@googlegroups.com
Theirs been a few attempts at creating a standardised format, one is:


with a gem available:


Implemented on a recent project here and it seems to be quite flexible.

Samuel Richardson
www.richardson.co.nz | 0405 472 748


--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.

Anthony Richardson

unread,
May 31, 2012, 7:48:14 PM5/31/12
to rails-...@googlegroups.com
Hi Julio,

All I meant to convey was it can be handy telling the client what you
have done in case it was different to what they asked.

Cheers,

Anthony
> --
> You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.

Iain Beeston

unread,
May 31, 2012, 7:31:40 PM5/31/12
to rails-...@googlegroups.com
I totally agree with ivan and julio. Great advice.


On a related note, one idea that I've found helpful (if you want to provide a public api) is to document how your api works through cucumber features. The idea being that consumers of your API can be sure that your API does what it says it will, and saves you having to write documentation *and* maintain tests (I got that idea from Anthony Eden of DNSimple). Think of your third party developers as stakeholders I guess.


Iain
Reply all
Reply to author
Forward
0 new messages