Your input is appreciated. Feel free to file a GitHub Issue, a Pull Request, or contact me on Twitter @MarioDemuth. Thank you!
NOTE: The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Hyper-Item is a hypermedia type that tries to combine the best parts of the Collection+JSON and Siren media types to enable the creation of task-based/inductive user interfaces that have the ability to be extended by new features (properties, links, actions and sub-items) at runtime.
If you have a Hyper-Item client feel free to say hi my demo API at http://hi.cognicraft.net and try to follow links and submit actions.
The media type for Hyper-Item in JSON is application/vnd.hyper-item+json
.
The image above shows how an item in a generic task-based UI might look like.
The image above shows a representation of a generic collection.
The image above shows a representation of a generic action component.
The image above shows a representation of a generic filter component.
The image above shows a representation of a generic sort component.
The following example shows a list of users configured within an exemplary auth service. The self
link shows that his collection has been filtered to only include users that have had their last-login before noon on Jan 9 2017 and sorted by the users name in ascending order. A new user may be added to this collection by means of submitting the add-user
action.
{
"label": "Users",
"type" : "users",
"items":[
{
"label": "Alice",
"type": "user",
"id": "0001",
"properties": [
{
"label": "Name",
"type": "text",
"name": "name",
"value": "Alice"
},
{
"label": "Status",
"type": "text",
"name": "status",
"value": "activated",
"display": "Activated"
},
{
"label": "Last Login",
"type": "date",
"name": "last-login",
"value": "2017-01-08T15:09:12Z",
"display": "Jan 8, 2017"
}
],
"links": [
{
"label": "Details",
"rel": "details",
"href": "/auth/users/0001"
}
]
},
{
"label": "Bob",
"type": "user",
"id": "0002",
"properties": [
{
"label": "Name",
"type": "text",
"name": "name",
"value": "Bob"
},
{
"label": "Status",
"type": "text",
"name": "status",
"value": "deactivated",
"display": "Deactivated"
},
{
"label": "Last Login",
"type": "date",
"name": "last-login",
"value": "2017-01-09T06:12:18Z",
"display": "Jan 9, 2017"
}
],
"links": [
{
"label": "Details",
"rel": "details",
"href": "/auth/users/0002"
}
]
}
],
"links":[
{
"label": "Reload",
"rel": "self",
"href": "/auth/users/?sort=name,ASC&filter=last-login,lt,2017-01-09T12:00:00Z"
},
{
"label": "Filter",
"rel": "filter",
"template": "/auth/users/?sort=name,ASC{&filter*}",
"parameters": [
{
"name": "filter",
"type": "filter",
"components": [
{
"label": "Name",
"name": "name",
"type": "text",
"operators": [
{
"label": "Like",
"operator": "like"
},
{
"label": "Not Like",
"operator": "nlike"
}
]
},
{
"label": "Status",
"name": "status",
"type": "select",
"operators": [
{
"label": "=",
"operator": "eq"
},
{
"label": "!=",
"operator": "neq"
}
],
"options": [
{
"label": "Activated",
"value": "activated"
},
{
"label": "Deactivated",
"value": "deactivated"
}
]
},
{
"label": "Last Login",
"name": "last-login",
"type": "date",
"operators": [
{
"label": "Before",
"operator": "lt"
},
{
"label": "After",
"operator": "gt"
}
]
},
],
"value": [
{
"name": "last-login",
"operator": "lt",
"value": "2017-01-09T12:00:00Z"
}
]
}
]
},
{
"label": "Sort",
"rel": "sort",
"template": "/auth/users/?filter=last-login,lt,2017-01-09T12:00:00Z{&sort*}",
"parameters": [
{
"name": "sort",
"type": "sort",
"components":[
{
"label": "Name",
"name": "name",
"orders": [
{
"label": "ascending",
"order": "ASC",
}
{
"label": "descending",
"order": "DESC",
}
]
}
{
"label": "Last Login",
"name": "last-login",
"orders": [
{
"label": "ascending",
"order": "ASC",
}
{
"label": "descending",
"order": "DESC",
}
]
}
],
"value": [
{
"name": "name",
"order": "ASC"
}
],
}
],
},
],
"actions": [
{
"label": "Add User",
"rel": "add-user",
"href": "/auth/users/",
"encoding": "application/json",
"method": "POST",
"parameters": [
{
"type": "text",
"label": "Name",
"name": "name",
"value": "New User",
"required": true
}
],
"ok": "Add",
"cancel": "Cancel"
}
]
}
*** REQUEST ***
POST /auth/users/ HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
Content-Type: application/json
{
"name": "New Users Name"
}
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{ ... }
Each filter component leads to an &filter
query part in the URL. Each of those has a list of comma separated values with at least three parts: name,operator,value. If the value is an array then each of these will in turn be separated by a comma.
*** REQUEST ***
GET /auth/users/?sort=name,ASC&filter=last-login,lt,2017-01-09T12:00:00Z HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{ ... }
Each sort component leads to an &sort
query part in the URL. Each of those has a list of comma separated values with exactly two parts: name,order.
*** REQUEST ***
GET /auth/users/?filter=last-login,lt,2017-01-09T12:00:00Z&sort=name,ASC HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{ ... }
The following example shows a users details within our exemplary auth service. A user has a set of claims. Hidden parameters in actions are used to transfer enough information to the service to know which action is being submitted. Clients always need to send any hidden parameters within the message Body.
*** REQUEST ***
GET /auth/users/0001 HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{
"label": "Alice",
"type": "user",
"id": "0001",
"properties": [
{
"label": "Name",
"type": "text",
"name": "name",
"value": "Alice"
},
{
"label": "Status",
"type": "text",
"name": "status",
"value": "activated",
"display": "Activated"
},
{
"label": "Last Login",
"type": "date",
"name": "last-login",
"value": "2017-01-08T15:09:12Z",
"display": "Jan 8, 2017"
}
],
"items": [
{
"label": "Claims",
"rel": "claims",
"type": "claims",
"items": [
{
"label": "role → admin",
"type": "claim",
"properties": [
{
"label": "Type",
"name": "type",
"type": "text",
"value": "role"
},
{
"label": "Value",
"name": "value",
"type": "text",
"value": "admin"
}
],
"actions": [
{
"label": "Remove Claim",
"rel": "remove-claim",
"href": "/auth/users/0001",
"encoding": "application/json",
"method": "POST",
"parameters": [
{
"type": "hidden",
"name": "@action",
"value": "remove-claim"
},
{
"type": "hidden",
"name": "type",
"value": "role"
},
{
"type": "hidden",
"name": "value",
"value": "admin"
}
],
"ok": "Remove Claim",
"cancel": "Cancel"
}
]
}
],
"actions": [
{
"label": "Add Claim",
"rel": "add-claim",
"href": "/auth/users/0001",
"encoding": "application/json",
"method": "POST",
"parameters": [
{
"type": "hidden",
"name": "@action",
"value": "add-claim"
},
{
"label": "Type",
"type": "text",
"name": "type",
"required": true
},
{
"label": "Value",
"type": "text",
"name": "value",
"required": true
}
],
"ok": "Add Claim",
"cancel": "Cancel"
}
]
}
],
"links": [
{
"label": "Reload",
"rel": "self",
"href": "/auth/users/0001"
}
],
"actions": [
{
"label": "Rename",
"rel": "rename",
"href": "/auth/users/0001",
"encoding": "application/json",
"method": "POST",
"parameters": [
{
"type": "hidden",
"name": "@action",
"value": "rename"
},
{
"label": "Name",
"type": "text",
"name": "name",
"value": "Alice",
"required": true
}
],
"context": "name",
"ok": "Rename",
"cancel": "Cancel"
},
{
"label": "Deactivate",
"rel": "deactivate",
"href": "/auth/users/0001",
"encoding": "application/json",
"method": "POST",
"parameters": [
{
"type": "hidden",
"name": "@action",
"value": "deactivate"
}
],
"context": "status",
"ok": "Deactivate",
"cancel": "Cancel"
},
{
"label": "Delete",
"rel": "delete",
"href": "/auth/users/0001",
"method": "DELETE",
"ok": "Delete",
"cancel": "Cancel"
}
]
}
*** REQUEST ***
POST /auth/users/0001 HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
Content-Type: application/json
{
"@action": "rename",
"name": "Alice (new)"
}
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{ ... }
*** REQUEST ***
POST /auth/users/0001 HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
Content-Type: application/json
{
"@action": "deactivate"
}
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{ ... }
*** REQUEST ***
DELETE /auth/users/0001 HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{ ... }
*** REQUEST ***
POST /auth/users/0001 HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
Content-Type: application/json
{
"@action": "add-claim",
"type": "role",
"value": "simple-user"
}
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{ ... }
*** REQUEST ***
POST /auth/users/0001 HTTP/1.1
Host: www.example.com
Accept: application/vnd.hyper-item+json
Content-Type: application/json
{
"@action": "remove-claim",
"type": "role",
"value": "admin"
}
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/vnd.hyper-item+json
Content-Length: xxx
{ ... }
{
...
"parameters": [
{
"label": "Country",
"name": "country",
"type": "select",
"related": "http://www.example.com/countries/"
}
],
...
}
*** REQUEST ***
GET /countries/ HTTP/1.1
Host: www.example.com
Accept: application/json
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/json
Content-Length: xxx
[
{
"label": "Bahamas",
"value": "BS"
},
{
"label": "Germany",
"value": "DE"
},
{
"label": "United States of America",
"value": "US"
}
]
{
...
"parameters": [
{
"label": "Country",
"name": "country",
"type": "select",
"related": "http://www.example.com/countries/"
},
{
"label": "State",
"name": "state",
"type": "select",
"related": "http://www.example.com/countries/{country}/states/",
"dependencies": [
"country"
]
},
{
"label": "City",
"name": "city",
"type": "select",
"related": "http://www.example.com/countries/{country}/states/{state}/cities/",
"dependencies": [
"country",
"state"
]
}
],
...
}
*** REQUEST ***
GET /countries/DE/states/ HTTP/1.1
Host: www.example.com
Accept: application/json
*** RESPONSE ***
200 OK HTTP/1.1
Content-Type: application/json
Content-Length: xxx
[
{
"label": "Baden-WĂĽrttemberg",
"value": "DE-BW"
},
{
"label": "Bavaria",
"value": "DE-BY"
},
{
"label": "Berlin",
"value": "DE-BE"
},
{
"label": "Brandenburg",
"value": "DE-BB"
},
{
"label": "Bremen",
"value": "DE-HB"
},
...
]
An item represents the state of a domain concept.
Describes the relation of the item to its parent. Possible values are subject to the domain represented by the item and SHOULD be documented and linked to this item via rel
of profile
.
A document local identifier for the item that may be used as the target of URL fragments.
Describes the type of the item. Possible values are subject to the domain represented by the item and SHOULD be documented and linked to this item via rel
of profile
.
A list of properties describing the current state of the item.
An arbitrary JSONValue that MAY be used as payload that does not naturally fit into properties. This property SHOULD only be used sparsely and SHOULD be ignored by most generic Hyper-Item clients.
A list of links related to the item. A root item SHOULD include a link with rel
to self
with the canonical href
for this item.
A list of actions related to the item.
A list of (sub-)items related to the item.
A list of rendering hints.
none
in case the item SHOULD not be rendered. Can be used to provide local URL fragment targets that do not naturally fit into the standard tree hierarchy.transclude
in case the item SHOULD be replaced with an item targeted by a link withrel
todetails
. (i.e. server-side includes, edge-side includes, resource contributions).
A localized label for the item.
A localized description of the item.
A stamp object indicating when and by whom the item was created (e.g.{"by": "author", "date": "2019-11-18T12:39:55Z"}
).
by
: a string indicating the author that is reponsible for the creationdate
: a ISO formated date string indicating when the cration happened
A stamp object indicating when and by whom the item was last updated (e.g.{"by": "author", "date": "2019-11-18T12:39:55Z"}
).
A property represents a part of the state of an item.
The name of the property. Possible values are subject to the domain represented by the item and SHOULD be documented and linked to this item via rel
of profile
.
The JSONValue of the property.
The type of the property.
date
email
hidden
number
password
text
select
- ...
A localized label for the property.
A localized description of the property.
A localized string representation of the value.
A list of renderinging hints.
none
in case the property SHOULD not be rendered.
A stamp object indicating when and by whom the property was last updated (e.g.{"by": "author", "date": "2019-11-18T12:39:55Z"}
).
Links the item to another concept. The fields href
and template
with parameters
are mutually exclusive. Links MUST be resolved via the HTTP method GET.
The relation of the link to its item.
self
Thehref
SHOULD point to the canonical URL for this item.details
Thehref
SHOULD point to more detailed information about the item.next
can be used by items representing a result set. It SHOULD be used to select the next page of items.previous
can be used by items representing a result set. It SHOULD be used to select the previous page of items.filter
SHOULD be used by items representing a result set to reduce the number of items.sort
SHOULD be used by items representing a result set to reorder the items.profile
SHOULD be used to describe itemtypes
,rels
and propertynames
.- ...
The URL of the target. Links MUST be resolved via the HTTP method GET
.
A URI template as defined by RFC 6570.
Parameters that are used within the URI template.
The value of the type
property specifies the media type of the linked document/resource.
The value of the language
property specifies the base language of the linked document/resource.
The value of the accept
property may be used by the Hyper-Item client to aid in content negotiation. If a value is specified it SHOULD be used as the Accept
header. If the property is missing application/vnd.hyper-item+json
SHOULD be used.
The value of the accept-language
property may be used by the Hyper-Item client to aid in content negotiation. If a value is specified it SHOULD be used as the Accept-Language
header. If the property is missing the current language settings SHOULD be used.
A localized label for the link.
The value of the accept-profile
property may be used by the Hyper-Item client to aid in content negotiation. If a value is specified it SHOULD be used as the Accept-Profile
header as described in Negotiating Profiles.
A list of rendering hints.
none
in case the link SHOULD not be rendered (candidate:rel: profile
).transclude
in case the content returned from the URL SHOULD be embedded into the current item.
A localized label for the link.
A localized description of the link.
The relation of the action to the item. Possible values are subject to the domain represented by the item and SHOULD be documented and linked to this item via rel
of profile
.
The URL of the target.
The HTTP Method to use.
POST
PATCH
PUT
DELETE
- ...
The media type of the action payload. The value SHOULD be used as the Content-Type
header.
application/x-www-form-urlencoded
application/json
multipart/form-data
- ...
Parameters that will need to be sent.
Marks a property as the actions context. If a properties name would be hair-color and this action would represent change-hair-color then a generic representer could use context: hair-color to move the action closer to the representation of the property.
A localized label for the action.
A localized description of the action.
A localized ok/submit label for the action.
A localized cancel label for the action.
A list of rendering hints.
none
in case the action SHOULD not be rendered.
The name of the parameter.
The type of the parameter.
The accepted file types in a file type parameter.
The currently used or default value.
The placeholder to use if no default value is provided.
A list of select-options or select-groups for a select type parameter.
The web-related select-options or select-groups for a select type parameter. The property MUST be a URI template as defined by RFC 6570. Parameters needed within this template MUST be defined in the dependencies list.
A list of parameters that need to be set before this parameter can be set. The values of the parameters that MUST be used during the template expansion for the web-related URI.
A list of filter-components for a filter type parameter. A list of sort-components for a sort type parameter.
A pattern for a text type parameter.
A min value for a number or date type parameter.
A max value for a number or date type parameter.
The step size of number type parameters.
The number of columns in a text-area type parameter.
The number of rows in a text-area type parameter.
Specifies if the parameter is required.
Specifies if the parameter is read only.
Specifies if the parameter may be set multiple times.
A localized label for the link.
A localized description of the link.
The value of a select-option.
A localized label for the select-option.
A localized description of the select-option.
A list of select-options or select-groups for a select-group.
A localized label for the select-group.
A localized description of the select-group.
The name of the filter component.
A list of possible filter operators for the filter component.
The type of value to be set.
The select-options for a select type filter component.
The web-related select-options for a select type filter component.
Specifies if the value is an array.
A localized label for the filter component.
A localized description of the filter component.
The operator that SHOULD be used.
eq
equalneq
not equallt
less thangt
greater thanleq
less than or equal togeq
greater than or equal toin
innin
not inlike
likenlike
not likebet
betweennbet
not between- ...
A localized label for the filter operator.
A localized description of the filter operator.
If the operator has an infix part this is a localized infix text. (bet
: start
and end
)
The name of the sort component.
A list of possible sort orders of the sort component.
A localized label for the sort component.
A localized description of the sort component.
A localized label for the sort order.
A localized description of the sort order.
The order that SHOULD be used.
ASC
ascendingDESC
descending
A localized label for the error.
A localized description of the error.
A localized message of the error.
An error code.
This document describes the Hyper-Item vocabulary. Markup from other vocabularies ("foreign markup") can be used in a Hyper-Item document. Any extensions to the Hyper-Item vocabulary MUST not redefine any objects (or their properties), arrays, properties, link relations, or data types defined in this document. Clients that do not recognize extensions to the Hyper-Item vocabulary SHOULD ignore them.
The details of designing and implementing Hyper-Item extensions is beyond the scope of this document.
NOTE: It is possible that future forward-compatible modifications to this specification will include new objects, arrays, properties, link-relations, and data types. Extension designers SHOULD take care to prevent future modifications from breaking or redefining those extensions.
- Arrays have a predefined order. An Object is an unordered collection of properties.
- Arrays enable the use of
each()
,filter()
,sortBy()
andkeyBy()
. - In a context where each property, link, and action is subject to authentication access rules a client may never know which of these actually exist. And in such a context indexing by key will never be a good idea, but you may always use
keyBy()
. - Since we are using
each()
in our representation logic new properties, links and actions may appear at runtime. - The concepts used in this specification are build so that you may reduce context at each step during representation. For example if you need to display an action for renaming a user this action will have a name parameter with the value set to the current users name. This enables you to loose the reference to the user item entirely. Another example would be the users name property that holds every piece of information needed to represent a text property.
- Collection+JSON: Collection+JSON - Hypermedia Type
- Siren: Siren: a hypermedia specification for representing entities
- Task-Based UI: CQRS: Task-Based UI
- Inductive UI: Microsoft Inductive User Interface Guidelines
- RFC 3986: Uniform Resource Identifier (URI)
- RFC 6570: URI Template
- RFC 2119: Key words for use in RFCs to Indicate Requirement Levels
- Negotiating Profiles: Accpet-Profile and Profile headers
- http://hi.cognicraft.net: Hyper-Item: Demo API