Apidog

All-in-one Collaborative API Development Platform

API Design

API Documentation

API Debugging

API Mocking

API Automated Testing

A Complete Introduction to the JSON:API Specification

This comprehensive guide offers a deep dive into the JSON:API specification, exploring its core concepts, structure, and powerful features.

Stefania Boiko

Stefania Boiko

Updated on May 19, 2025

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 a great API Testing tool that generates beautiful API Documentation?

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!
button

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. The data member must not be present if errors 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 include version (the highest JSON:API version supported), ext (an array of URIs for applied extensions), and profile (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 the attributes object represent the resource's properties (e.g., "title", "body" for an article). Relationships must not be represented in the attributes 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 a self 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 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 include rel (relation type), describedby (link to a description document), title, type (media type of the target), hreflang, and a meta 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 the application/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 the Accept header, it must respond with a 406 Not Acceptable status.
  • Content-Type Header: Clients and servers must use this header with the application/vnd.api+json media type for all requests and responses that contain a JSON:API document in their body. If a request specifies a Content-Type other than application/vnd.api+json (or other registered media types the server supports) and contains a body, the server must respond with a 415 Unsupported Media Type status. If a request specifies Content-Type: application/vnd.api+json but the body is not a valid JSON:API document, the server must respond with 400 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: Contains self and related 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 return 400 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 and type 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 a Location header identifying the URL of the newly created resource. The response body should include the newly created resource, including its server-assigned id.
  • 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. The Location 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's data.
  • 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 with POST (to add), DELETE (to remove), or PATCH (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+jsonJSON
{
  "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+jsonJSON
{
  "data": [
    { "type": "comments", "id": "101" },
    { "type": "comments", "id": "102" }
  ]
}
  • Adding to a To-Many Relationship:POST /articles/1/relationships/commentsContent-Type: application/vnd.api+jsonJSON
{
  "data": [
    { "type": "comments", "id": "103" }
  ]
}
  • Removing from a To-Many Relationship:DELETE /articles/1/relationships/commentsContent-Type: application/vnd.api+jsonJSON
{
  "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.3
  • detail: 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:4
  • pointer: 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 or first_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.

Top 10 AI Doc Generators & API Documentation Makers for 2025Viewpoint

Top 10 AI Doc Generators & API Documentation Makers for 2025

Whether you're documenting internal systems, creating user guides, or publishing detailed API references, the right tool can make all the difference. Let's dive into the best options available today.

Emmanuel Mumba

May 19, 2025

Top 10 Best API Newsletters & Podcasts You Cannot Miss in 2025Viewpoint

Top 10 Best API Newsletters & Podcasts You Cannot Miss in 2025

This article presents a curated selection of top API-centric newsletters and podcasts that are indispensable for anyone serious about mastering the API domain in 2025.

Mikael Svenson

May 19, 2025

How to Use Spectral with TypeScriptViewpoint

How to Use Spectral with TypeScript

This tutorial will guide you through the process of leveraging Spectral with TypeScript, from initial setup to crafting sophisticated custom validation logic.

Mikael Svenson

May 19, 2025