Specification

JSON Hyper-Schema

Introduction

JSON Hyper-Schema is an extension of JSON Schema that allows for the definition of hypermedia-driven APIs. The hyper-schema vocabulary shows how to annotate JSON documents with hypermedia controls by enabling the description of links and actions that can be executed on JSON data. Consecutively, it helps provide a more interactive and dynamic representation of JSON data. It also enhances API discoverability using the description features of actions and links within the JSON documents.

JSON Hyper-Schema seamlessly integrates with existing JSON HTTP APIs and offers functionalities to describe complex resource relationships, facilitate client-side validation, and promote better interaction patterns. It makes APIs more intuitive, self-descriptive, and efficient, particularly in RESTful architectures.

In essence:

  • JSON Hyper-Schema is helpful in complex APIs where clients need to define and explicitly understand the relationships between resources and actions, especially when navigating resources without prior knowledge of the API structure.
  • It helps create more discoverable and self-documenting APIs, making it easier for clients to interact with them.

Hyper-schema use cases

Basic concept

Get a JSON instance

1GET https://example.com/book/12345

This GET request fetches information about a specific book, identified by the book ID (12345). In response:

1200 OK
2Content-Type: application/json
3Link: <https://example.com/schemas/book>; rel="describedby"
4
5{
6  "title": "Mobby-Dick",
7  "authorId": 100,
8  ...
9}

The server responds with a successful 200 OK status, JSON-formatted data, and a Link header that references the schema describing the structure of the returned data, including details like the book title and a link to the author via authorId.

Get the schema that describes the instance using the "describedby" Link header.

1GET https://example.com/schemas/book

This request fetches the schema describing the structure of the book object. In response:

1200 OK
2Content-Type: application/schema+json
3
4{
5  "links": [
6    {
7      "href": "/author/{authorId}",
8      "rel": "author",
9      "templateRequired": ["authorId"]
10    }
11  ]
12}

The response contains a JSON Schema describing the instance's structure and links to relevant resources, such as the author's resource via rel="author." It also specifies that the authorId is required to complete the URL template.

Use the LDO combined with the instance to construct a link to get information about the author.

1GET https://example.com/author/100

This request fetches data for the author with authorId 100. In response:

1200 OK
2Content-Type: application/json
3Link: <https://example.com/schemas/author>; rel="describedby"
4
5{
6  "name": "Herman Melville",
7  ...
8}

The response includes a JSON object with the author's details, and the Link header points to the schema that describes the author, following the same format as the book schema.

Keep following links to discover more content.

Actions

Using a link to construct a URI from user input. (Analogous to <form method="get">)

data
{ "links": [ { "href": "/books" "rel": "search", "hrefSchema": { "type": "object", "properties": { "title": { "type": "string" }, "author": { "type": "integer" }, ... } } } ]}
1GET https://example.com/books?title=Moby-Dick

This GET request searches for books with the title Moby-Dick.

1200 OK
2Content-Type: application/json
3Link: <https://example.com/schemas/books>; rel="describedby"
4
5[
6  {
7    "title": "Mobby-Dick",
8    "authorId": 100,
9    ...
10  }
11]

The response returns a successful 200 OK JSON-formatted data with a Link to the schema describing the book collection, and the response body contains an array of books, each with details such as the title and authorId.

Submission

Using a link to submit user input. (Analogous to <form method="post">)

data
{ "links": [ { "href": "/books" "rel": "collection", "submissionSchema": { "type": "object", "properties": { "title": { "type": "string" }, "authorId": { "type": "integer" }, ... }, "required": ["title", "authorId", ...] } } ]}
1POST https://example.com/books
2Content-Type: application/json
3
4{
5  "title": "Moby-Dick",
6  "authorId": 100,
7  ...
8}

This POST request submits data to create a new book with the title "Moby-Dick" and authorId 100.

1204 No Content

The request was successful, and the book was created, but the response contained no content.

Submission Media Type

You can also make submission will media types other than JSON.

data
{ "links": [ { "href": "/books" "rel": "collection", "submissionMediaType": "application/xml", "submissionSchema": { "type": "object", "properties": { "title": { "type": "string" }, "authorId": { "type": "integer" }, ... }, "required": ["title", "authorId", ...] } } ]}
1POST https://example.com/books
2Content-Type: application/xml
3
4<Book>
5  <title>Moby-Dick<title>
6  <authorId>100</authorId>
7  ...
8</Book>
1204 No Content

This code shows using application/xml, to create a new book resource with a POST request.

Relations

Registered relation

A registered relation pointing to an author.

data
{ "links": [ { "href": "https://example.com/authors/100", "rel": "author" } ]}

Custom relation

A custom relation linking to a specific review.

data
{ "links": [ { "href": "https://example.com/review/6789", "rel": "https://example.com/relations/review" } ]}

Multiple relations

A multiple relations associating a comments collection with two different relationship types.

data
{ "links": [ { "href": "https://example.com/reviews", "rel": ["collection", "https://example.com/relations/reviews"] } ]}

Base URIs

A base URI is used to resolve relative URIs.

Default

By default, the base URI for relative URIs in LDOs is the URI used to retrieve the resource

1GET https://example.com/books/1
data
{ "links": [ { "href": "/author/1", "rel": "author" } ]}

Link Target: https://example.com/author/1

Setting the base URI

base can be used to alter the base URI. It is a URI Template and can be relative.

1GET https://example.com/myapi/v3/books/1
data
{ "base": "/myapi/v3/", "links": [ { "href": "author/1", "rel": "author" } ]}

Link Target: https://example.com/myapi/v3/author/1

Setting a link's "anchor"

A link is a connection between two resources. The source of the link is called the "anchor" and the destination is called the "target. The "anchor" usually doesn't need to be specified because it's understood to be the resource the link appears in. anchor allows you to change the link's "anchor" to something other than the resource it appears in.

When anchor appears in an LDO, it becomes the base URI for resolving href.

NOTE: I don't have an example because I can't think of any reason someone would want to do this. It's an anti-pattern at best. It might be best to just leave anchor and anchorPointer undocumented.

URI Templates

href, base, and anchor are URI Templates

Template variables

data
{ "type": "object", "properties": { "documentation": { "links": [ { "href": "/docs", "rel": "about" } ] } }}
data
{}

about: N/A

Note: Links are annotations, which means they're attached to a location in the JSON instance. If the location of the link doesn't exist in the JSON instance, the link doesn't apply.

data
{ "documentation": true}

about: https://example.com/docs

Required/Optional variables
data
{ "links": [ { "href": "/books?page={next}{&perPage}", "rel": "next", "templateRequired": ["next"] }, { "href": "/books?page={previous}{&perPage}", "rel": "previous", "templateRequired": ["previous"] } ]}
data
{ "list": [ { ... book 1 ... }, { ... book 2 ... }, { ... book 3 ... } ], "page": 0, "next": 1}

next: https://example.com/books?page=1

previous: N/A

Note: previous doesn't apply because the required property "previous" is not present. Note: perPage is an optional variable. The next link still applies even though there is no "perPage" property.

data
{ "list": [ { ... book 1 ... }, { ... book 2 ... } ], "metaData": { "page": 0, "next": 1, "perPage": 2 }}

next: https://example.com/books?page=1&perPage=2

previous: N/A

Note: Optional variable perPage is present and included in the link.

Variable coersion
data
{ "links": [ { "href": "/{a}", "rel": "https://example.com/relations/a" } ]}
data
{ "a": true }

https://example.com/relations/a: https://example.com/true

data
{ "a": false }

https://example.com/relations/a: https://example.com/false

data
{ "a": null }

https://example.com/relations/a: https://example.com/null

data
{ "a": 42 }

https://example.com/relations/a: https://example.com/42

Pointers

Expand a variable from a different object.

data
{ "type": "object", "properties": { "cartItems": { "type": "array", "items": { "links": [ { "href": "cart-item/{cartId}/{cartItemId}", "rel": "https://example.com/relations/cart-item", "templateRequired": ["cartId", "cartItemId"], "templatePointer": { "cartId": "/cartId" } } ] } } }}
data
{ "cartId": 100, "cartItems": [ { "cartItemId": 200, ... } ], ...}

https://example.com/relations/cart-item: https://example.com/cart-item/100/200

Example using Relative JSON Pointer instead of JSON Pointer.

data
{ "type": "object", "properties": { "cartItems": { "type": "array", "items": { "links": [ { "href": "cart-item/{cartId}/{cartItemId}", "rel": "https://example.com/relations/cart-item", "templateRequired": ["cartId", "cartItemId"], "templatePointer": { "cartId": "2/cartId" } } ] } } }}

https://example.com/relations/cart-item: https://example.com/cart-item/100/200

TODO

  • PUT and DELETE
  • title
  • description
  • targetMediaType
  • targetSchema
  • targetHints
  • headerSchema
  • Special case relations: collection/item, root, self

Hyper Schema Specification

Schemas:

Release Notes

Need Help?

Did you find these docs helpful?

Help us make our docs great!

At JSON Schema, we value docs contributions as much as every other type of contribution!

Still Need Help?

Learning JSON Schema is often confusing, but don't worry, we are here to help!.