W3C Member Submission

Volkswagen Infotainment Web Interface protocol specification (viwi protocol)

W3C Member Submission 18 July 2019

This version:
https://www.w3.org/submissions/2019/SUBM-viwi-protocol-20190718/
Latest version:
https://www.w3.org/submissions/viwi-protocol/
Previous version:
https://www.w3.org/submissions/2016/SUBM-viwi-protocol-20161213/
Authors:
Patrick Lünnemann (VW)
Martin Wuschke (VW)

Volkswagen Infotainment Web Interface protocol specification (viwi protocol)

curly braces icon for VIWI

 

version 1.9.0-w3c

Abstract

Status of this document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications can be found in the W3C technical reports index at https://www.w3.org/TR/.

By publishing this document, W3C acknowledges that the Submitting Members have made a formal Submission request to W3C for discussion. Publication of this document by W3C indicates no endorsement of its content by W3C, nor that W3C has, is, or will be allocating any resources to the issues addressed by it. This document is not the product of a chartered W3C group, but is published as potential input to the W3C Process. A W3C Team Comment has been published in conjunction with this Member Submission. Publication of acknowledged Member Submissions at the W3C site is one of the benefits of W3C Membership. Please consult the requirements associated with Member Submissions of section 3.3 of the W3C Patent Policy. Please consult the complete list of acknowledged W3C Member Submissions.

Introduction

Purpose of this document

This document provides an overview over web services on the WebInfotainment platform. All services provide mainly HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_DELETE (herein called GET, POST, PUT, DELETE) and WebSocket interfaces. The response is always of contentType: application/json (or application/vnd.viwi.v<major>.<minor>.<patch>+json for a dedicated version), this also applies for the WebSocket interface. The general approach is a RESTful API.

Client server architecture

The general architecture of a system using the herein defined interfaces is split into WebServer and Client (Figure 1: Example of system architecture). While any client can have different contexts, the web server can have different services providing a number of resources that hold a number of elements. The connection between client and server is always a TCP/IP connection that allows http(s):// and ws(s):// protocols on top.

Paradigms

The API described herein follows some general 'design rules' or 'paradigms'. Deviation of actual interface definition must be minimized in a reasonable way.

  1. The depth of the URI tree is limited to 3, i.e. /<service>/<resource>/<element>. While <service> and <resource> are predefined in this document, <element> will always be an identifier or the keyword $id or $spec. <resource> is always a collection (e.g stations) and thus has to be a noun plural form, even if the <resource> will only carry one single <element> by design. The <resource> identifiers must not carry verbs like getAccount or createTrack.

  2. An event is considered 'on change' if two subsequent GET requests - with no expansion applied (see Expansion concept) - on the corresponding URI would result in different responses. If an elements property 'name' is changed, an 'on change' event is fired (Publish Subscribe). Example: Only if an element is added or removed from a list of objects (on resource level), an 'on change' event is fired (Publish Subscribe). If a client wants to get notified on nested properties or structures, the corresponding event has to be subscribed individually at element level.

  3. To keep the overall network traffic at a minimum, every resource and element access supports filtering. The response filtering concepts "fields", "paging" and "expand" are generally available.

  4. Every object inherits from XObject. I.e. every Object has three mandatory properties: id, uri and name. These properties will be present in every response object.

    1. Every instance inherited from XObject has its own endpoint.

    2. An XObject consists only of properties with primitive or complex types and references to other XObject or arrays of those.

  5. RESTful HTTP calls are the main interface. A client does not necessarily have to register for events, it can also use plain HTTP polling. A polling client will not be automatically updated with the server state. API-Calls that change the server state are responded with a StatusObject only. The altered state has to be accessed via HTTP_GET or by event subscription.

HTTP status codes

All services have to follow the W3C/IANA HTTP status code specification (Hypertext Transfer Protocol (HTTP) Status Code Registry).

This section extends each Status-Code with implication and handling information for a client. The general meaning of status codes remains untouched. The client has to be able to work with any response with a status code defined in RFC6585, RFC7231 and RFC7725. In addition to the status code definitions, some status codes are generally not expected by the client, those codes are marked as not applicable in viwi context. Each status code may have a domain specific meaning, which will be described in the according domain section separately. Please note that each service domain may overwrite the implication and client-side treatment.

Code name Implication Client-side treatment
100 Continue Not applicable. None
101 Switching Protocols Only used for establishing websockets, see RFC6455 section 4.2.2. None
200 OK Used for successful HTTP Requests. This code is used to acknowledge successful change of the resource or element. None
201 Created Used for successfully creating new entities. None
202 Accepted The request has been accepted for processing, but the processing has not been completed. The request might or might not eventually be acted upon. There is no guarantee that the request will be fullfilled. Subscribe to affected entity, if the actual outcome is of interest.
203 Non-Authoritative Information Not applicable. None
204 No Content Not applicable. None
205 Reset Content Not applicable. None
206 Partial Content Not applicable. None
300 Multiple Choices Used if multiple services match the request criteria (service registry only) Client has to select and re-request
301 Moved Permanently Not applicable, see service registry. None
302 Found Not applicable. None
303 See Other Not applicable. None
304 Not Modified If the entity is not modified, this status code can be sent. This indicates that the client should look for this entity in its cache. None
305 Use Proxy Not applicable. None
307 Temporary Redirect The requested resource has moved. The new location is send as an absolute URL in the response HTTP-Header Location field. Client should submit a new HTTP request.
400 Bad Request The client submitted a malformed request, repeating the request will not help. Client has to make sure to send a valid request.
401 Unauthorized This request requires authentication. If client is not authenticated, client has to authenticate.
402 Payment Required Not applicable. None
403 Forbidden The client has insufficient rights to obtain the requested information or has submitted an HTTP request with POST method to a property, which is read-only or not allowed to be set by the client(e.g. POST on /car/info property vehicleIdentication). If client do not have access rights, client has to acquire access rights.
404 Not Found The service cannot find the entity, this may be a permanent or temporary condition. Do not expect elements to be generally available. This status code is treated as a valid response. Use-case specific error handling is required.
405 Method Not Allowed Not applicable, because all possible HTTP-Methods are defined by the the viwi document and missing privileges are singnaled by status code 403. None
406 Not Acceptable Not applicable. None
407 Proxy Authentication Required Not applicable. None
408 Request Time-out Not applicable. The request sent to the server took longer than the server was prepared to wait. In other words, client connection with server "timed out". Server configuration should rule out this error. None
409 Conflict It is not possible to establish the required state. This might be the case if a client wants to set an objects property that is not writable or that currently can not be set. The message property of StatusObject may have information for the client to recognize the source of the conflict. This information is only for debugging and development purposes. Resource specific treatment is necessary (e.g. media audioSource might be disabled during navigation announcements, the clients tries to un-mute media). See RFC7231 section 6.5.8 for more information.
410 Gone Used for WebSocket only. None
411 Length Required Not applicable, see RFC7231 section 6.5.10. None
412 Precondition Failed Not applicable. None
413 Request Entity Too Large Not applicable, see paging. None
414 Request-URI Too Large Not applicable, see RFC7231 section 6.5.12. None
415 Unsupported Media Type Not applicable. None
416 Requested range not satisfiable Not applicable. None
417 Expectation Failed Not applicable. None
426 Upgrade Required Not applicable. None
428 Precondition Required Not applicable. None
429 Too Many Requests The user has sent too many requests in a given amount of time. Used to rate-limit access to a certain resource or element
431 Request Header Fields Too Large Client send header fields which are to large or not correctly formated Send smaller and correct header fields
451 Unavailable For Legal Reasons A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource. Sent, when for any legal reason access to a resource or element is (temporarily) blocked
500 Internal Server Error The server encountered an unexpected condition which prevented it from fulfilling the request. There is no client-side fix/solution for this kind of errors.
501 Not Implemented The server does not support the functionality required to fulfill the request. Unless specified otherwise in service specific status code handling, this error indicates a not-permitted error. Not applicable.
502 Bad Gateway Not applicable. None
503 Service Unavailable Service is currently unable to handle the request. This implies a temporary problem which will be solved after a given delay. If known, the delay may be indicated in a Retry-After header. Client should retry to submit the request after delay.
504 Gateway Time-out The request can not be fulfilled because server acts as a proxy and did not receive a timely response from remote component (e.g. ECU of driver assistance). There is no client-side fix/solution for this kind of error. If the remote component is expected to be reachable after a delay in this power-cycle then server sends a response with status code 503.
505 HTTP Version not supported Not applicable. None
511 Network Authentication Required The connection to the vehicle infrastructure is not authenticated. Reauthenticate the connection channel.

Handling service responses with respecting status codes

To communicate with the services a client submits an HTTP request and waits for the response. A response is always sent with a status code. The service uses the pre-defined status codes when sending a response. Each client interprets the response with the respective status code.

Each status code is categorized as successful (2xx), redirection (3xx), client error (4xx) or server error (5xx). If status code is not successful, then the response must be handled by the client as following:

RESTful API

General information

The REST architectural style describes six constraints:

Conforming to the REST architectural style, will enable any kind of distributed system to have desirable emergent characteristics, such as performance, scalability, simplicity, modifiability, visibility, portability and reliability.

These constraints, applied to the architecture, were originally communicated by Roy Fielding in his doctoral dissertation and define the basis of RESTful-style.

Source: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm

Uniform interface

The uniform interface constraint defines the interface between clients and servers. It simplifies and decouples the architecture, which enables each part to evolve independently. The four guiding principles of the uniform interface are:

Resource-based

Individual resources are identified in requests using URIs as resource identifiers. The resources themselves are conceptually separate from the representations that are returned to the client. For example, the server does not send its database, but rather, some HTML, XML or JSON that represents some database records. For instance data for a finish client, in Finnish language and encoded in UTF-8, depending on the details of the request and the server implementation.

Manipulation of resources through representations

When a client holds a representation of a resource, including any meta data attached, it has enough information to modify or delete the resource on the server, provided it has permission to do so.

Self-descriptive messages

Each message includes enough information to describe how to process the message. For example, which parser to invoke may be specified by an Internet media type (previously known as a MIME type). Responses also explicitly indicate their cache-ability.

Hypermedia as the engine of application state (HATEOAS)

Clients deliver state via body contents, query-string parameters, request headers and the requested URI (the resource name). Services deliver state to clients via body content, response codes, and response headers. This is technically referred-to as hypermedia (or hyperlinks within hypertext). Aside from the description above, HATEOAS also means that, where necessary, links are contained in the returned body (or headers) to supply the URI for retrieval of the object itself or related objects.

The uniform interface that any REST services must provide is fundamental to its design.

Statelessness

As REST is an acronym for REpresentational State Transfer, statelessness is key. Essentially, what this means is that the necessary state to handle the request is contained within the request itself, whether as part of the URI, query-string parameters, body, or headers. The URI uniquely identifies the resource and the body contains the state (or state change) of that resource. Then after the server does it's processing, the appropriate state, or the piece(s) of state that matter, are communicated back to the client via headers, status and response body. Classical - statefull - API design provides us with the concept of programming within a container which maintains state across multiple HTTP requests. In REST, the client must include all information for the server to fulfill the request, resending state as necessary if that state must span multiple requests. Statelessness enables greater scalability since the server does not have to maintain, update or communicate that session state. Additionally, load balancers don't have to worry about session affinity for stateless systems. So what's the difference between state and a resource? State, or application state, is the one which the server cares about to fulfill a request - data necessary for the current session or request. A resource, or resource state, is the data that defines the resource representation — the data stored in the database, for instance. Consider application state to be data that could vary by client, and per request. Resource state, on the other hand, is constant across every client who requests it. Ever had back-button issues with a web application where it went AWOL (Absent Without Official Leave - military jargon) at a certain point because it expected you to do things in a certain order? That's because it violated the statelessness principle. There are cases that don't honor the statelessness principle, such as three-legged OAuth, API call rate limiting, etc. However, make every effort to ensure that application state doesn't span multiple requests of your service(s).

Cacheability

As on the World Wide Web, clients can cache responses. Responses must therefore, implicitly or explicitly, define themselves as cacheable, or not, to prevent clients reusing stale or inappropriate data in response to further requests. Well-managed caching partially or completely eliminates some client-server interactions, further improving scalability and performance.

Client-server architecture

The uniform interface separates clients from servers. This separation of concerns means that, for example, clients are not concerned with data storage, which remains internal to each server, so that the portability of client code is improved. Servers are not concerned with the user interface or user state, so that servers can be simpler and more scalable. Servers and clients may also be replaced and developed independently, as long as the interface is not altered.

Layered system

A client cannot ordinarily tell whether it is connected directly to the end server, or to an intermediary along the way. Intermediary servers may improve system scalability by enabling load-balancing and by providing shared caches. Layers may also enforce security policies.

Code-on-demand optional

Servers are able to temporarily extend or customize the functionality of a client by transferring logic to it that it can execute. Examples of this may include compiled components such as Java applets and client-side scripts such as JavaScript.

Application to viwi

The interfaces described inhere follow the RESTful principles. The main concept in REST is the existence of resources (sources of specific information or services), each of which is referenced with a uri as global identifier. The RESTful API is used to retrieve information for the client (request) from the server (response), while Events provide a channel for communication in server to client direction (push information). The supported HTTP request methods are GET, POST, PUT and DELETE. The following table explains the main principle of interface definition for all services defined hereafter:

type GET (HTTP) POST (HTTP) PUT (HTTP) DELETE (HTTP) subscribe (WebSocket)
root URI, such as / or /api/v1/ List the URIs and perhaps other details of the service known to the system. n/a Register new service with the system. The new service's URI is assigned automatically and is returned by the operation. n/a Get updated on collection changes (e.g. addition, removal of elements)
serviceObject, such as /<service> or /api/v1/<service> Retrieve a representation of the addressed service as serviceObject n/a Add a new service to the registry. De-Register new service with the system. <service> has to be accessed by its uuid. (special permissions needed) n/a
service URI, such as /<service>/ List all elements matching the request (including query parameters). n/a n/a n/a Get updated on collection changes (e.g. addition, removal of elements)
Resource Object, such as /<service>/<resource> Retrieve an XObject describing the resource n/a n/a n/a n/a
Resource URI, such as /<service>/<resource>/ List the URIs and perhaps other details of the collection's members. Create new element in collection or simultaneously change a collecton of elements. The new element's URI is assigned automatically and is returned by the operation. n/a n/a Get updated on collection changes (e.g. addition, removal of elements)
Element URI, such as /<service>/<resource>/<element> Retrieve a representation of the addressed member of the collection, expressed in an appropriate Internet media type. Update element. The updated element's URI is assigned automatically and is returned by the operation. Create new element in collection by sending the entire entity. The new element's URI is assigned accordingly. Delete the referred entity of the collection (or its attributes) Get updated on element changes (e.g. playing tracks offset on media player)

The HEAD(see RFC7231 section 4.3.2) method is identical to GET except that the server MUST NOT return a message-body in the response. The meta information contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining meta information about the entity implied by the request without transferring the entity-body itself. The method is often used to obtain information about expiry or existence, specially in cases where cross origin resource sharing (CORS) is needed.

DELETE

A client can either delete entire <element>s or just properties on the <element>s properties specified in the $fields query parameters. The mandatory fields id, name and uri can not be deleted. In case of a writable name property on an <element>, a client can reset the name to "" by POSTing.

Example element deletion

request:

DELETE /medialibrary/tracks/01aceb4b-002d-4060-a8eb-81868bf0bc37 HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: application/json
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok"
}
Example deletion of element properties artists and rating

request:

DELETE /medialibrary/tracks/01aceb4b-002d-4060-a8eb-81868bf0bc37?$fields=artists,rating HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: application/json
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok"
}
DELETE collection of elements

In order to allow simultaneous deletion of different elements, combining query parameters as known from a GET request with a DELETE query on <resource> is used. The idea behind is that a query will select a sub-collection (filter) of the <resource>s elements for which the DELETE action shall be applied. The request will only return a success if all deletions were successful, it will fail and return an appropriate code otherwise.

request:

DELETE /addressbook/contacts/?id=34a96130-4beb-47ed-b5e5-5a835907e0eb,2fb85b19-6eb6-49c1-87c5-dcb987aa23d6 HTTP/1.1
Host: 127.0.0.1:1337
Accept: application/json
Accept-Encoding: gzip,deflate

response:

HTTP/1.1 200 OK
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip


{
  "status" : "ok"
}
POST

Creating or changing an entity should always be done in the most condensed way (one request) possible, except the client explicitly wants to execute a sequence.

Creating e.g. an element is possible by providing no additional information in some cases and later in time modifying the newly created element to the desired state is possible. Nevertheless, providing all known information with the initial request ensures object integrity and minimum network traffic at the same time.

Changing an element's properties subsequently may cause unwanted effects like flickering in the maprenderer's case or jumps in the mediarenderer's case.

The protocol is based on the last-wins principle, i.e. last update arriving at the server determines the final state, disregarding the actual time gap between arrival of two subsequent requests.

POSTing a reference

Referenced objects are objects located in a resource. With a proper $expand option a service implementation might send the content of the referenced object when requested within a GET request, otherwise will only send its id, name and uri with the repsonse. Within a POST or PUT the convinience feature $expand (cmp. expansion) is not described, therefore it is always necessary to send the reference (the URI) within a POST or PUT. This means that only the link to the object is POSTed, not the entire element. The referenced object can thus not be changed together with the referencing object in an atomic interaction.

POST to change a collection of elements

In order to allow simultaneous updates on different elements, combining query parameters as known from a GET request with a POST query on <resource> is used. The idea behind is that a query will select a sub-collection (filter) of the <resource>s elements for which the POST action shall be applied. This is particularly useful for multi-element-transactions such as setting the color of multiple lights simultaneously. The request will only return a success if all changes were applied successfully, it will fail and return an appropriate code otherwise.

Light decoration example

Assuming a desire to change the light color of LEDs simultaneously for two decoration elements with the ids 0dde0b53-b862-44ec-bab2-045c049d220c and 1ca1b74-da9a-4933-baa5-1d6f0669dad0, a combined request would be sent instead of two subsequent ones.

request:

POST /dashboard/decoration/?id=0dde0b53-b862-44ec-bab2-045c049d220c,1ca1b74-da9a-4933-baa5-1d6f0669dad0 HTTP/1.1
Host: 127.0.0.1:1337
Accept: application/json
Accept-Encoding: gzip,deflate


{
  "color": "blue"
}

response:

HTTP/1.1 200 OK
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip


{
  "status" : "ok"
}

Caching

Cache control is established by using HTTP headers ETag and If-None-Match (cmp. RFC 7232-2.3). Mainly used for web cache validation, a client can make conditional requests based on the response. This allows caches to be more efficient, saves bandwidth and avoid cache-sync problems as a service does not need to send a full response if the content has not changed and a client can use the cached information. This also applies to service-service communication, especially when service A references element owned by service B. A will act as a client and use its own cached information if no changes where detected by B.

The basic sequence is: First: ask for an entity

request:

GET /cdn/images/foo.png HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: image/*
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
Content-Type: image/png
ETag: "641abf"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked
Content-Length: 15360


<base64encodedfile>

The client uses the content delivered with the response, stores the ETag info with the entity in its cache.

Second: later ask for the same entity together with If-None-Match

GET /cdn/images/foo.png HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
If-None-Match: "641abf"
Accept: image/*
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 304 Not Modified
Content-Type: image/png
ETag: "641abf"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 21:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked
Content-Length: 0

The client uses the information from its cache, 0 bytes of payload were transfered.

Addressing aspects

The system uses unified resource identifiers (uris) to reference to entities from and to each other. There are to types of uris, absolute (e.g. https://127.0.0.1:1337/cdn/images/image001.jpg) and relative (e.g. /cdn/images/image001.jpg). All uris are absolute by default, only if a service is referencing to information that is available under the same host and port, the uri will be relative. All clients have to send the HTTP Host header when accessing a service. The service will use the information provided by the HTTP Host header to build the absolute uris. This means that an external client will get uris based on the external IP address or hostname and port number of the service it is talking to.

Cross-origin resource sharing (CORS)

Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts, JavaScript, etc.) on a web page to be requested from another domain outside the domain from which the resource originated.

A web page may freely embed images, stylesheets, scripts, iframes, videos and some plugin content from any other domain. However embedded web fonts and AJAX(XMLHttpRequest) requests have traditionally been limited to accessing the same domain as the parent web page (as per the same-origin security policy). "Cross-domain" AJAX requests are forbidden by default because of their ability to perform advanced requests (POST, PUT, DELETE and other types of HTTP requests, along with specifying custom HTTP headers) that introduce many cross-site scripting security issues.

CORS defines a way in which a browser and server can interact to safely determine whether or not to allow the cross-origin request. It allows for more freedom and functionality than purely same-origin requests, but is more secure than simply allowing all cross-origin requests. It is a recommended standard of the W3C.

To allow clients (e.g. browsers) which are following these guidelines access to the service, the service must use CORS.

Source: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing Web Server requirements:

Request response content

The response is always of contentType: application/json (or application/vnd.viwi.v<major>.<minor>.<patch>+json) with charset=UTF-8, expect for binary content. Binary content uses regular HTTP headers according to its MIME-type.

POST requests only accept payload in the body, no form encoded (HEADER) data is accepted.

Graph interface

A fundamental concept of the API is the graph. All objects can get into relation with each other. The objects can be understood as nodes in a graph while the relations between them are the edges in this graph. This relation is expressed by referencing from one object to another in the object structure. For example, a 'media renderer' (node) may 'have' (edge) a 'mediaCollection' (node). The 'mediaCollection' (node) itself may 'contain' (edge) multiple items like a 'track' (node) or a 'video' (node).

Graph API

Response filtering

Reserved query parameters

An API-query or subscription can contain none, one or multiple parameters that represent the desire to filter a query. All parameters that do not point to an object property, but have a general nature, are prefixed with $. The following paragraphs make use of this rule and give examples.

A search can be performed on any resource, as either freetext search, parameter search or a combination of both. The parameter search is performed by using request parameters according to the properties of the resources object. The request parameters name has to resemble the objects property name (search key) and the value (search value) that is being looked for.

The character "%" (URL encoded: %25) is used as wildcard and can be used anywhere in the search value. Searches are only possible on properties which reference an XObject or primitive or arrays of those (i.e. searches are not possible on properties that are inlined objects). Searching for XObjects references are expressed by providing either the id or the name as the search value.

AND vs. OR queries

It is possible to combine multiple search keys in a single search in terms of an AND query (e.g. GET /api/v1/<service>/<resource>/?name=foo&type=bar). Combining multiple search values for the same search key in a single request is also supported in terms of an OR query (e.g. GET /api/v1/<service>/<resource>/?name=foo,bar) on search value level. To obtain elements by OR queries over multiple search keys, multiple queries are needed to be made and being combined by the client (in contrast to the pseudo standard using ';' on some web APIs). This also applies to $expand, $fields etc. GET /api/v1/<service>/<resource>/?$expand=foo,bar reads like 'please expand the properties whose name is either foo OR bar'. GET /api/v1/<service>/<resource>/?name=foo,bar reads like 'give me only those properties whose name is either foo OR bar'.

Example http://127.0.0.1:1337/medialibrary/tracks/?$q=5&artists=marshmello reads as 'fetch all tracks from the medialibrary that have any property set to 5 AND the the artists property contains 'marshmello'.

request:

GET /medialibrary/tracks/?rating=5 HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: application/json
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok",
  "data":[
    {
    "id" : "1ebe63c0-b528-11e3-a5e2-0800200c9a66",
    "name" : "me and my empty wallet",
    "duration":42,
    "artists":[
      {
        "id" : "bb3372f0-b527-11e3-a5e2-0800200c9a66",
        "name" : "ich",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b527-11e3-a5e2-0800200c9a66"
      }
    ],
    "albums":[
      {
        "id" : "5088aaa0-b528-11e3-a5e2-0800200c9a66",
        "name" : "where is my car",
        "uri" : "http://127.0.0.1:1337/medialibrary/albums/5088aaa0-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "image" : "http://127.0.0.1:1337/cdn/images/FT1hZIPBHX.png",
    "genres":[
      {
        "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
        "name" : "Rock",
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "rating":5,
    "uri" : "http://127.0.0.1:1337/medialibrary/tracks/1ebe63c0-b528-11e3-a5e2-0800200c9a66"
    }
  ]
}

Property search using freetext

A freetext search is performed by using the request parameter $q. The search value will be tested against any property value. Freetext search also supports wildcard character "%" (URL encoded: %25). Note: Inlined Objects are not handled as primitive types therefore searching within these with $q is not possible.

Example freetext

request:

GET /medialibrary/tracks/?$q=me%20and%20my%20empty%20wallet HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: application/json
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok",
  "data":[
    {
    "id" : "1ebe63c0-b528-11e3-a5e2-0800200c9a66",
    "name" : "me and my empty wallet",
    "duration":42,
    "artists":[
      {
        "id" : "bb3372f0-b527-11e3-a5e2-0800200c9a66",
        "name" : "ich",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b527-11e3-a5e2-0800200c9a66"
      }
    ],
    "albums":[
      {
        "id" : "5088aaa0-b528-11e3-a5e2-0800200c9a66",
        "name" : "where is my car",
        "uri" : "http://127.0.0.1:1337/medialibrary/albums/5088aaa0-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "image" : "http://127.0.0.1:1337/cdn/images/FT1hZIPBHX.gif",
    "genres":[
      {
        "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
        "name" : "Rock",
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "rating" : "5",
    "uri" : "http://127.0.0.1:1337/medialibrary/tracks/1ebe63c0-b528-11e3-a5e2-0800200c9a66"
    }
  ]
}
Referenced object

To search for a linked object, use its id or its name as search value with the search key according to the referencing property.

request:

GET /medialibrary/tracks/?artists=bb3372f0-b527-11e3-a5e2-0800200c9a66 HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: application/json
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok",
  "data":[
    {
    "id" : "1ebe63c0-b528-11e3-a5e2-0800200c9a66",
    "name" : "me and my empty wallet",
    "duration": 42,
    "artists": [
      {
        "id" : "bb3372f0-b527-11e3-a5e2-0800200c9a66",
        "name" : "ich",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b527-11e3-a5e2-0800200c9a66"
      }
    ],
    "albums": [
      {
        "id" : "5088aaa0-b528-11e3-a5e2-0800200c9a66",
        "name" : "where is my car",
        "uri" : "http://127.0.0.1:1337/medialibrary/albums/5088aaa0-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "image" : "http://127.0.0.1:1337/cdn/images/FT1hZIPBHX.png",
    "genres": [
      {
        "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
        "name" : "Rock",
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "rating": 5,
    "uri" : "http://127.0.0.1:1337/medialibrary/tracks/1ebe63c0-b528-11e3-a5e2-0800200c9a66"
    }
  ]
}

Fields

Every GET request is filterable by using the request parameter $fields. The parameter accepts a comma separated list of attribute names. For event subscriptions, the $fields parameter can be set as well. The fields defined by XObject are mandatory, i.e. these are always part of the response. If no ‘field’ parameter is given, the client gets an unfiltered response.

Sorting

Similar to searching, a generic parameter $sortby can be used for GET requests on resource level to describe sorting rules. Accommodate complex sorting requirements by letting the sort parameter take in a list of comma separated fields, each with a possible unary negative to imply descending sort order.

Every <resource> has a fixed default ordering, i.e. statelessness is given by defining a default sort behavior per <resources>, the $sortby parameter just over-rules the default behavior.

  http://127.0.0.1:1337/medialibrary/tracks/?$sortby=rating

or

  http://127.0.0.1:1337/medialibrary/tracks/?$sortby=-rating

Sorting can be combined with searching by adding both request parameters to the query:

  http://127.0.0.1:1337/medialibrary/tracks/?$sortby=rating&name=me%20an%my%20empty%20wallet
Ordering by primitives

Ordering by primitive value properties is possible alphabetical an numerical.

Ordering by XObjects

The default ordering by references to XObjects is defined by the mandatory name member of the object. A service might implement a different behavior that has to be specified per service, if different to the default.

Ordering by complex types

Complex types do not provide a default field for sorting. Therefore, sorting on complex types is not supported.

Ordering by array properties

Ordering by array of primitive or array of XObjects is solved by comparison similar to string comparison. Instead of comparing characters, array elements are compared. Arrays of inlined objects do not support sorting. Arrays of XObjects follow the default behavior defined for ordering by XObjects regarding comparison.

Ordering by array of primitive or array of XObjects is solved by comparison similar to string comparison. Instead of comparing characters, array elements are compared. Arrays of inlined objects do not support sorting. Arrays of XObjects follow the default behavior defined for ordering by XObjects regarding comparison.

[] < ["a","b","c","d"] < ["a","b","d"]

The expansion concept

Some of the services defined in this document deliver objects, that have reference to each other. The media library for example delivers a list of tracks, where every track can have a reference to an album. An album has a list of tracks itself. To avoid having too many circular references, the concept of resolve level is introduced. The default expansion level is 0 and might be 3 at maximum. Every data structure that potentially contains XObjects supports the expansion level request parameter $expand. A client can also request the expansion of certain object references by providing a list of property name on a request. If a query requests on one or more properties to be expanded and also on one certain level, e.g. $expand=foo,bar,3, only the level expansion should be used. The level expansion has priority over described property expansions.

Expansion does only work on JSON payload. If an object references to binary data, like images, expansion does NOT apply. Of course binary data can NOT be embedded into JSON payload and thus can not be expanded.

No expansion

request:

GET /medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66 HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: application/json
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok",
  "data": {
    "id" : "6149c270-b528-11e3-a5e2-0800200c9a66",
    "name" : "its in my pocket",
    "genres": [
      {
        "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
        "name" : "Rock",
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "81c816a0-b528-11e3-a5e2-0800200c9a66",
        "name" : "Pop",
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/81c816a0-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "artists": [
      {
        "id" : "bb3372f0-b527-11e3-a5e2-0800200c9a66",
        "name" : "ich",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b527-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "bb3372f0-b500-11e3-a5e2-0800200c9a66",
        "name" : "du",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b500-11e3-a5e2-0800200c9a66"
      }
    ],
    "duration": 42,
    "rating": 2,
    "tracks": [
      {
        "id" : "6ec6abc0-b528-11e3-a5e2-0800200c9a66",
        "name" : "coin",
        "uri" : "http://127.0.0.1:1337/medialibrary/tracks/6ec6abc0-b528-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "9df7f840-b528-11e3-a5e2-0800200c9a66",
        "name" : "wumpel",
        "uri" : "http://127.0.0.1:1337/medialibrary/tracks/9df7f840-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "discs": 2,
    "image" : "http://127.0.0.1:1337/cdn/images/image09720.png",
    "uri" : "http://127.0.0.1:1337/medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66"
  }
}

Single property expansion

A client might want to expand just a single property on a given element. To achieve single property expansion, a client adds an $expand parameter followed by a comma separated list of property names.

request:

GET /medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66?$expand=artists HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: application/json
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok",
  "data": {
    "id" : "6149c270-b528-11e3-a5e2-0800200c9a66",
    "name" : "its in my pocket",
    "genres": [
      {
        "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
        "name" : "Rock",
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "81c816a0-b528-11e3-a5e2-0800200c9a66",
        "name" : "Pop",
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/81c816a0-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "artists": [
      {
        "id" : "bb3372f0-b527-11e3-a5e2-0800200c9a66",
        "name" : "ich",
        "genres": [
          {
            "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
            "name" : "Rock",
            "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "rating": 5,
        "tracks": [
          {
            "id" : "1ebe63c0-b528-11e3-a5e2-0800200c9a66",
            "name" : "me and my empty wallet",
            "uri" : "http://127.0.0.1:1337/medialibrary/tracks/1ebe63c0-b528-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "9df7f840-b528-11e3-a5e2-0800200c9a66",
            "name" : "wumpel",
            "uri" : "http://127.0.0.1:1337/medialibrary/tracks/9df7f840-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "albums": [
          {
            "id" : "5088aaa0-b528-11e3-a5e2-0800200c9a66",
            "name" : "where is my car",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/5088aaa0-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "image" : "http://127.0.0.1:1337/cdn/images/image837943.jpg",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b527-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "bb3372f0-b500-11e3-a5e2-0800200c9a66",
        "name" : "du",
        "genres": [
          {
            "id" : "81c816a0-b528-11e3-a5e2-0800200c9a66",
            "name" : "Pop",
            "uri" : "http://127.0.0.1:1337/medialibrary/genres/81c816a0-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "rating": 4,
        "tracks": [
          {
            "id" : "6ec6abc0-b528-11e3-a5e2-0800200c9a66",
            "name" : "coin",
            "uri" : "http://127.0.0.1:1337/medialibrary/tracks/6ec6abc0-b528-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "9df7f840-b528-11e3-a5e2-0800200c9a66",
            "name" : "wumpel",
            "uri" : "http://127.0.0.1:1337/medialibrary/tracks/9df7f840-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "albums": [
          {
            "id" : "5088aaa0-b528-11e3-a5e2-0800200c9a66",
            "name" : "where is my car",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/5088aaa0-b528-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "6149c270-b528-11e3-a5e2-0800200c9a66",
            "name" : "its in my pocket",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "image" : ".dev.portrait2",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b500-11e3-a5e2-0800200c9a66"
      }
    ],
    "duration": 42,
    "rating": 3,
    "tracks": [
      {
        "id" : "6ec6abc0-b528-11e3-a5e2-0800200c9a66",
        "name" : "coin",
        "uri" : "http://127.0.0.1:1337/medialibrary/tracks/6ec6abc0-b528-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "9df7f840-b528-11e3-a5e2-0800200c9a66",
        "name" : "wumpel",
        "uri" : "http://127.0.0.1:1337/medialibrary/tracks/9df7f840-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "discs": 2,
    "image" : "http://127.0.0.1:1337/cdn/images/image834543.jpg",
    "uri" : "http://127.0.0.1:1337/medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66"
  }
}

level expansion

A client might want to expand all properties on a certain level for a given element. To achieve level expansion, a client adds an $expand parameter followed a number specifying the expansion level (0-3).

request:

GET /medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66?$expand=1 HTTP/1.1
Host: 127.0.0.1:1337
Connection: keep-alive
Accept: application/json
User-Agent: Chrome/34.0.1847.137 Safari/537.36
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok",
  "data": {
    "id" : "6149c270-b528-11e3-a5e2-0800200c9a66",
    "name" : "its in my pocket",
    "genres": [
      {
        "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
        "name" : "Rock",
        "rating": 3,
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "81c816a0-b528-11e3-a5e2-0800200c9a66",
        "name" : "Pop",
        "rating": 2,
        "uri" : "http://127.0.0.1:1337/medialibrary/genres/81c816a0-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "artists": [
      {
        "id" : "bb3372f0-b527-11e3-a5e2-0800200c9a66",
        "name" : "ich",
        "genres": [
          {
            "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
            "name" : "Rock",
            "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "rating": 5,
        "tracks": [
          {
            "id" : "1ebe63c0-b528-11e3-a5e2-0800200c9a66",
            "name" : "me and my empty wallet",
            "uri" : "http://127.0.0.1:1337/medialibrary/tracks/1ebe63c0-b528-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "9df7f840-b528-11e3-a5e2-0800200c9a66",
            "name" : "wumpel",
            "uri" : "http://127.0.0.1:1337/medialibrary/tracks/9df7f840-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "albums": [
          {
            "id" : "5088aaa0-b528-11e3-a5e2-0800200c9a66",
            "name" : "where is my car",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/5088aaa0-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "image" : "http://127.0.0.1:1337/cdn/images/image65476.jpg",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b527-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "bb3372f0-b500-11e3-a5e2-0800200c9a66",
        "name" : "du",
        "genres": [
          {
            "id" : "81c816a0-b528-11e3-a5e2-0800200c9a66",
            "name" : "Pop",
            "uri" : "http://127.0.0.1:1337/medialibrary/genres/81c816a0-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "rating": 4,
        "tracks": [
          {
            "id" : "6ec6abc0-b528-11e3-a5e2-0800200c9a66",
            "name" : "coin",
            "uri" : "http://127.0.0.1:1337/medialibrary/tracks/6ec6abc0-b528-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "9df7f840-b528-11e3-a5e2-0800200c9a66",
            "name" : "wumpel",
            "uri" : "http://127.0.0.1:1337/medialibrary/tracks/9df7f840-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "albums": [
          {
            "id" : "5088aaa0-b528-11e3-a5e2-0800200c9a66",
            "name" : "where is my car",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/5088aaa0-b528-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "6149c270-b528-11e3-a5e2-0800200c9a66",
            "name" : "its in my pocket",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "image" : "http://127.0.0.1:1337/cdn/images/image837943.jpg",
        "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b500-11e3-a5e2-0800200c9a66"
      }
    ],
    "duration": 42,
    "rating": 2,
    "tracks": [
      {
        "id" : "6ec6abc0-b528-11e3-a5e2-0800200c9a66",
        "name" : "coin",
        "duration": 8,
        "artists": [
          {
            "id" : "bb3372f0-b500-11e3-a5e2-0800200c9a66",
            "name" : "du",
            "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b500-11e3-a5e2-0800200c9a66"
          }
        ],
        "albums": [
          {
            "id" : "5088aaa0-b528-11e3-a5e2-0800200c9a66",
            "name" : "where is my car",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/5088aaa0-b528-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "6149c270-b528-11e3-a5e2-0800200c9a66",
            "name" : "its in my pocket",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "image" : "http://127.0.0.1:1337/cdn/images/We9Hv47YeG.jpg",
        "genres": [
          {
            "id" : "81c816a0-b528-11e3-a5e2-0800200c9a66",
            "name" : "Pop",
            "uri" : "http://127.0.0.1:1337/medialibrary/genres/81c816a0-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "disc": 1,
        "rating": 1,
        "uri" : "http://127.0.0.1:1337/medialibrary/tracks/6ec6abc0-b528-11e3-a5e2-0800200c9a66"
      },
      {
        "id" : "9df7f840-b528-11e3-a5e2-0800200c9a66",
        "name" : "wumpel",
        "duration": 13,
        "artists": [
          {
            "id" : "bb3372f0-b527-11e3-a5e2-0800200c9a66",
            "name" : "ich",
            "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b527-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "bb3372f0-b500-11e3-a5e2-0800200c9a66",
            "name" : "du",
            "uri" : "http://127.0.0.1:1337/medialibrary/artists/bb3372f0-b500-11e3-a5e2-0800200c9a66"
          }
        ],
        "albums": [
          {
            "id" : "6149c270-b528-11e3-a5e2-0800200c9a66",
            "name" : "its in my pocket",
            "uri" : "http://127.0.0.1:1337/medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "image" : "http://127.0.0.1:1337/cdn/images/ZYnb9UpJxU.png",
        "genres": [
          {
            "id" : "81c816a0-b528-11e3-a5e2-0800200c9a66",
            "name" : "Pop",
            "uri" : "http://127.0.0.1:1337/medialibrary/genres/81c816a0-b528-11e3-a5e2-0800200c9a66"
          },
          {
            "id" : "92884410-b528-11e3-a5e2-0800200c9a66",
            "name" : "Rock",
            "uri" : "http://127.0.0.1:1337/medialibrary/genres/92884410-b528-11e3-a5e2-0800200c9a66"
          }
        ],
        "disc": 1,
        "rating": 1,
        "uri" : "http://127.0.0.1:1337/medialibrary/tracks/9df7f840-b528-11e3-a5e2-0800200c9a66"
      }
    ],
    "discs": 2,
    "image" : "http://127.0.0.1:1337/cdn/images/af7643.jpg",
    "uri" : "http://127.0.0.1:1337/medialibrary/albums/6149c270-b528-11e3-a5e2-0800200c9a66"
  }
}

Paging

Every request is filterable by using the request parameter $offset and $limit. Using these parameters, a list response can be offset by $offset and limited to a total length of $limit. E.g. GET /tuner/stations/?$offset=5&$limit=10 returns a list of 10 elements in total, starting with the 6th (lists start with index 0) element, closing with the 15th element. Both parameters are applicable for requests and subscriptions. The response and event payload contains a paging object if there are multiple pages to let the client know the previous and next page of results (if applicable). If at least $offset or $limit are provided with the query the paging object is contained in the response.

$offset can be either an integer value or an uuid. E.g. GET /tuner/stations/?$offset=932e5b1e-1848-11e5-b60b-1697f925ec7b&$limit=10 returns a list of 10 elements in total, starting with the element 932e5b1e-1848-11e5-b60b-1697f925ec7b.

$limit can be any positive or negative integer. Positive values let the returned list start at $offset. Negative values will return a list of elements before the $offset, with $offset being just out of scope for the repsonse, i.e. the $offset will be behind the last element returned.

The PagingObject will hold all neccessary information within the responseObject.

Retrieve from the end of a list

To retrieve a list backwards a client might use a query with negative $limit AND negative $offset. The index -1 marks the last element of a list. E.g. GET /tuner/stations/?$offset=-1&$limit=-10 will return the last 10 last, $offset=-1 marks the last element.

Paging is not only available on resource level but also on nested lists, even though paging nested lists does not tell the client about the previous and next page. Paging nested lists is only possible together with $fields filtering by adding ($offset:<offset>,$limit:<limit>) to the expanded or filtered property name in the query string, link in /addressbook/contacts/?$fields=emails($offset:0,$limit:2).

Service initiated paging

In case of queries that can not be answered with a single response, a service sends a partial result with paging information attached. E.g. if a client queries /navigation/pois (without any filters), the result list might be too big to transfer, so the service will initiate the paging of the result itself, by sending a certain number of results and setting the paging properties accordingly.

Timestamping

All responses can carry an optional relative timestamp. The timestamp is defined as Integer and expresses the time difference in milliseconds between system boot and message creation. The regular interval is limited to 10 milliseconds so that valid values for timestamp always comply to

  timestamp mod 10 = 0

Publish-subscribe

In order to allow a server pushing information to a client, the WebSocket technology is used. Any client may register for events on the GET url of a query it is interested in to receive update by this push mechanism.

There is only one WebSocket connection established per client and server port. If a client tries to open a second WebSocket connection on the same server port, the server rejects with a 409 Conflict message. The ErrorObject contains the following message: Dude, the higlander principle applies.. I warned you.. The WebSockets endpoint is the same as the regular root uri (e.g. / or /api/v2/). The same port number as for regular http requests shall be used.

Note: WebSockets payload may get concatenated if two messages shall be sent within a frame of 50ms (according to W3C specification). Thus, payload objects always shall have a trailing ‘\n’. for client side separation.

Definition

An event is considered on change if two subsequent GET requests on the corresponding url would result in different responses on the first level ($expand=0) of the response object (<element> level) or list characteristics, i.e. if an element object is added or removed from a list of objects an on change event is fired. In case of an id based $offset, an on change is fired as well, if the PagingObject nested in the paging will change its content, e.g. if the offsetIndex would change due to list modifications outside the list scope a client subscribed to or if the total changes.

Note: This means, list subscriptions will not fire an event if the elements content changes, so if a client is interested in <element> level changes, dedicated subscriptions to each <element> of interest are needed

ATTENTION Subscriptions on levels other than <element> do only support expand level 0.

An event message is defined as object serialized to JSON that is transmitted via WebSocket. The term <event> specifies the event uri a client wants to subscribe to, unsubscribe from, receive or emit messages for.

The <event> follows the the similar syntax as a regular GET request

    /<service>/<resource>/<element>?<query-params>#<uniqueid-per-session>

The known parameters (e.g. filters, paging, etc.) applicable on GET requests could be applied to subscriptions on an <event> too. This approach is reasonable for elements as well as resources.

    /<service>/<resource>?<query-params>#<uniqueid-per-session>

While /<service>/<resource>/<element> describes the actual event uri, or /<service>/<resource>/ describes the resource name, the <uniqueid-per-session> parameter allows multiple subscription for the same event with different fields of interest, update rate or updatelimit on client side. Therefore an optional <uniqueid-per-session> is generated on client side. The server does not care about the <uniqueid-per-session> as it is pure client side information. The server must not alter or remove <uniqueid-per-session> from the event uri. The optional <query-params> section of the subscription contains the GET parameters, a regular GET query would have in polling mode.

There are two mandatory properties for every message sent via WebSocket that are type and uri (=event). The first describes the intention (e.g. ‘subscribe’) of the message sent, while the later identifies the endpoint itself.

Subscribe

To subscribe to an <event>, the client has to send the following JSON stringified object to the server:

{
  "type" : "subscribe",
  "event" : "/<service>/<resource>/<element>?<query-params>#<uniqueid-per-session>",
  "interval": <timeStepInMs>,
  "updatelimit": <timeStepInMs>,
  "authorization": <token>,
  "autosubscribe": <true|false>,
  "Accept": String
}

The fields a client is interested in and also the number of items defined by $offset and $limit parameters have to be send in the optional <query-params> section of the subscription. In case a client subscribes with $offset and $limit set, a list window is subscribed.

The response to a subscribe must follow the structure if it succeeded or trigger an error message if it fails:

{
  "type" : "subscribe",
  "event" : "/<service>/<resource>/<element>?<query-params>#<uniqueid-per-session>",
  "status" : "ok"
}

Filtering

The subscription takes an optional list of <attrName> strings named fields that specify the attributes the client wants to subscribe to.

Periodic

The optional interval attribute specifies the update frequency in milliseconds for periodic updates, while the optional updatelimit attribute specifies the maximum update rate in milliseconds for ‘on change’ notification. If interval is set, updatelimit is always overruled.

On change

If interval is not set, the notification interval is defined ‘on change’ and can be limited by specifying an updatelimit. If an ‘on change’ occours before updatelimit elapsed, an event will be sent as soon as updatelimit elapsed. If there are multiple changes before the next possible update, only the last one know state is sent after updatelimit elapsed.

Automatic subscription

If autosubscribe is set as true while requesting subscription on resource level, the server will additionally subscribe the client as listener for all elements as well. Furthermore, the server is responsible for maintaining subscriptions, i.e. removing subscriptions for vanished elements, and adding subscriptions for new elements. This is a convenience mechanism used to avoid traffic when a client needs to watch a list of elements.

In case a client subscribes with $offset and $limit set, only the elements of the requested list window are additionally subscribed.

Unsubscribe

To unsubscribe an event, the client has to send the following JSON-serialized object to the server, regardless of the query parameters?<query-params> used to subscribe:

{
  "type" : "unsubscribe",
  "event" : "/<service>/<resource>/<element>#<uniqueid-per-session>",
  "autosubscribe": <true|false>
}
Automatic unsubscription

If autosubscribe is set as true while requesting unsubscription on resource level, the server will additionally unsubscribe the client as listener for all elements as well.

In case a client unsubscribes with $offset and $limit set, only the elements of the requested list window are additionally unsubscribed.

reauthorize

In case of expiring access tokens (cmp. authorization), a subscription has to be re-authorizable. To reauthorize a subscription for an <event>, the client has to send the following JSON stringified object to the server:

{
  "type" : "reauthorize",
  "event" : "/<service>/<resource>/<element>?<query-params>#<uniqueid-per-session>",
  "authorization": <token>
}

The subscription parameters do not change with reauthorize. The client will receive the same fields, with the same rates as for the original subscription.

The response to a reauthorize must follow the structure if it succeeded or trigger an error message if it fails:

{
  "type" : "reauthorize",
  "event" : "/<service>/<resource>/<element>?<query-params>#<uniqueid-per-session>",
  "status" : "ok"
}

Data

For now, only the server is able to emit data events. The emitted data is expected to be a JSON formatted object:

{
  "type" : "data",
  "event" : "/<service>/<resource>/<element>?<query-params>#<uniqueid-per-session>",
  "data": <payload>,
  "timestamp": Integer,
  "Content-Type": String
}

or for subscriptions to collections

{
  "type" : "data",
  "event" : "/<service>/<resource>/?<query-params>#<uniqueid-per-session>",
  "data": <payload>,
  "paging" : <pagingObject>,
  "timestamp": Integer,
  "Content-Type": String
}

The <payload> is defined per event. The server manages the event distribution for all connected clients as defined above, i.e. parsing the event and transmitting it to all registered clients with the expected timing and filtering applied.

Error

If any error on the server side occurs an object with type ‘error’ is sent. The following JSON format shall be used:

{
  "type" : "error",
  "code": <identifier>,
  "event": /<service>/<resource>/<element>#<uniqueid-per-session>,
  "message": <errormessage>
}

The content in <errormessage> must be a describing string for the error, while code contains an identifier from the error codes table.

Note: The request leading to this error is NOT processed, i.e. neither subscription, nor unsubscription is processed!

In case of an error during subscription, an immediate response (e.g. code 403) is sent.

Element gone

Assuming that a subscription to a specific element exists (i.e. /<service>/<resource>/<element>), a 410 error shall be sent if the element vanishes for any reason.

Forbidden access

Assuming that a subscription to a specific element (i.e. /<service>/<resource>/<element>) or resource (i.e. /<service>/<resource>/) exists, a 403 error shall be sent if access is restricted for any reason (e.g. expired token or insufficient rights).

Multi client behavior

The system and therefore the API is designed to be multi client capable. If two clients send subsequent requests to the server, there is no guaranty that a client achieves the desired system state. E.g. Client A sends a request to tune to a frequency X, Client B may send a request to tune to frequency Y just before tuning to frequency X is finished and clients are notified. The final state will be ‘tuned to frequency Y’. The system works in a FIFO fashion, not considering latencies in communicating to different clients. To avoid race conditions, clients MUST not force the system to desired state by any kind of cheating like continuous requesting.

Initial data response

Immediately after sending the subscription response, a server shall send the first data message, to avoid race conditions by handing multiple clients. This leads to the messaging sequence shown below:

avoid race conditions

When using autosubscribe on resource level, the server will also send initial data messages for all elements that were automatically subscribed. The same holds true for new elements when automatic subscription is enabled.

Lifecycle

All subscription are strictly bound to the WebSocket connection, if the connection is lost or is terminated in a regular way, subscription become invalid. The client listening for events has to resubscribe.

Using a specific interface version

The Accept property describes which service interface version(s) is/are requested by the client (similar to the Accept-header on HTTP). The Content-Type property within a data statement (similar to the Content-Type-header) describes which version the statement is delivered in by the service.

If a version cannot be served by the service an error (with code 404) must be sent.

If no Accept property is sent with in the subscription message, it is up to the service which version will to deliver (cmp. Access versioned interface).

Examples:

Subscription:

{
  "type" : "subscribe",
  "event" : "/<service>/<resource>/<element>?<query-params>#<uniqueid-per-session>",
  "interval": <timeStepInMs>,
  "updatelimit": <timeStepInMs>,
  "Authorization": <token>,
  "Accept": <expected service version(s)>
}

Data:

{
  "type" : "data",
  "event" : "/<service>/<resource>/<element>?<query-params>#<uniqueid-per-session>",
  "data": <payload>,
  "timestamp": Integer,
  "Content-Type": <service version>
}

Error:

{
  "type" : "error",
  "code": 404,
  "event": /<service>/<resource>/<element>#<uniqueid-per-session>,
  "message": "Version not supported"
}

Error codes

The following regular HTTP error codes have a specific interpretation in the subscription context. The codes are be transferred within the JSON response, not on HTTP level for subscriptions.

code meaning
400 syntax error
403 access denied (token invalid or expired)
404 subscription uri invalid or incompatible version requested
503 maximum number of event subscriptions reached

Security

All information exchanged between any client and server in the network shall be secured by using https and wss protocols. The exploration feature (1.14) has to be limited to only the endpoints that a client is authorized to access (e.g. public endpoints).

There are different well known methods of authentication for web interfaces such as certificate exchange between client and server, username/password exchange or identifier/token transmission Authorization & Authentication. There will be a master client that has administrative rights, e.g. the main unit UI, that can can grant or deny the API access request based on the information provided with the request.

REST is not RPC

While RPC APIs expose procedures to perform the necessary steps to get from one state to another on the servers side, the REST API has to be understood in a OO (object oriented) way. The server is providing an interface to its objects/models. The client requests changes of the servers models/objects properties.

There is no mechanism defined to use RPC via the interfaces described in here for a reason. There is no need to, because the server models/objects are defined accordingly.

Exploration

The server provides access its own interfaces by an explore mechanism via GET.

The client can start exploring a server from its root (e.g. /or /api/v2/) endpoint, which returns a list of available services represented as serviceObjects..

request:

GET /api/v2/ HTTP/1.1
Host: 127.0.0.1:1337
Accept: application/json
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
ETag: "-32550834"
Content-Encoding: gzip
Date: Thu, 04 Dec 2015 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
"status": "ok",
"data": [
  <serviceObject>,
  ...,
  <serviceObject>
  ]
}

Querying the next level /<service>/ - please note the trailing /for a list query - returns a list of available resources of the service.

request:

GET /api/v2/<service>/ HTTP/1.1
Host: 127.0.0.1:1337
Accept: application/json
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
Date: Thu, 04 Dec 2015 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
"status": "ok",
"data": [
    {
      "id": <uui>,
      "name": <string>,
      "uri": <string(uri)>,
      "description": <detailled description of the resource>,
    },
    {
      ...
    }
  ]
}

Finally a resource will list all of its elements under /<service>/<resource>/ - find examples in the Expansion section.

Reserved keywords

The protocol knows dedicated keywords that are described below.

$accessrights

A query on service level that contains the $accessrights keyword (/<service>/$accessrights/) will return a description of the privileges the querying client has. Every entry describes one access right for the given path. The given response describes these privileges according to the given JSON schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "type": "object",
  "patternProperties": {
  "^\/[a-z0-9_]+(\/[A-Za-z0-9_]+)?\/$": {
    "type": "array",
    "items": {
     "type": "string",
      "enum": [
        "create",
        "read",
        "update",
        "delete"
        ]
      }
    }
  },
  "additionalProperties": false
}

Example: Request priveleges for the connecting client for media

request:

GET /media/$accessrights HTTP/1.1
Accept: application/json;q=0.8

response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8


{
    "/media/titles/": [ "create" ],
    "/media/artists/": ["create", "update"],
    "/media/": ["read"]
}
$id

A query that contains the $id keyword on any level except <element> will return a plain string (Content-Type: text/plain), the id. On <element> level is forbidden, because a trailing slash on <element> is needed to separate the $id keyword from the uri, but at the same time is forbidden. In the case of a request on root level, the id is a unique identifier for the system. On all other levels, the id of the item of interest will be return in the same plain way.

Note the Accept header being set to text/plain.

Example: Request the system id

request:

GET /$id HTTP/1.1
Host: 127.0.0.1:1337
Accept: text/plain

response:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8


d2cb3c92-ad8f-496f-b463-7a86973c677a

Example: Request a <service>s id

request:

GET /<service>/$id HTTP/1.1
Host: 127.0.0.1:1337
Accept: text/plain

response:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8


864d79f9-a1ea-405a-82a3-2a62c3745f25
$spec

A query that contains the $spec keyword like /<service>/$spec will return a schema of the resources interface according to the viwi schema specification (refer to the actual viwi object definition). The resource describes its own object shape this way.

Example: Request a service's schema

request:

GET /media/$spec HTTP/1.1
Host: 127.0.0.1:1337
Accept: application/json;q=0.8

response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8


{
    "name": "viwi.service.media",
    "description": "",
    "resources": {
        "collections": {
            "description": "The media collections can also be understood as media queues of the renderer. Media collections can only contain playable media like tracks, videos or pictures.",
            "model": {
                "name": "mediaCollectionObject",
                "resource": "media.collections",
                "properties": {
                    "id": {
                        "description": "collection id",
                        "type": "string",
                        "format": "uuid"
                    },
                    "name": {
                        "description": "collection name",
                        "type": "string"
                    },
                    "uri": {
                        "description": "object uri",
                        "type": "string",
                        "format": "uri"
                    }
                }
            },
            "endpoints": {
                "resource": {
                    "get": {
                        "parameters": {}
                    }
                },
                "element": {
                    "get": {
                        "parameters": {}
                    },
                    "delete": {
                        "parameters": {}
                    }
                }
            }
        },
        "renderers": {
            "description": "The media player renderer can be understood as the actual media player. It accepts media objects and applies actions like play, pause etc. to them.",
            "model": {
                "name": "rendererObject",
                "resource": "media.renderers",
                "properties": {
                    "id": {
                        "description": "renderer id",
                        "type": "string",
                        "format": "uuid"
                    },
                    "name": {
                        "description": "renderer name",
                        "type": "string"
                    },
                    "uri": {
                        "description": "object uri",
                        "type": "string",
                        "format": "uri"
                    },
                    "type": {
                        "description": "type of medium",
                        "type": "string",
                        "enum": [
                            "track",
                            "video",
                            "image"
                        ]
                    }
                }
            },
            "endpoints": {
                "resource": {
                    "get": {
                        "parameters": {}
                    }
                },
                "element": {
                    "get": {
                        "parameters": {}
                    }
                }
            },
            "systemTriggeredEvents": [
                "element",
                "resource"
            ]
        }
    }
}

Global JSON objects

General

General object structures are introduced below. All keys in request and response (including event payload) JSON have to be treated with regard of their casing (case sensitive), to avoid conflicts reading or writing values from or to a JSON object. E.g. the property name RouteCalculationProgress is different to routecalculationprogress.

XObject

Mandatory properties

The XObject is the general object used to derive all detailed objects from. Thus, every object exchanged through the API defined inhere has at least all XObject properties (mandatory). Optional properties for any object are only sent if applicable. A missing property is treated as being undefined and thus not processable for the client.

Property Description Type
id identifier uuid
name object name String
uri object uri URI

The uri of an XObject can be either relative or absolute. If the XObject refers to an element available on the same host and port, the uri has to be relative, else if the referred element is stored on a different host or port, the uri must be absolute, to keep the amount of redundant data and thus traffic at a minimum.

A service might define additional properties that are treated as mandatory, i.e. a service might define a property which is always present and if not must be treated as a failure of the server side implementation.

Types for XObject properties

The properties of a XObject can be of one of the following types:

Complex types

A complex type is a kind of named inline object that is not a XObject and therefore can not be addressed or referenced by it's own 'uri' and thus has no mandatory properties. It's comparable to structs in C programming language and may contain primitives, complex types, references to XObjects and arrays of those. Complex types can be reused in multiple XObject or other complex type definitions once specified.

StatusObject

A StatusObject is a general object without data (payload) attribute. For HTTP response code 201 (Created), the HTTP header ‘Location’ is the one of the new element which was created by the request (HTTP/1.1 status codes, 2012, RFC2616). Possible StatusObjects are shown below:

OK
{
  "status" : "ok"
}
Error
{
  "status" : "error",
  "message": <String>,
  "code": <identifier>
}

PagingObject

In case of paged data response, the paging property contains the previousand next pages a client can query. If the data does not have a previous or next page, the corresponding property will be undefined. Lists are linear, i.e. the first page has no predecessor, the last page has no successor.

In addition to those pointers, total and totalPages are defined to represent the total number of items available on the server and the number of totalPages resulting in chunking them in $limitelements per page. In case of unknown or uncountable items, the properties might be undefined. If the data does not have a previous or next page, the corresponding property will be undefined.

The $offset is returned with the repsonse in two ways, offsetId expresses the offset as number, offsetIndex expresses the offset as elements uuid, regardless of the type of the query, i.e. independently from wether an id or an numeric $offsetwas used in the request.

The general structure of an paging object is described as follows

{
  "offsetId": String,       // offset expressed as element id (uuid)
  "offset": Integer,        // offset expressed as index (numeric)
  "total": Integer,         // total number of elements
  "totalPages": Integer,    // total number of pages based on limit
  "previous": String,       // link to previous page
  "next": String,           // link to next page
  "limit": Integer          // the limit used
}

A query with ?$limit=0 (ref. Paging) gets the total number of items without actually retrieving a list of items in data, which will be an empty list.

request:

  GET /medialibrary/albums/?$limit=0 HTTP/1.1
  Host: 127.0.0.1:1337
  Accept: application/json
  Accept-Encoding: gzip,deflate
  Accept-Language: en-US,en;q=0.8,de;q=0.6

response:

HTTP/1.1 200 OK
Vary: Accept-Encoding
Content-Type: application/json; charset=utf-8
Date: Tue, 13 Jun 2014 19:47:27 GMT
Connection: keep-alive
Transfer-Encoding: chunked


{
  "status" : "ok",
  "data": [],
  "paging": {
    "total": 314159
  }
}

ResponseObject

All viwi objects are JSON (objects), that are encapsulated in a general response structure, except binary data such as images, videos etc.. This response structure is different between HTTP payload and Publish-Subscribe messages, while the data property in both cases is identical and is considered being the actual payload. If a request was successfully processed (HTTP 200), an object in the following notation is returned:

{
  "status" : "ok",
  "data": <Response>,
  "paging" : <PagingObject>,
  "timestamp": Integer
}

In case of paged data response, the paging property contains the PagingObject described above. Of course, paging only applies to /<service>/<resource>/ queries.

The <response> contains the actual JSON payload that is defined separately for each REST endpoint. If a request failed, status is set to error and ErrorObject is sent.

For responses on <service> level (e.g. /api/v1/myService - note the missing trailing / for an element like accesss), a ResponseObject contains an additional service field to represent itself as a serviceObject:

{
  "status" : "ok",
  "data": <Response>,
  "paging" : <pagingObject>,
  "timestamp": Integer,
  "service": <serviceObject>
}

binary content

There are endpoints like the cdn service that delivery binary data. This data is transmitted with regular HTTP headers according to its MIME-type. Subscription on binary data like images, video, certificates etc. is not possible. Binary data is considered being static.

Schema

All object properties make use JSON Schema definitions RFC7159. In addition to the formats defined in RFC7159 the date-time, date and time formats are derived but not identical to the definition in RFC3339. APIs may also use the following (proprietary) formats:

{
  "uuid": {
    "description": "unique identifier",
    "regex": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
    "example": "388d3a25-663b-11e3-949a-0800200c9a66"
  },
  "geoposition": {
    "description": "latitude followed by longitude and altitude in meters separated by ';'",
    "regex": "^[-+]?[0-9]*\.?[0-9]*;[-+]?[0-9]*\.?[0-9]*;[-+]?[0-9]*\.?[0-9]*$",
    "example": "37.772323;-122.214897;0"
  },
  "e164telephonenumber": {
    "description": "E.164 encoded Telephone number",
    "regex": "^\+?\d{4,23}$",
    "example": "+49536190"
  },
  "macaddress": {
    "description": "device mac address",
    "regex": "^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$",
    "example": "a3:3E:ff:e3:01:fe"
  },
  "rgba": {
    "description": "rgba color with alpha",
    "regex": "^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d(?:\.\d+)?))?\)$",
    "example": "rgba(25,9,19,0.4)"
  },
  "language": {
    "description": "HTTP1.1 compatible language tag",
    "regex": "^(\w{2})(-\w*)?$",
    "example": "en-gb"
  },
  "servicecategory": {
    "description": "plain string category name",
    "regex": "^[a-z]*$",
    "example": "car"
  },
  "duration": {
    "description": "iso8601 duration",
    "regex": "^P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$",
    "example": "P0003-06-04T12:30:05"
  },
  "temperatureUnit": {
    "description": "temperature measurement unit",
    "regex": "^[KCF]{1}$",
    "example": "C"
  },
  "distanceUnit": {
    "description": "distance unit",
    "regex": "^mi|km|yd|m|ft$",
    "example": "km"
  },
  "pressureUnit": {
    "description": "pressure measurement unit",
    "regex": "^bar|psi|kPa$",
    "example": "bar"
  },
  "volumeUnit": {
    "description": "volume measurement unit (ul = microliter, us.dry.gal = dry US gallon, us.liq.gal = liquid US gallon, imp.gal = imperial gallon)",
    "regex": "^l|ml|ul|us.liq.gal|us.dry.gal|imp.gal$",
    "example": "ml"
  },
  "consumptionUnit": {
    "description": "consumption measurement unit (fluid, gas, electric)",
    "regex": "^l/100km|l/h|km/l|mpgUS|mpgUK|g/h|kg/100km|km/kg|kg/h|m3/100km|km/m3|m3/h|miles/lbs|lbs/h|miles/yard3|yard3/h|miles/kg|miles/m3|kWh/100km|km/kWh|kWh/100miles|miles/kWh|kW|Ws|miles/gal_eqUS$",
    "example": "l/h"
  },
  "weightUnit": {
    "description": "consumption measurement unit (fluid, gas, electric)",
    "regex": "^t|kg|g|mg|lbs$",
    "example": "kg"
  },
  "speedUnit": {
    "description": "speed unit",
    "regex": "^kmh|mph|m/s$",
    "example": "kmh"
  },
  "timeFormat": {
    "description": "the time format for clocks",
    "regex": "^12h|24h$",
    "example": "12h"
  },
  "dateFormat": {
    "description": "date format",
    "regex": "^ddmmyyyy|mmddyyyy|yyyymmdd$",
    "example": "mmddyyyy"
  },
  "date-time": {
    "description": "date-time (based on RFC3339 5.6) detailed fraction",
    "regex": "^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?((Z|(\+|\-)\d{2}:\d{2}))$",
    "example": "2005-11-12T12:01:42.123+01:00"
  },
  "time": {
    "description": "time (based on RFC3339 5.6) detailed fraction",
    "regex": "^\d{2}:\d{2}:\d{2}(\.\d{3})?((Z|(\+|\-)\d{2}:\d{2}))$",
    "example": "12:01:42.123+01:00"
  },
  "date": {
    "description": "date (RFC3339 5.6)",
    "regex": "^\d{4}-\d{2}-\d{2}$",
    "example": "2005-11-12"
  },
  "uri": {
    "description": "uri with schema http(s) or w/o Authority",
    "regex": "^(https?):\/\/((?:[a-z0-9.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!&$'()*+,;=:@]|%[0-9A-F]{2})*)*)(?:\\?((?:[a-z0-9-._~!&$'()*+,;=:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!&$'()*+,;=:\/?@]|%[0-9A-F]{2})*))?$/i",
    "example": "https://www.example.com/service/resource/?$foo=bar#none"
  },
  "ical": {
    "description":"iCal format defined in https://tools.ietf.org/html/rfc5545",
    "regex": "^BEGIN:VCALENDAR[\S\s]+END:VCALENDAR$",
    "example": "BEGIN:VCALENDAR  END:VCALENDAR"
  },
  "point2D": {
    "description": "The coordinates of a point, where “0;0” is the upper left corner of the coordinate system.",
    "regex": "^\d{6};\d{6}$",
    "example": "300;200"
  },
  "rectangle2D": {
    "description": "The description of a rectangle. The first two values describe the upper left corner of the rectangle, where “0;0” is the upper left corner of the coordinate system. Value 3 describes the width, value 4 the height of the rectangle.",
    "regex": "^\d{6};\d{6};\d{6};\d{6}$",
    "example": "0;0;1280;640"
  }
}

It is also allowed to define a dedicated regular expression pattern as format on property definition level.

number vs integer

JSON does not allow non-numbers like NaN, nor does it make any distinction between integer and floating point. If the format integer is used in this document or the object definitions, the meaning is that a service will send and expect integer values. The parsing must be compatible with parsing a number into an integer, because there is no distinction during transfer (on the wire). If the format is defined as number, neither client nor service may parse it as an integer.

Language tags

The specified JSON format language allows the following tags:

LANGUAGE TAGS
[
"af",    //Afrikaans
"ar",    //Arabic
"ar-ae", //Arabic (U.A.E.)
"ar-bh", //Arabic (Bahrain)
"ar-dz", //Arabic (Algeria)
"ar-eg", //Arabic (Egypt)
"ar-iq", //Arabic (Iraq)
"ar-jo", //Arabic (Jordan)
"ar-kw", //Arabic (Kuwait)
"ar-lb", //Arabic (Lebanon)
"ar-ly", //Arabic (Libya)
"ar-ma", //Arabic (Morocco)
"ar-om", //Arabic (Oman)
"ar-qa", //Arabic (Qatar)
"ar-sa", //Arabic (Saudi Arabia)
"ar-sy", //Arabic (Syria)
"ar-tn", //Arabic (Tunisia)
"ar-ye", //Arabic (Yemen)
"az",    //Azerbaijanian
"be",    //Belarusian
"bg",    //Bulgarian
"bn",    //Bengali
"bs",    //Bosnian
"ca",    //Catalan
"cs",    //Czech
"da",    //Danish
"de",    //German
"de-at", //German (Austria)
"de-ch", //German (Switzerland)
"de-li", //German (Liechtenstein)
"de-lu", //German (Luxembourg)
"el",    //Greek
"en",    //English (Caribbean)
"en-au", //English (Australia)
"en-bz", //English (Belize)
"en-ca", //English (Canada)
"en-cn", //English (Chinese)
"en-gb", //English (United Kingdom)
"en-ie", //English (Ireland)
"en-ja", //English (Japanese)
"en-jm", //English (Jamaica)
"en-ko", //English (Korean)
"en-nz", //English (New Zealand)
"en-tt", //English (Trinidad)
"en-tw", //English (Taiwanese)
"en-us", //English (United States)
"en-za", //English (South Africa)
"es",    //Spanish
"es-ar", //Spanish (Argentina)
"es-bo", //Spanish (Bolivia)
"es-cl", //Spanish (Chile)
"es-co", //Spanish (Colombia)
"es-cr", //Spanish (Costa Rica)
"es-do", //Spanish (Dominican Republic)
"es-ec", //Spanish (Ecuador)
"es-gt", //Spanish (Guatemala)
"es-hn", //Spanish (Honduras)
"es-mx", //Spanish (Mexico)
"es-ni", //Spanish (Nicaragua)
"es-pa", //Spanish (Panama)
"es-pe", //Spanish (Peru)
"es-pr", //Spanish (Puerto Rico)
"es-py", //Spanish (Paraguay)
"es-sv", //Spanish (El Salvador)
"es-uy", //Spanish (Uruguay)
"es-ve", //Spanish (Venezuela)
"et",    //Estonian
"eu",    //Basque (Basque)
"fa",    //Farsi
"fi",    //Finnish
"fo",    //Faeroese
"fr",    //French
"fr-be", //French (Belgium)
"fr-ca", //French (Canada)
"fr-ch", //French (Switzerland)
"fr-lu", //French (Luxembourg)
"ga",    //Irish
"gd",    //Gaelic (Scotland)
"gl",    //Galician
"he",    //Hebrew
"hi",    //Hindi
"hr",    //Croatian
"hu",    //Hungarian
"hy",    //Armenian
"id",    //Indonesian
"is",    //Icelandic
"it",    //Italian
"it-ch", //Italian (Switzerland)
"ja",    //Japanese
"ji",    //Yiddish
"ka",    //Georgian
"ko",    //Korean
"lb",    //Luxembourgish
"lt",    //Lithuanian
"lv",    //Latvian
"mk",    //Macedonian (FYROM)
"mn",    //Mongolian
"ms",    //Malaysian
"mt",    //Maltese
"nl",    //Dutch
"nl-be", //Dutch (Belgium)
"no",    //Norwegian
"pl",    //Polish
"pt",    //Portuguese (Portugal)
"pt-br", //Portuguese (Brazil)
"pt-us", //Portuguese (US)
"rm",    //Rhaeto-Romanic
"ro",    //Romanian
"ro-mo", //Romanian (Republic of Moldova)
"ru",    //Russian
"ru-mo", //Russian (Republic of Moldova)
"sb",    //Sorbian
"sk",    //Slovak
"sl",    //Slovenian
"sq",    //Albanian
"sr",    //Serbian (Cyrillic)
"sr",    //Serbian (Latin)
"sv",    //Swedish
"sv-fi", //Swedish (Finland)
"sx",    //Sutu
"sz",    //Sami (Lappish)
"th",    //Thai
"tn",    //Tswana
"tr",    //Turkish
"ts",    //Tsonga
"uk",    //Ukrainian
"ur",    //Urdu
"uz",    //Uzbek
"ve",    //Venda
"vi",    //Vietnamese
"xh",    //Xhosa
"zh-cn", //Chinese (PRC)
"zh-hk", //Chinese (Hong Kong SAR)
"zh-sg", //Chinese (Singapore)
"zh-tw", //Chinese (Taiwan)
"zu"     //Zulu
]

Service category names

Services are categorized to let clients register and find similar services. Services are found via GETon root level, using the query parameter ?servicecategory=<name>, wherein <name> is one of the following category names:

[
  "addresses",        // address related information
  "auth",             // authentication and authorization
  "car",              // vehicle states, vehicle configurations
  "cdn",              // static content
  "charging",         // charging management
  "ev",               // electric vehicle information, states and configurations
  "search",           // dedicated searches that aggregate general searching
  "hybrid",           // HEV related information, states and configurations
  "map",              // digital map
  "maprendering",     // visual maps
  "inputdevices",     // input devices like optical sensors, hardkeys, capacitive keys
  "media",            // media related like rendering, queues(collections)
  "medialibrary",     // services hosting media items like tracks, albums or videos
  "mixer",            // audio management
  "navigation"        // route calculation and navigation general
  "communication",    // phone, messaging etc.
  "system",           // system related, like performance, window management or registration
  "radio",            // radio
  "speech",           // speech dialogs
  "swdl"              // all things related to software download
]

Language

Every request may contain an Accept-Language Header field, which allows the client to let the service know about the accepted languages (cmp. RFC2616#14.4).

In case of language depended content (e.g. city names), the service shall add the Content-Language Header to the response representing the actual language used to generate the content.

uuid generation

The viwi protocol uses uuids for element identification on different levels. A uuid is a 128bit number (dorucre https://en.wikipedia.org/wiki/Universally_unique_identifier). In its canonical form, a UUID is represented by 32 hexadecimal digits, displayed in five groups separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 alphanumeric characters and four hyphens). For example 5967e93f-40f9-4f39-893e-cc0da890db2e.

In the canonical representation, xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx, the most significant bits of N indicates the variant (depending on the variant; one, two, or three bits are used). The variant covered by the UUID specification is indicated by the two most significant bits of N being 1 0 (i.e., the hexadecimal N will always be 8, 9, A, or B).

UUID specification describes five versions. The four bits of M indicate the UUID version (i.e., the hexadecimal M will be either 1, 2, 3, 4, or 5).

Each version uses different information to generate the uuid and thus some may be more appropriate than the others in specific use cases. According to the specification, Version 1 UUIDs are generated from date-time and MAC address, Version 2 UUIDs are generated from group or user id and date-time, Version 3 & 5 produces deterministic UUIDs generated from a user-specified namespace and user-supplied data, and Version 4 is generated from pseudo-random number.

For the ids generated by services in the context of this protocol, the use of Version 4 and Version 5 a recommended.

Namespaces

Namespaces are, themselves, uuids. While the UUID specification gives example UUIDs for namespaces corresponding to fully qualified domain names (DNS), URLs, ISO OIDs, and X.500 DNs, any UUID can be used as the namespace when generating Version 5 uuids. Thus, nesting of namespaces is possible. A good practice is to use <service> and <resource> names for namespacing.

Version 4 (random)

Version 4 UUIDs use a scheme relying only on random number generations. This algorithm sets the version number (4 bits) as well as two reserved bits. All other bits (the remaining 122 bits) are set using a random or pseudorandom data source. Version 4 UUIDs have the form

  xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxx

where x is any hexadecimal digit and Y is one of 8, 9, A or B.

Version 5 (SHA-1 hash & namespace)

Version 5 UUIDs use a scheme with SHA-1 hashing. Note that the 160 bit SHA-1 hash is truncated to 128 bits to make the length work out.

uniqueness criteria

In order to generate deterministic uuids for entities that need to be recognized being identical on different systems, Version 5 shall be used. The characteristics used to determine similarity shall be used as inputs for the hashing function. An example for such an entity is a FM radio station, which is exactly characterized by its ecc and pi codes. The generation rule in this case would be

  uuid = sha1(namespace.toBytes()+eec.toBytes()+pi.toBytes()).substr(0,32);

Thus, the same broadcasting station will get the same uuid on every system. Transferring presets based on station ids or finding the correct station icon becomes very easy when using this approach.

The uniqueness criteria and thus the generation rule have to be defined on a per resource basis.

Interface design patterns

Settings

When ever a service needs general settings which also may include a reset to factory defaults, a settings resource is used. The individual settings are evaluated by their name and value/state.

Naturally delayed responses

There are cases, where a delayed and incremental results to a query can generally be expected. In case of Bluetooth device discovery for example a server side device scan has to be triggered. In these cases an immediate response to a GET request is neither possible nor practical for client side usage. The general pattern for these kind of applications/services should follow the principle :

A POST request on resource level is used to create a new object. The client can subscribe to on change events for the newly created object or poll (GET) it continuously. The newly created object will change over time until completion. This pattern is applicable to:

Server vs. client decision

Instances that receive direct user input, like buttons, graphical user interfaces or key word activation shall be clients rather than servers. Servers have to serve multiple clients at once.

Endpoint naming

In addition to utilizing the HTTP verbs appropriately, <service> and <resource> naming is arguably the most debated and most important concept to grasp when creating an understandable, easily leveraged web service API. When resources are named well, an API is intuitive and easy to use. Done poorly, that same API can feel klutzy and be difficult to use and understand. The following rules are to follow for a well defined API syntax:

Property and Enumeration naming

JSON properties are treated as keys in a map/dictionary. Thus, the property names are case-sensitive. In order to make the API more readable and consistent, property names and enumeration members shall be camelCased starting with a lower case character. Properties on the same object must not only be distinguishable by their casing, such a situation is considered a collision without technically being one.

Using inlined objects

Following the design rule that every XObject in the system can be referenced by its own uri, use of inline object definitions is discouraged, as it limits the API capabilities such as filtering, expansion, property access and subscription. If optimized data structures (like structs in OO languages) are needed, first ask, if some other component may have an interest in the complex structure in question at all. If this is not the case consider moving the inlined object properties up to be regular properties of the XObject. Following rules should be respected:

Virtual collection content

There are cases in which an endpoint needs to present a list of items to strictly follow REST principles and/or to allow a convenient access to its information despite a lack of knowledge about these items. In particular, cases where a list is dynamically appended or prepended based on a selection made in this list by the holding object itself. An example for these use cases is a media renderer for bluetooth media - the next bluetooth track is only available once playback of this track has already started.

The list will contain 0,1 or 2 virtual elements that are accessible but will just have a valid id and uri for as long as they are not pointed at. These elements will be placed at either end of the list. Once the element is referenced / pointed to by the client, an atomic action is executed:

Only on success, an update on the list and the reference is sent.

virtual elements

Using this approach would allow to conditionally show a previous/next button on client side depending on the relation between current item an List end/beginning. If an element follows, skip is available, if an element precedes, previous is available.

Note please be aware that this approach only works for unique list elements.Using an index instead of an item reference might be appropriate in some cases.

Memory Management

Utilizing VIWI as microservice architecture, there are multiple parties creating a lot of resource instances. This section describes a guideline regarding memory management.

Typically, the lion's share of instances will be created by services themselves. The lifecycle of an instance is managed by the service that created it. In other words: Each service is responsible for deploying an reasonable memory management strategy (e.g. garbage collection).

Limitation While there won't be a prescribed strategy how to implement this feature, a service must not remove an instance

On the other hand, if a client creates an instance, it should be aware of the footprint it is leaving and - if not longer required - remove the instance by deleting it contemporary.

A consequence following the limitation mentioned above: If a client removes an instance another client has subscribed to in the meantime, the hosting service should send a appropriate status message (e.g. 409 conflict) and keep the instance. The service is then responsible for managing this specific instance.

As a rule of thumb the party who is creating an instance is responsible for managing its lifecycle.

Passwords

It is common practice to transfer passwords an other secrets with the Authorization Header. User name and passwords are concatenated with a : character and finally base64 encoded, i.e. aUser:aPassw0rd becomes YVVzZXI6YVBhc3N3MHJk. To set a new password, this method is sufficient, for login too. For changing a password, the service needs to be sure that the user requesting the change is allowed to do so. The solution here is to concatenate the new password using a : and base64 encoding it, i.e. aUser:oldPassw0rd:newPassw0rd becomes YVVzZXI6b2xkUGFzc3cwcmQ6bmV3UGFzc3cwcmQ=.

API versioning

Building APIs requires more thought up front than agile developer might be used to. Paths, data structure, meta-data and extensibility are important and would be best considered up front. Once those decisions have been made, changes to the structure would potentially result in breaking compatibility to older versions of the API. Thus, API design and setting some rules for future consistency is important.

The versioning discussed in here only applies to the interface definition and does not tell anything about the service or client versions that implement the interface.

Semantic versioning

The versioning is based on SemVer 2.0.0. The rules defined there do apply with the following exception.

The multi digit version number, separated by "." consists of the three:

If the API is not released in a publicly available system (i.e. at development time) the patch level may be changed for any fixes, but not for feature additions.

The majorversion can also be considered as the generation which can be part of the root level uri seen in the document (e.g. /api/v1/).

Reasons for changing the major version

In case of a total rework of the API, a new major version has to be used. The following actions are considered being a total rework:

The change of the major version is an incompatible change (breaking change) of the service definition or its mechanisms.

The following activities are normally regarded as changes that lead to a new major version:

Reasons for changing the minor version

Changing the minor version indicates a breaking change or a noteworthy extension/enhancement of the API or general mechanisms.

The following reasons are being considered noteworthy:

Reasons for changing the patch version

The API includes a documentation.

The following changes to the API are considered as being compatible:

Deprecation

Entities on all level may be deprecated at some point in time. Properties, resources and services marked deprecated are meant to be deleted in a future release. As described above the addition of the deprecated flag increases the patch level.

The deprecation is anounced by inserting the string @deprecated: as the first characters of the description field of the entity in question. In case of a replacement, an additional marking @replacedby: is also strongly recommended to be placed into the description.

Example for a description: @deprecated: @replacedby: <new entity> <description>

Pre-release tagging

Any API version can be pre-released mainly for review purposes, following the pre-release rules of SemVer (§9 @ SemVer2.0.0). Pre-release versions are normally just used for review purposes.

Version ranges

To express a version range, a service might register with the SemVer2.0 range notation. The ^ and ~ are supported.

~ describes all versions higher and including the given patch level within the patch level. Example: ~1.4.4

^ describes all versions higher and including the given minor and patch level within the minor and patch level. Example: ^1.4.4

Per service versioning

Every <service> can work with its own API version, while the resources provided by this <service> have to be consistent with the <service> version.

There is NO package versioning that groups <service> versions into an overall API (global) version.

Every <service> will tell its supported versions by setting the versions property of the <service> object to a list of supported versions.

Accessing the interface (specific version)

To access a specific API version, the client has to provide the desired version indication in the Accept-Header. If no such information is provided, the latest available version is used.

To indicate the desired version, the vendor tree following http://tools.ietf.org/html/rfc4288#section-3.2 is used:

Accept: application/vnd.viwi.v<MAJOR>.<MINOR>.<PATCH>+json

To access the API version 1.4.2 the Accept Header would be:

Accept: application/vnd.viwi.v1.4.2+json

The response API version shall be at least at the requested API version patch level. The response API version must at least match the requested major or minor version.

If the requested API version cannot be offered by the service 404 will be returned, because the resource the client is requesting is not available in the requested API version.

Example

request:

GET /coolservice/coolresource/ HTTP/1.1
Host: 127.0.0.1:1337
Accept: application/vnd.viwi.v1.4.2+json

response (because 1.4.2 is not implemented by the service):

HTTP/1.1 404 Not Found

{
  "code": 404,
  "status": "error",
  "message": "Requested version not implemented"
}

Note: If no version is given with the Accept-header of a request the service can use any version the service can offer.

Service registry

A service will register itself by PUTing itself into the root level providing information about itself with the serviceObject. It can unregister itself by using DELETE.

A detailed explanation of the registration process can be found in an external ServiceRegistry_MSC_* document.

serviceObject

In order to register a service with the service registry, a special object called serviceObject, inherited from XObject, definition is available.

{
  "id": {
    "type": "string",
    "description": "service id (if sent with registration request, it will be ignored by the registry)",
    "format": "uuid"
  },
  "name": {
    "type": "string",
    "description": "service name"
  },
  "uri": {
    "type": "string",
    "description": "service uri (the desired <service_path> relative to the root of the service registry)",
    "format": "uri"
  },
  "description": {
    "type": "string",
    "description": "human readable description of the micro service"
  },
  "port": {
    "type": "integer",
    "description": "TCP port the service is running on"
  },
  "serviceCategories": {
    "type": "array",
    "items": {
      "type": "string",
      "description": "predefined key words"
    }
  },
  "versions":  {
    "type": "array",
    "items": {
      "type": "string"
      "description": "supported version in semVer notation"
    }
  }
}

serviceCategories lists the categories a service is assigned to. The supported API versions are listed as strings in semVer format (major.minor.patch) under version.

Registration

The service registers itself by providing the name it wants to be accessible under, relative to the root level uri (e.g. / or /api/v1).

The service registry generates an id (uuid) for the service and responds with the newly registered services id in the Location header like in the following example:

Location: https://<IP-address_of_service_registry>:<port_of_service_registry>/api/v1/<uuid_of_service>

or

Location: /<uuid_of_service>

Refer to version ranges to determine the necessary versions notation.

registration example:

request:

PUT /<service_path> HTTP/1.1
Host: 127.0.0.1:1337
Accept: application/json;q=0.8


<serviceObject>

response:

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: <URI of created service>


{
  "status": "ok"
}

Unregistration

Unregistration is only allowed on id level, i.e. by sending DELETE. DELETE requests on other accessing paths will result in a status code 400 Bad request

request:

DELETE /<service_uuid> HTTP/1.1
Host: 127.0.0.1:1337
Accept: application/json;q=0.8

response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8


{
  "status": "ok"
}

Only the entity which has registered the service is allowed to unregister the service. If the wrong user (client or service) tries to delete the service, the Service Registry will deny the DELETE request by sending the HTTP 403 Forbidden status code.

valid status codes

The Service Registry may respond with different status codes, depending on the requested action. The 30X range differs from regular viwi.

code content type meaning
200 content type request successful, regular content follows
201 content type creation succeeded, Location header set
300 list of potential redirect uris Multiple matches found, client has to decide
307 no payload, just redirect Exact match found

User Authentication and Authorization

In order to secure API access and to guarantee delivery of authorized content only, a token-based mechanism (cmp. OAuth 2.0 - RFC6749, JWT - RFC7519, JWT OAuth Profile - RFC7523) is used. The token mechanism can be understood as a dedicated valet key for valet services. This valet key often only allows the car to be driven a particular distance, and typically does not unlock the trunk and the glove box. OAuth is often referred to as a valet key for the web in that it grants an application access to protected data only for specific use cases (scopes) and often for a limited amount of time.

To integrate Tokens with HTTP requests, the Authorization header can be used. Similarly, WebSockets might need authorization, so the subscribe message (Publish Subscribe) contains the optional authorization property. Once an accessToken expires, the corresponding subscription(s) will send an error message with the error code 403. This is the indication that a new subscription with a valid token has to be setup or a reauthorization of the subscription with a new, valid accessToken is needed.

JWT-based access token

JSON Web Token (JWT) is an open standard RFC 7519 that defines a compact and self-contained way for transmitting authenticated information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can generally be signed using a public/private key pair using RSA or ECC.

For the application to the protocol, the token issuer (iss claim) has to provide access to its public key (sigurl claim) for token verification. At the same time, the public key references in the sigurl claim must be authenticated using appropriate PKI mechanisms. Alternatively, the public key used for verifying tokens may be exchanged (and updated) via a separate, authenticated channel.

The payload of a JWT-based access token contains all the required information like the scope (scp claim), avoiding the need to query the auth server more than once, thus they are called self-contained.

Token verification is crucial and MUST be implemented by all services handling sensitive information.

JWT tokens will be sent in the Authorization header, following with term Bearer and a space character.

The following example assumes eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UifQ.xuEv8qrfXu424LZk8bVgr9MQJUIrp1rHcPyZw_KSsds is the actual token:

  GET /<service>/<resource>/ HTTP/1.1
  Host: 127.0.0.1:1337
  Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UifQ.xuEv8qrfXu424LZk8bVgr9MQJUIrp1rHcPyZw_KSsds
Expiry

JWT-based access tokens are stateless keys to information that needs to be protected. Therefore a token has to be revokable. As the underlying concept of self-containing tokens does not allow revocation by a central component on a request by request basis, JWT-based access tokens shall generally expire quickly. This narrows down the possible time window for using a token without permission. After a token expires, the client is forced to obtain a fresh access token and can be denied access to the resource at this point in time. Additionally, if narrow time constraints must be met, the authorization server must offer a token whitelist resource - listing only token meta information - every service can subscribe to based on the JWT access token's identifier (jti claim).

Identification

Every JWT shall contain a jti claim in its header to allow for blacklisting, revocation notification and one time use.

Trust decisions

The contents of a JWT cannot be relied upon in a trust decision unless its contents have been cryptographically secured and bound to the context necessary for the trust decision. In particular, the key(s) used to sign and/or encrypt the JWT will typically be under the control of the party identified as the issuer of the JWT. There shall be no unsigned JWT-based access tokens in the viwi protocol.

Every JWT shall contain an iss (issuer) claim in its header to identify the principal that issued the token by an identifier known to the party attempting to verify the token.

Authorization Process & Security (recommendation)

Applying the token mechanisms discussed earlier in this chapter allows authorization based on these tokens. This allows a central token service to enforce a policy based on flexible scopes that may or may not group several resources into logical groups. In the following, we describe a reference authorization architecture based on JWTs. Using JWTs allows resource services to evaluate a request in isolation, without having to query a central component repeatedly. However, to achieve timely revocation of resource access without having the client obtain new tokens frequently, we propose to implement a revocation notification mechanism (see below).

Security by OSI Level

On a basic level, the system has to implement specific security related mechanisms on different layers of the network stack:

OSI Mechanism Protocol(s)
7 Application Token-based Authorization JWT/OAuth2
6 Presentation
5 Session Authentication + Encryption TLS
4 Transport Firewall TCP
3 Network Firewall IP
2 Data-Link
1 Physical

Firewall

Each server shall be assigned a port number, on demand or per runtime configuration, based on the general security requirements for the system. A firewall shall ONLY let the traffic pass it is configured for. All other ports shall be blocked by default.

Transport Layer Security

To secure the transfer of messages and tokens, transport layer security shall be established through the TLS protocol. For more detailed requirements on the required TLS version and cipher suites see Section #ref. Whether a concrete interface needs to be encrypted is subject of the security specification. However, all requests that include tokens MUST be made through an encrypted TLS channel. All necessary measures to allow TLS must be implemented, such as reliable time and secure certificate storage on the device using the certificate to authenticate. In case of mutual authentication, the client also needs to have appropriate key material.

Communication types

There are two different types of communication – public and confidential. The differentiation between these two types of communication is rather a project oriented than a security oriented. Communication is considered confidential when two trusted devices communicate with each other, e.g., the communication between two ECUs that are under full control (quality, security, testing etc.) of the vendor is considered confidential while the communication with at least one 3rd party device (meaning: not shipping with the vehicle) is considered public (e.g. smartphone, smartwatch,…).

An internal communication is between a service/server and a - in an OAuth sense - confidential confidential client (i.e., one that can be securely provisioned with cryptographic key material). Internal communication will usually rely on a mutually authenticated TLS channel to establish a client identity for issuing tokens. See OAuth 2.0 - RFC6749 Section 2.1 for details.

Conversely, public communication occurs between a service/server and a - in the OAuth sense - public client. A public client cannot have reliable key material and can therefore not be authenticated directly. Instead, the protocol relies on a trusted system to attest the authenticity of a user identity on that client. The client then forwards a proof of authentication (e.g. an Authorization Grant Token cf. RFC7523) from the trusted system to the in-vehicle authorization server to attest the clients identity.

Use Cases

For the different use case for API usage, different cipher suites are recommended:

Communication between Cipher Suite
vehicle internal clients with encryption, with token-based authentication (default - internal) TLS_PSK_WITH_AES_128_CBC_SHA256
vehicle internal clients with encryption, with TLS-based authentication (e.g. certificate) (mutual auth with certificate pinning) TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 with ECC: secp256r1 (prime256v1)
vehicle internal clients without encryption, with authentication (might be decided to be used on per-service basis) TLS_PSK_WITH_NULL_SHA256
vehicle and vehicle external clients with encryption, with token based authentication (e.g. certificate) (default - external) TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 with ECC: secp256r1 (prime256v1)
vehicle and vehicle external clients without encryption or without authentication -
Authorization Grant Token (AGT)

The AGT is a JWT (https://jwt.io, https://tools.ietf.org/html/rfc7519) Token, profiled according to RFC7523 (JWT OAuth 2.0 Client Authorization Grant profile). The token MUST be signed by a trusted entity. The VIWI auth server uses the AGT to establish a client user identity (if any). An AGT shall only be valid for presentation at a particular vehicle's authorization server and shall thus contain a vehicle identifier (e.g., the VIN). The VIWI auth server must have access to an authentic public key to verify the AGT signature. The VIWI auth server must also be able to obtain updated public key material from the backend for AGT validation.

AGTs shall only be valid for a limited amount of time (several days). Checking revocation is impractical for the VIWI auth server as limited connectivity would negatively impact the user experience when attempting to access resources in a vehicle. Instead, the client shall fetch new AGTs when the existing copies are about to expire. Under adversarial conditions, this would mean that an attacker can abuse a stolen device to access VIWI services in the name of the original user. This issue is however mitigated by smartphone locking mechanisms as well as the ability to remove a user identity from a vehicle remotely using other frontends. Exfiltrating an AGT from a device or the communication channel would require a much higher effort, if platform and transport security are intact.

The AGT shall contain at least the following claims:

Creating the token

The client requests the AGT from the backend, specifying a target audience (a VIN), alongside backend authentication information (e.g., a backend access token). The backend system shall create and sign the desired AGT only if the user authentication is or was previously successful. Additional authorization checks MAY occur in the backend system. The signature MUST only be applied when all checks are positive. After this step a signed AGT is available for presentation to the VIWI auth server.

Token Header

Claims in the AGT header are – find the claim key for JSON in parentheses:

Issuer (iss)

The iss (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a String or URI value. This claim is MANDATORY and allows the token consumer to select appropriate key material for validation.

Link to public key used for signature (sigurl)

The sigurl claim (signature url) contains an url pointing to the public key to be applied for token signature verification. This public key has to be assigned to and issued by a trusted source (i.e., the consumer of the token must be able to authenticate the public key obtained from the sigurl, for example by pinning a specific root or intermediate certificate for a certificate in the sigurl). This claim is OPTIONAL, as the VIWI auth server may obtain the necessary public key through another suitable (authenticated, updatable) channel.

Issuing date (iat)

The iat (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a unix timestamp and MUST be validated to be in the past. This claim is MANDATORY.

Expiration time (exp)

The exp (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the exp claim requires that a reliable time source is available and that the current date/time MUST be before the expiration date/time listed in the exp claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a unix timestamp. This claim is MANDATORY.

JWT identifier (jti)

The jti (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object. The jti claim can be used to prevent the JWT from being replayed. The jti value is a case-sensitive string, it SHALL be used to revoke a token. This claim is MANDATORY.

Token payload

Mandatory claims in the AGT payload – find the claim key for JSON in parentheses:

Username/Userid (uid)

The uid (User Id) claim provides a unique identifier for the user the client is requesting authorization for. Usually the uid claim will hold a system-wide user identifier. The uid claim is used by the viwi auth server to inform authorization decisions (e.g., whether an additional permission popup is necessary). The uid value is a case-sensitive string. The uid will be compared to the user logged in on the main unit. This claim is MANDATORY.

Audience (aud)

The aud (Audience) claim shall indicate which vehicle this AGT was issued for. Usually, the aud claim will contain a VIN. The token consumer MUST ensure that the audience string matches the expected value, i.e. that the token was issued for this vehicle.

Auth Server

The Access-Token issuer Service (auth server) is in charge of deciding over access requests submitted by a client. The client will request access to a given scope. A scope usually comprises access to one or more viwi resources. Services will validate an access token issued by the auth server, checking - among other things - that the token carries the expected scope. Below, relevant identities, scopes, policies and how tokens are issued are described.

The client request for an access token shall include the following information:

The auth server shall then decide whether or not to issue an access token based on this information, obtaining consent form the current vehicle user, if necessary. This process will be detailed in the following.

Types of Identities for Authorization decisions

In the context of use for VIWI, several identities need to be considered for authorization decisions and therefore be used by the authorization server. These identities are listed in the following.

Client Identity

The client requesting a service via VIWI may have an identity itself. In this context, the term client usually refers to the device a piece of software is running on. The identity of this client (device) is generally only ascertainable if cryptographic key material is securely available on the device. Hence, the OAuth 2.0 standard distinguishes confidential and public clients. A confidential client is more tightly controlled in terms of software as well as hardware and can thus be securely provisioned with key material, which can be used to run cryptographic authentication protocols. In turn, a public client (e.g., a smartphone app on a smartphone without a trust store for secure certificate storage) comes with no such guarantees and is assumed to be more easily compromised by an adversary.

For the purposes of VIWI, a confidential client shall be able to establish a mutually authenticated TLS channel with the auth server and resource service, providing a trusted identity, usually in the form of a certificate signed by a trusted authority. A confidential client can thus provide a proof of possession for a certain client identifier (client ID). After establishing a mutually authenticated channel with such a client, the server can be reasonably sure to be communicating with the same client as before. Confidential clients are therefore extended a client-level trust.

In contrast, a public client cannot provide any reliable identification information. Instead, public clients shall generate a unique identifier (UUID, client ID), for which it is very unlikely that another client chooses the same value. The client ID is thus only a weak identifier, which shall be used with care. In most cases, public clients shall provide a user identity that is asserted by a trusted backend on top of the client ID. It is this client user identity (that must have been previously authenticated between client, user and backend) which shall be the main source of a trust decision.

Client User Identity

The client user identity is a user identity that has been authenticated by a trusted backend systems towards the client. In the context of VIWI, the auth server may choose to make authorization decisions based on this identity, for example comparing the client user identity with the system user identity (see below). If these two identities match, the auth server might conclude that the user in the vehicle is the same as the user that is logged in on the client and therefore grant a request. For the purposes of this protocol, the client user identity shall be communicated using JWT authorization grant tokens (AGT, see above).

System User Identity

The system user identity corresponds to the identity of the user currently active inside the vehicle. It is assumed that this identity belongs to the user currently in charge of operating the vehicle (i.e., the driver). The system will usually have credentials to act as this user with respect to other backend systems (e.g., to access personalized navigation functionality). The system user identity shall be available to the auth server from the environment (i.e., the platform the auth server is running on, e.g., the main unit).

In the future, there may be situations where more than one user identity is considered active on the system (i.e., the vehicle is personalized for multiple users, including potential passengers). In this case, additional metadata about the system users (e.g., which screen belongs to their seat) may be required for making authorization decisions. This use case, however, is not covered by the current version of this specification.

Scopes

When requesting access to a set of resources, the client must specify a scope it wants to get access to. A scope describes a logical set of resources. This facilitates the specification of policies as well as describing the current state of authorization to users, if need be. Clients request access to a certain scope and a resource service expects an access token for a certain scope. Similarly, the authorization server is not concerned with particular resources but just with more abstract scopes. Using scopes allows the reuse of an access token for multiple services, which reduces the burden on client-side token management as well as requests against the auth server. Additionally, scopes also allow more fine-grained access control, where one resource might expect different scopes for operations of different sensitivity (e.g., read vs. write).

The same scope shall be assigned to services offering similar resources or services that shall be authorized as a group. There shall be two types of scopes: internal scopes are granted based on client properties (e.g. client identity or client type) without user interaction; conversely, user scopes may be granted through user interaction, i.e. a popup asking the user to decide whether or not a certain client should be able to use certain functionality. These user-scopes shall be defined in a way that facilitates understanding of a grant decision when being presented to a user, i.e., so that the set of resources grouped into the scope carries meaning to a user. Otherwise, users are not able to effectively exercise the control that is expected by the system to protect them from unwanted accesses.

Every client has to know which scopes to request access á priori, i.e. the scope usually are to be known at implementation time.

User Scopes

As a reference, the following set of scopes shall be used to govern access:

Scope Label Description
communicationRead Read communication-related information (address book, messages, phone)
communicationControl Control communication-related operations (send messages, make phone calls, etc.)
mediaStatusRead Read media status (tracks, playlists, etc.)
mediaControl Control media playback (e.g. skip, switch playlist, etc.)
navigationRead Read navigation state (e.g. position, speed, time to destination, etc.)
navigationControl Control navigation-related operation (new destination, select different route, etc.)
garageDoorRead Use garage door opener functionality (trigger existing slots)
garageDoorControl Manage garage door opener functionality (learn new garage codes, delect existing, etc.)
hvacRead Read heating, ventilation and AC information
hvacControl Change HVAC state (e.g. set temperature, enable seat heating, etc. )
carStatusRead Read state of vehicle (doors/windows open, oil temperature, etc.)
carConfigrurationRead Read car-related settings (i.e. read settings found in car menu)
carConfigrurationControl Modify car-related settings (i.e. modify settings found in car menu)
audioRead Read audio/sound management (sources, volume, balance, etc.)
audioControl Modify audio/sound settings (sources, volume, balance, etc.)

New services may require the definition of additional scopes. However, the list of scopes that can be presented to the user for grating authorization must remain as low as possible to avoid user confusion.

Internal Scopes

Internal scopes are scopes that can only be granted to clients based on their properties (client type, client id, client user type, etc.) and not by user interaction. The granularity of such scopes shall be flexible and up to the implementer as well as the desired control over internal ressource access. They can range from one single scope for all internal services and ressource to one scope per ressource operation.

Policies

Based on the requested scope, the client type, the client user identity, the current system user identity as well as previously granted permissions, the authorization server will search for a policy match. If a match is found, an access token matching the requested scope is issued.

A policy may additionally specify that user interaction is necessary to grant a certain scope. An example policy may state: "If the client user identity does not match the current system user identity, display a popup asking for permission to access scope X". This decision shall be remembered, in the context of the current system user identity, by the authorization server. It is important to note that each system user identity shall be able to grant or deny permissions individually. This means that a system user A may grant permission P to client user Z, while system user B denies P to Z. Implementations must choose to grant (or deny) requested scopes indefinitely (until revocation) or for a certain (potentially user-chosen) amount of time. This choice may be made on a scope-by-scope basis.

On the other hand, the authorization server shall be able to deny access to certain (more sensitive) resources under certain conditions. For example, only a viwi client in the body control ECU shall be able to update the status of the vehicle's doors and windows or all write access shall be denied to public clients.

Requesting Permission from the User

A policy may defer an authorization decision to the current system user, as only the user currently in control of the vehicle shall be able to decide whether or not a certain client shall be able to access certain functionality.

There shall only be a small number of scopes (likely less than 10) that a user can ever be asked to grant for a given client. The interface presented to the user for making the authorization decision shall be presented when the client in question is making the request to access a resource associated to the scope in question. The interface shall present a meaningful description of the extent of the scope to be granted. It shall also present a meaningful identifier for the client to be authorized. Lacking a reliable identifier, clients shall be allowed to provide a human-readable name when requesting access at the auth server.

It may be beneficial to allow the user making the authorization decision to specify additional constraints. The interface may choose to offer options to grant the permission until a certain time or event. Examples include until the permission is explicitly revoked, until the end of the current drive, for the next month, etc. Useful options should be determined from user research and must be enforced by the auth server.

All permission decisions shall be stored on a per-system-user basis in a consent service within the auth server. This only concerns permission decisions made by the user. Decisions made by the auth server by policy only shall not be documented in this table (as they should be stateless). The consent service shall store decisions as follows:

This information shall also be available to the system user in order to manage permissions.

Access token

An access token is needed to access the APIs of services. These tokens are issued as JWT based on RFC7523 (OAuth 2.0 profile for client authorization grants). The accessToken is issued to the client by the auth server after the policy checks described above have been successfully passed. The client then sends the token with each RESTful request and with each WebSocket subscription. The recipient/service MUST deny any API access without a valid token being provided. The token's validity MUST be checked for each request and before any WebSocket update. Additionally, after receiving a token for the first time, the service must subscribe to notifications of revocation for this token's identifier. The accessToken is a JWT-type token with a signature provided by the issuer. The token SHALL be as small as possible to avoid excessive communication overhead.

accessTokens are strictly bound to the system user identity who granted access (if any). This means that in a single user system they are only valid for the resources of the currently active system user identity.

AccessToken validity must begin with issuance. AccessTokens shall be issued for a certain vehicle (VIN) and system user identity (uid). Expiration may be indefinite (until revocation) or shorter, based on the configured policy or user decision. It is important to note that meaningful expiration requires a reliable source of date and time information (authenticated time).

If an accessToken is not valid anymore, all API access MUST be prohibited. On expiration or revocation, the client MUST request a new accessToken and satisfy the corresponding auth server policy. An appropriate status code MUST be sent as a response.

Revocation

The vehicle token server shall maintain a list of access token identifiers issued based on a certain user decision (see [Requesting Permission from the User] above). Once an access decision is revoked, the vehicle token server must notifiy all services consuming these access tokens of the revocation. It is the responsibility of the service to register for revocation notification of known token identifiers.

Access Token Content

Note It is left to the client application to decide whether to request a single accessToken with multiple scopes multiple access accessTokens with just a single scope or anything in between, dependeing on client needs. Client developers need to be aware that each accessToken request might throw a dedicated popup.

Access Token Signature

The accessToken will be signed with the issuers public certificate. Therefore, the public key of the issuer needs to be accessible by any client during runtime (cmp. /cdn/certificates, cdn service).

API access

The client MUST send the accessToken with each request and each WebSocket subscription. API access MUST be denied if no token is given or it is found to be invalid for any reason.

Access Token Validation

Services MUST be able to perform validation steps for access tokens in isolation, i.e. without having to contact the auth server on every presentation of an access token. Therefore, services shall be able to parse and validate JWT-based access tokens.

The following properties of an access token shall be verified in the following order:

Once these validation steps are completed successfully, the service shall use the token identifier to subscribe to a revocation status for this identifier at the auth server. As long as local validation is successful and no revocation notification is sent by the auth server, the service shall assume that the token is valid. Once a revocation notification is sent, the service shall cache this information for an appropriate amount of time.

Token sequence overview

The following diagram shows a brief overview of the authorization flow, omitting details like the actual HTTPS calls and WebSocket message syntax.

Authorization workflow