REST (Representational State Transfer) provides a foundational architectural style for building web services. However, it leaves many aspects of request and response formatting undefined. This ambiguity can lead to inconsistencies, increased development overhead, and a steeper learning curve for API consumers. Enter JSON:API, a specification that provides a standardized, convention-based approach to building APIs in JSON.
This comprehensive guide offers a deep dive into the JSON:API specification, exploring its core concepts, structure, and powerful features. We will dissect its mechanisms for fetching, creating, updating, and deleting resources, managing relationships, handling errors, and optimizing data transfer, equipping you with the knowledge to design and consume robust and efficient APIs.
Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?
Apidog delivers all your demans, and replaces Postman at a much more affordable price!
Why JSON:API? Explained:
Before delving into the technical intricacies, it's crucial to understand the problems JSON:API aims to solve. Without a shared convention, API developers often spend considerable time debating:
- Payload structure: How should resources and their attributes be represented?
- Relationship representation: How should links between different resources be conveyed?
- Data fetching strategies: How can clients request specific fields, include related resources, sort, paginate, and filter data?
- Error reporting: What format should error messages follow?
JSON:API addresses these by defining a clear, consistent format for requests and responses. This standardization offers several key benefits:
- Reduced Bikeshedding: By providing answers to common design questions, JSON:API allows teams to focus on the core business logic of their applications rather than debating API design minutiae.
- Improved Productivity: Standardized formats mean less custom code for both API producers and consumers. Client libraries can be developed to handle much of the boilerplate for interacting with any JSON:API compliant service.
- Enhanced Discoverability and Usability: Consistent link structures and clear delineation of resources and relationships make APIs easier to understand and navigate.
- Optimized Data Transfer: Features like sparse fieldsets and compound documents allow clients to request only the data they need, minimizing payload sizes and reducing the number of HTTP requests.
- Easier Caching: The specification promotes the use of standard HTTP caching mechanisms.
- Language Agnostic: Being JSON-based, it's inherently language-agnostic, facilitating broad adoption across diverse technology stacks.
Core Concepts: The Building Blocks of a JSON:API Document
At its heart, JSON:API revolves around the concept of resources. A resource is an individual record of a particular type, such as an "article," a "user," or a "product." Every JSON:API document, whether a request or a response, adheres to a specific structure.
The Document Structure: Top-Level Members
A JSON:API document is a JSON object that must contain at least one of the following top-level members:
data
: The document's "primary data." This can be:- A single resource object (e.g., when fetching a specific article).
- An array of resource objects (e.g., when fetching a collection of articles).
- A single resource identifier object (for representing a to-one relationship).
- An array of resource identifier objects (for representing a to-many relationship).
null
(e.g., when a to-one relationship is empty, or a request for a single resource that doesn't exist).- An empty array
[]
(e.g., when a to-many relationship is empty, or a request for a collection yields no results). errors
: An array of error objects providing details about processing errors. Thedata
member must not be present iferrors
is present.meta
: A meta object containing non-standard meta-information that doesn't fit within the rest of the specification.
Additionally, a document may contain these top-level members:
jsonapi
: An object describing the server's implementation. It can includeversion
(the highest JSON:API version supported),ext
(an array of URIs for applied extensions), andprofile
(an array of URIs for applied profiles).links
: A links object related to the primary data. This can include self-links, related resource links, and pagination links.included
: An array of resource objects that are related to the primary data and/or each other. This is used for "compound documents" to reduce the number of HTTP requests by sideloading related resources.
Resource Objects: Representing Your Data
A resource object is the cornerstone of JSON:API and must contain:
type
: A string that identifies the type of the resource (e.g.,"articles"
,"users"
). This helps namespace resources and avoids ID collisions between different types.id
: A string that uniquely identifies the resource within its type.
A resource object may also contain:
attributes
: An attributes object containing data specific to the resource. Keys in theattributes
object represent the resource's properties (e.g.,"title"
,"body"
for an article). Relationships must not be represented in theattributes
object.relationships
: A relationships object describing the connections between the resource and other resources.links
: A links object containing links related to the resource, such as aself
link pointing to the resource's canonical URL.meta
: A meta object containing non-standard meta-information about the resource.
Example of a Resource Object:JSON
{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API Unveiled",
"body": "A deep dive into the specification...",
"created_at": "2025-05-15T10:00:00Z",
"updated_at": "2025-05-16T14:30:00Z"
},
"relationships": {
"author": {
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": { "type": "users", "id": "42" }
},
"comments": {
"links": {
"self": "/articles/1/relationships/comments",
"related": "/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
},
"links": {
"self": "/articles/1"
}
}
Resource Identifier Objects
Resource identifier objects are minimal representations of a resource, containing only type
and id
. They are used within relationship objects to link to other resources without embedding the full resource object.
Example of a Resource Identifier Object:JSON
{ "type": "users", "id": "42" }
Links Objects
Links objects provide URLs for navigating the API. Common link members include:
self
: A link that represents the resource or document itself.related
: A link to a related resource or collection. Often used in relationships to fetch the actual related data.- Pagination Links:
first
,last
,prev
,next
for navigating paginated collections.
A link can be represented as:
- A simple string containing the URL.
- A link object, which must contain an
href
(string URL) and can optionally includerel
(relation type),describedby
(link to a description document),title
,type
(media type of the target),hreflang
, and ameta
object.
Example of a Links Object (within a relationship):JSON
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
}
Meta Objects
Meta objects allow for the inclusion of non-standard meta-information. This can be arbitrary key-value pairs. For instance, a meta
object could include copyright information or timestamps related to the data.
Example of a Meta Object:JSON
"meta": {
"copyright": "Copyright 2025 Example Corp.",
"authors": ["John Doe"]
}
Content Negotiation: Speaking the Right Language
JSON:API defines its own media type: application/vnd.api+json
.
Accept
Header: Clients must send this header with theapplication/vnd.api+json
media type to indicate they expect a JSON:API compliant response. If a server cannot satisfy any of the media types in theAccept
header, it must respond with a406 Not Acceptable
status.Content-Type
Header: Clients and servers must use this header with theapplication/vnd.api+json
media type for all requests and responses that contain a JSON:API document in their body. If a request specifies aContent-Type
other thanapplication/vnd.api+json
(or other registered media types the server supports) and contains a body, the server must respond with a415 Unsupported Media Type
status. If a request specifiesContent-Type: application/vnd.api+json
but the body is not a valid JSON:API document, the server must respond with400 Bad Request
.
Servers can support other media types alongside application/vnd.api+json
through standard content negotiation.
Fetching Data: Retrieving Resources and Collections
JSON:API provides robust mechanisms for clients to retrieve data precisely as needed.
Fetching Individual Resources
To fetch a single resource, a client sends a GET
request to an endpoint representing that resource.
Request:
GET /articles/1
Accept: application/vnd.api+json
Successful Response (200 OK):JSON
{
"links": {
"self": "/articles/1"
},
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API Rocks!"
}
// ... other attributes and relationships
}
}
If the resource does not exist, the server should return a 404 Not Found
. If a to-one related resource link is fetched and the relationship is empty, the primary data will be null
.
Fetching Collections of Resources
To fetch a collection of resources, a client sends a GET
request to an endpoint representing that collection.
Request:
GET /articles
Accept: application/vnd.api+json
Successful Response (200 OK):JSON
{
"links": {
"self": "/articles",
"next": "/articles?page[offset]=10",
"last": "/articles?page[offset]=50"
},
"data": [
{
"type": "articles",
"id": "1",
"attributes": { "title": "Article 1" }
// ...
},
{
"type": "articles",
"id": "2",
"attributes": { "title": "Article 2" }
// ...
}
// ... more articles
]
}
If the collection is empty, the data
member will be an empty array []
.
Relationships: Connecting Resources
Relationships are a fundamental part of most data models. JSON:API provides a clear way to define and interact with them.
Representing Relationships
Relationships are defined within the relationships
object of a resource. Each entry in the relationships
object represents a distinct relationship (e.g., "author," "comments").
A relationship object must contain at least one of:
links
: Containsself
andrelated
links.- The
self
link (relationship URL) allows manipulation of the relationship itself (e.g., adding/removing items in a to-many relationship). When fetched, it returns resource identifier objects for the related resources. - The
related
link (related resource URL) allows fetching the related resource objects directly. data
: Contains resource linkage (resource identifier objects).- For a to-one relationship: a single resource identifier object or
null
. - For a to-many relationship: an array of resource identifier objects or an empty array
[]
. meta
: A meta object for non-standard information about the relationship.
Example of "author" (to-one) and "comments" (to-many) relationships:JSON
"relationships": {
"author": {
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": { "type": "users", "id": "42" }
},
"comments": {
"links": {
"self": "/articles/1/relationships/comments",
"related": "/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
}
Fetching Relationships
Clients can fetch information about a relationship itself or the related resources using the links provided.
Fetching Relationship Linkage (self link):
GET /articles/1/relationships/comments
Accept: application/vnd.api+json
This returns a collection of resource identifier objects for the comments related to article "1".
Fetching Related Resources (related link):
GET /articles/1/comments
Accept: application/vnd.api+json
This returns a collection of full comment resource objects related to article "1".
Optimizing Data Retrieval
JSON:API offers several features to optimize how data is retrieved, minimizing bandwidth and improving client-side performance.
Compound Documents: Reducing HTTP Requests with include
To avoid multiple round trips to the server for fetching related resources, JSON:API allows clients to request that related resources be included in the primary response using the include
query parameter. The server will then sideload these resources into the top-level included
array.
Request to fetch an article and include its author and comments:
GET /articles/1?include=author,comments
Accept: application/vnd.api+json
Response (200 OK):JSON
{
"data": {
"type": "articles",
"id": "1",
"attributes": { "title": "..." },
"relationships": {
"author": {
"data": { "type": "users", "id": "42" }
},
"comments": {
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
}
},
"included": [
{
"type": "users",
"id": "42",
"attributes": { "name": "John Doe" }
},
{
"type": "comments",
"id": "5",
"attributes": { "body": "Great article!" }
},
{
"type": "comments",
"id": "12",
"attributes": { "body": "Very informative." }
}
]
}
- The
include
parameter takes a comma-separated list of relationship paths. - Nested relationships can be included using dot notation (e.g.,
include=comments.author
). - If an endpoint doesn't support
include
, it must return400 Bad Request
. - The server must not include unrequested resources in the
included
section.
Sparse Fieldsets: Fetching Only Necessary Fields
Clients can request that only specific fields (attributes and relationships) be returned for resources of a given type using the fields[TYPE]
query parameter. This reduces payload size.
Request to fetch articles, but only their title and author relationship:
GET /articles?fields[articles]=title,author
Accept: application/vnd.api+json
Response (200 OK):JSON
{
"data": [
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Article 1"
},
"relationships": {
"author": {
"data": { "type": "users", "id": "42" }
}
}
}
// ... other articles with only title and author
]
}
- The
id
andtype
are always included. - If a client requests a field that does not exist, the server must ignore it.
- If a client requests only fields that are relationships, the
attributes
member may be omitted.
Sorting
Clients can request that the primary data be sorted using the sort
query parameter.
Request to fetch articles sorted by creation date (descending) and then title (ascending):
GET /articles?sort=-created_at,title
Accept: application/vnd.api+json
- A leading hyphen (
-
) indicates descending order; otherwise, it's ascending. - The server defines which attributes can be used for sorting. Requests for sorting on unsupported attributes should result in a
400 Bad Request
.
Pagination
JSON:API supports various pagination strategies. The specification defines how pagination links (first
, prev
, next
, last
) should appear in the top-level links
object. The actual pagination strategy (e.g., page-based, offset-based, cursor-based) is determined by the server using query parameters like page[number]
, page[size]
, page[offset]
, page[limit]
, or page[cursor]
.
Example of page-based pagination links:JSON
"links": {
"self": "/articles?page[number]=2&page[size]=10",
"first": "/articles?page[number]=1&page[size]=10",
"prev": "/articles?page[number]=1&page[size]=10",
"next": "/articles?page[number]=3&page[size]=10",
"last": "/articles?page[number]=5&page[size]=10"
}
Clients should use these provided links rather than constructing their own pagination URLs.
Filtering
The specification reserves the filter
query parameter for filtering data. However, it does not mandate a specific filtering strategy. Servers can implement any strategy, such as filter[attribute]=value
or more complex expression-based filtering.
Example (recommended by JSON:API, but not mandated):
GET /comments?filter[post]=1 (Get comments for post with ID 1)
GET /comments?filter[post]=1,2&filter[author]=12 (Get comments for posts 1 or 2, by author 12)
Clients should consult the API's documentation to understand its specific filtering capabilities.
Modifying Data: Creating, Updating, and Deleting Resources
JSON:API defines clear protocols for data manipulation operations.
Creating Resources
To create a resource, a client sends a POST
request to a URL representing a collection of resources. The request body must contain a single resource object with type
and, optionally, attributes
and relationships
. The client must not provide an id
for the new resource (unless client-generated IDs are supported and enabled).
Request:
POST /articles
Accept: application/vnd.api+json
Content-Type: application/vnd.api+jsonJSON
{
"data": {
"type": "articles",
"attributes": {
"title": "New Article Title",
"body": "Content of the new article."
},
"relationships": {
"author": {
"data": { "type": "users", "id": "42" }
}
}
}
}
Successful Responses:
201 Created
: If the resource was created successfully. The response must include aLocation
header identifying the URL of the newly created resource. The response body should include the newly created resource, including its server-assignedid
.202 Accepted
: If the creation request has been accepted for processing, but the processing is not yet complete (e.g., for asynchronous operations). The response may include a link to monitor the status.204 No Content
: If the resource was created successfully, but the server chooses not to return the resource representation in the response body. TheLocation
header is still required.
If an attempt is made to create a resource with a client-generated ID that already exists, and the server doesn't support updating via POST
, it must return 409 Conflict
.
Updating Resources
Resources are updated using the PATCH
HTTP method. The request must include the id
of the resource to be updated. The request body contains a resource object with type
, id
, and the attributes
and/or relationships
to be updated.
Request to update an article's title and one of its relationships:
PATCH /articles/1
Accept: application/vnd.api+json
Content-Type: application/vnd.api+jsonJSON
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "Updated Article Title"
},
"relationships": {
"tags": {
"data": [
{ "type": "tags", "id": "3" },
{ "type": "tags", "id": "4" }
]
}
}
}
}
Key points for updates:
- Partial Updates:
PATCH
requests should be partial. Only the fields present in the request should be updated. Fields not included should remain unchanged. - Updating Relationships:
- To-one: Provide a resource identifier object or
null
in the relationship'sdata
. - To-many: Provide an array of resource identifier objects. This completely replaces the existing relationship members. To add or remove members without replacing the entire set, dedicated relationship endpoints (
/articles/1/relationships/tags
) should be used withPOST
(to add),DELETE
(to remove), orPATCH
(to replace all). - The server must not update attributes or relationships that are not specified in the request.
Successful Responses:
200 OK
: If the update was successful and the server returns a representation of the updated resource.202 Accepted
: If the update request has been accepted for processing, but is not yet complete.204 No Content
: If the update was successful, but the server chooses not to return a representation.
If the resource to be updated does not exist, the server must return 404 Not Found
.
Updating Relationships Directly
JSON:API provides specific ways to manage relationships without affecting the primary resource's attributes.
- Updating a To-One Relationship:
PATCH /articles/1/relationships/authorContent-Type: application/vnd.api+json
JSON
{
"data": { "type": "users", "id": "24" } // Assign new author or null to clear
}
- Updating a To-Many Relationship (Complete Replacement):
PATCH /articles/1/relationships/commentsContent-Type: application/vnd.api+json
JSON
{
"data": [
{ "type": "comments", "id": "101" },
{ "type": "comments", "id": "102" }
]
}
- Adding to a To-Many Relationship:
POST /articles/1/relationships/commentsContent-Type: application/vnd.api+json
JSON
{
"data": [
{ "type": "comments", "id": "103" }
]
}
- Removing from a To-Many Relationship:
DELETE /articles/1/relationships/commentsContent-Type: application/vnd.api+json
JSON
{
"data": [
{ "type": "comments", "id": "5" }
]
}
Successful relationship updates usually return 200 OK
or 204 No Content
.
Deleting Resources
To delete a resource, a client sends a DELETE
request to the resource's endpoint.
Request:
DELETE /articles/1
Accept: application/vnd.api+json
Successful Response:
204 No Content
: If the deletion was successful and no response body is returned.200 OK
: If the server returns meta-information about the deletion.202 Accepted
: If the deletion request has been accepted for processing.
If the resource does not exist, the server should return 404 Not Found
. The specification does not dictate how related resources or relationships should be handled upon deletion (e.g., cascading deletes); this is an implementation detail.
Error Handling
When an error occurs, servers must use appropriate HTTP status codes (4xx for client errors, 5xx for server errors). The response body should contain a JSON:API error document.
An error document includes a top-level errors
member, which is an array of error objects. Each error object can contain:
id
: A unique identifier for this particular occurrence of the problem.links
: A links object containing:about
: A link that leads to further1 details about this particular occurrence of the problem.type
: A link that identifies the type of error that this particular error is an instance of.status
: The HTTP status code applicable to this problem, expressed as a string.code
: An application-specific error code, expressed as a2 string.title
: A short, human-readable summary of the problem.3detail
: A human-readable explanation specific to this occurrence of the problem.source
: An object containing references to the source of the error in the request document:4pointer
: A JSON Pointer [RFC6901] to the associated entity in the request document.parameter
: A string indicating which URI query parameter caused the error.header
:5 A string indicating the name of a single request header which caused the error.meta
: A meta object containing non-standard meta-information about the error.
Example Error Response (422 Unprocessable Entity):JSON
{
"errors": [
{
"status": "422",
"source": { "pointer": "/data/attributes/email" },
"title": "Invalid Attribute",
"detail": "The email address is not valid."
},
{
"status": "422",
"source": { "pointer": "/data/relationships/author" },
"title": "Invalid Relationship Value",
"detail": "Author with ID '999' does not exist."
}
]
}
Server-Side Considerations and Best Practices
While the core specification is comprehensive, JSON:API also provides recommendations for aspects like URL design and member naming.
URL Design
- Use plural nouns for resource collections (e.g.,
/articles
). - Use
/articles/{id}
for individual resources. - Use
/articles/{id}/relationships/{relationshipName}
for relationship self-links. - Use
/articles/{id}/{relationshipName}
for related resource links.
Member Naming
- Member names (keys) should be camel-cased (e.g.,
firstName
) or use hyphens/underscores as word separators (e.g.,first-name
orfirst_name
). Consistency within an API is key. The specification itself uses kebab-case for its own query parameters (e.g.,page[number]
). - Member names should contain only ASCII alphanumeric characters, hyphens, and underscores.
Extending the Specification: Extensions and Profiles
JSON:API is designed to be extensible to cater to evolving needs and specific use cases.
Extensions
Extensions can introduce new functionality not covered by the base specification. An example is the "Atomic Operations" extension, which allows multiple operations (create, update, delete) to be performed in a single, atomic request. Both client and server must understand an extension for it to be used. If a server receives a request with an unsupported extension, it must respond with an appropriate error.
Profiles
Profiles define a set of conventions on top of the base specification for a particular use case (e.g., a specific way to handle timestamps or a common set of meta
attributes). Unlike extensions, profiles can be safely ignored if not understood by one party. They are intended to promote interoperability for common patterns without requiring changes to the core specification or mandating universal support.
Servers can advertise supported extensions and profiles in the top-level jsonapi
object. This allows clients to discover these capabilities and tailor their requests accordingly.
The Future of JSON:API
JSON:API continues to evolve, driven by community input and the need to address emerging API design challenges. Its focus on convention over configuration, efficiency, and developer experience has solidified its place as a leading standard for building modern APIs. By adopting JSON:API, development teams can significantly reduce ambiguity, enhance interoperability, and accelerate the pace of API development and consumption.
This detailed exploration covers the vast majority of the JSON:API specification. By understanding and implementing these principles, developers can create APIs that are not only functional but also clean, consistent, and a pleasure to work with, ultimately fostering a more productive and collaborative API ecosystem.