There are two JSON API styles:
- The ID Style
- The URL Style
The ID style is the easiest to get started with, but requires that your clients be able to guess the URLs for related documents. It also locks your API into a particular URL structure, which may become a problem as your API grows.
The URL style requires less guessing on the client side, and makes clients more resilient to API changes, but is trickier to use with relationships and compound documents.
In general, you should be able to start with an ID-based JSON API and upgrade to a URL-based API, if you want.
In this specification, the term "document" refers to a single object with a set of attributes and relationships.
A JSON response may include multiple documents, as described below.
The top-level of a JSON API document MAY have the following keys:
meta
: meta-information about a resource, such as pagination- Other resource names (
posts
,comments
,people
, etc.)
If the value of a resource key is a JSON object, the value represents a single document.
{
"posts": {
// an individual post document
}
}
The document MUST contain an id
key.
If the value of a resource key is a JSON array, the value represents a list of documents.
{
"posts": [{
// an individual post document
}, {
// an individual post document
}]
}
Each document in the list MUST contain an id
key.
The "id"
key in a document represents a unique identifier for the document, scoped to the document's type. The type scope is implicit, and hardcoded into clients of the API.
Other than the "rels"
and "id"
keys, every key in a document represents an attribute. An attribute's value may be any JSON value.
{
"posts": {
"id": 1,
"title": "Rails is Omakase"
}
}
The value of the "rels"
key is a JSON object that represents related documents.
{
"posts": {
"id": 1,
"title": "Rails is Omakase",
"rels": {
"author": 9,
"comments": [ 5, 12, 17, 20 ]
}
}
}
A to-many relationship is represented as a JSON array of IDs.
{
"posts": {
"id": 1,
"title": "Rails is Omakase",
"rels": {
"comments": [ 5, 12, 17, 20 ]
}
}
}
An API that provides a to-many relationship as an array of IDs MUST respond to a GET
request with a list of the specified documents with a URL formed by joining:
- A base URL that represents the type of the related resource (this must be hardcoded in the client)
?ids=
- A comma-separated list of the specified IDs
In the above example, a GET
request to /comments?ids=5,12,17,20
returns a document containing the four specified comments.
A to-one relationship is represented as a single string or number value.
{
"posts": {
"id": 1,
"title": "Rails is Omakase",
"rels": {
"author": 17
}
}
}
An API that provides a to-one relationship as an ID MUST respond to a GET
request with the specified document with a URL formed by joining:
- A base URL that represents the type of the related resource (this must be hardcoded in the client)
/
- The specified ID
In the above example, a GET
request to /people/17
returns a document containing the specified author.
To save HTTP requests, it may be convenient to send related documents along with the requested documents.
{
"posts": {
"id": 1,
"title": "Rails is Omakase",
"rels": {
"author": 9
}
},
"people": [{
"id": 9,
"name": "@d2h"
}]
}
The related documents are provided as an additional top-level document or document list whose key is a name that represents the document type.
The linkage between the key under "rels"
and the top-level keys is hardcoded into the client.
In the above description of ID-based JSON, there were several places where information about the location of related resources needed to be hardcoded into the client.
The goal of the URL-Based JSON API is to eliminate the need for those specific instances of hardcoded information.
The top-level of a JSON API document MAY have the following keys:
meta
: meta-information about a resource, such as paginationrels
: in compound resources, information about relationships that would otherwise need to be repeated- Other resource names (
posts
,comments
,people
, etc.)
If the value of a resource key is a JSON object, the value represents a single document.
{
"posts": {
// an individual post document
}
}
The document MAY contain an id
key.
If the value of a resource key is a JSON array, the value represents a list of documents.
{
"posts": [{
// an individual post document
}, {
// an individual post document
}]
}
Each document in the list MAY contain an id
key.
The "id"
key in a document represents a unique identifier for the document, scoped to the document's type. It can be used with URL templates to fetch related records, as described below.
Other than the "rels"
and "id"
keys, every key in a document represents an attribute. An attribute's value may be any JSON value.
{
"posts": {
"id": 1,
"title": "Rails is Omakase"
}
}
The value of the "rels"
key is a JSON object that represents related documents.
{
"posts": {
"id": 1,
"title": "Rails is Omakase",
"rels": {
"author": "http://example.com/people/1",
"comments": "http://example.com/comments/5,12,17,20"
}
}
}
A to-many relationship is a string value that represents a URL.
{
"posts": {
"id": 1,
"title": "Rails is Omakase",
"rels": {
"comments": "http://example.com/posts/1/comments"
}
}
}
An API that provides a to-many relationship as a URL MUST respond to a GET
request with a list of the specified documents with the specified URL.
In the above example, a GET
request to /posts/1/comments
returns a document containing the four specified comments.
A to-one relationship is represented as a string value that represents a URL.
{
"posts": {
"id": 1,
"title": "Rails is Omakase",
"rels": {
"author": "http://example.com/people/17"
}
}
}
An API that provides a to-one relationship as an ID MUST respond to a GET
request with the specified document with the specified URL.
In the above example, a GET
request to /people/17
returns a document containing the specified author.
When returning a list of documents from a response, a top-level "rels"
object can specify a URL template that should be used for all documents.
Example:
{
"rels": {
"posts.comments": "http://example.com/posts/{post.id}/comments"
},
"posts": [{
"id": 1,
"title": "Rails is Omakase"
}, {
"id": 2,
"title": "The Parley Letter"
}]
}
In this example, fetching /posts/1/comments
will fetch the comments for "Rails is Omakase"
and fetching /posts/2/comments
will fetch the comments for "The Parley Letter"
.
{
"rels": {
"posts.comments": "http://example.com/comments/{posts.comments}"
},
"posts": {
"id": 1,
"title": "Rails is Omakase",
"rels": {
"comments": [ 1, 2, 3, 4 ]
}
}
}
In this example, the posts.comments
variable is expanded by "exploding" the array specified in the "rels"
section of each post. The URL template specification specifies that the default explosion is to join the array members by a comma, so in this example, fetching /comments/1,2,3,4
will return a list of all comments.
This example shows how you can start with a list of IDs and then upgrade to specifying a different URL pattern than the default.
The top-level "rels"
key has the following behavior:
- Each key is a dot-separated path that points at a repeated relationship. For example
"posts.comments"
points at the"comments"
relationship in each repeated document under"posts"
. - The value of each key is interpreted as a URL template.
- For each document that the path points to, act as if it specified a relationship formed by expanding the URL template with the non-URL value actually specified.
Here is another example that uses a has-one relationship:
{
"rels": {
"posts.author": "http://example.com/people/{posts.author}"
},
"posts": [{
"id": 1,
"title": "Rails is Omakase",
"rels": {
"author": 12
}
}, {
"id": 2,
"title": "The Parley Letter",
"rels": {
"author": 12
}
}, {
"id": 3,
"title": "Dependency Injection is Not a Virtue",
"rels": {
"author": 12
}
}]
}
In this example, the author URL for all three posts is /people/12
.
Top-level URL templates allow you to specify relationships as IDs, but without requiring that clients hard-code information about how to form the URLs.
To save HTTP requests, it may be convenient to send related documents along with the requested documents.
In this case, a bit of extra metadata for each relationship can link together the documents.
{
"rels": {
"posts.author": {
"url": "http://example.com/people/{post.author}",
"type": "people"
},
"posts.comments": {
"url": "http://example.com/comments/{post.comments}",
"type": "comments"
}
}
"posts": [{
"id": 1,
"title": "Rails is Omakase",
"rels": {
"author": 9,
"comments": [ 1, 2, 3 ]
}, {
"id": 2,
"title": "The Parley Letter",
"rels": {
"author": 9,
"comments": [ 4, 5 ]
}, {
"id": 1,
"title": "Dependency Injection is Not a Virtue",
"rels": {
"author": 9,
"comments": [ 6 ]
}
}],
"people": [{
"id": 9,
"name": "@d2h"
}],
"comments": [{
"id": 1,
"body": "Mmmmmakase"
}, {
"id": 2,
"body": "I prefer unagi"
}, {
"id": 3,
"body": "What's Omakase?"
}, {
"id": 4,
"body": "Parley is a discussion, especially one between enemies"
}, {
"id": 5,
"body": "The parsley letter"
}, {
"id": 6,
"body": "Dependency Injection is Not a Vice"
}]
}
The benefit of this approach is that when the same document is referenced multiple times (in this example, the author of the three posts), it only needs to be presented a single time in the document.
By always combining documents in this way, a client can consistently extract and wire up references.
JSON API documents MAY specify the URL for a document in a compound response by specifying a "url"
key:
{
// ...
"comments": [{
"url": "http://example.com/comments/1",
"id": 1,
"body": "Mmmmmakase"
}, {
"url": "http://example.com/comments/2",
"id": 2,
"body": "I prefer unagi"
}, {
"url": "http://example.com/comments/3",
"id": 3,
"body": "What's Omakase?"
}, {
"url": "/comments/1",
"id": 4,
"body": "Parley is a discussion, especially one between enemies"
}, {
"url": "/comments/1",
"id": 5,
"body": "The parsley letter"
}, {
"url": "/comments/1",
"id": 6,
"body": "Dependency Injection is Not a Vice"
}]
}
This makes sense to me :)