owned this note
owned this note
Published
Linked with GitHub
Identity Hub Request and Response Formats
===
Goals
---
This document hopes to revise and refine identity hub requests and response formats with a set of features that are needed for pretty much any identity hub use case.
The formats described in this document are taken from and/or inspired by the OData standard for building RESTful APIs. We also took ideas from REST API design best practices around the web, as well as popular web APIs that are considered best-in-industry. OData saves us from "having to worry about the various approaches to define request and response headers, status codes, HTTP methods, URL conventions, media types, value formats, query options, etc. OData also provides guidance for tracking changes, defining functions/actions for reusable procedures, and sending asynchronous/batch requests."
With that said, identity hubs are not REST APIs. For instance:
- REST APIs assumes there is a finite, fixed set of resources (data types/objects) that are exposed by a service and can be described in something like a CSDL schema. Instead, we want hubs to support any resource, where each resource is described using JSON-LD, Turtle, or another RDF serialization.
- REST APIs assume requests and responses always take place over HTTP. Instead, we want the interface to identity hubs to be valid over other transport protocols as well.
- REST API request and response metadata is communicated in HTTP headers, URL query parameters, and URL fragments. We are concerned that even over TLS secured HTTP connections, an attacker may be able to compromise the privacy of a message. Instead, we want to ensure all privacy-sensitive metadata is contained in the body of a request, which can be encrypted with the DID keys of the message recipient.
These differences force us to significantly depart from REST API design and the OData standard. As a result, we'll lose some advantages of the OData standard, such as automatic integration with OData clients and the use of client library generation tools.
We can still borrow and/or adapt many of OData's patterns and conventions to serve the needs of identity hubs. There's a lot to cover here, so we'll begin with the most pressing issues and develop this document over time to meet new requirements.
This document currently includes:
1. Base Schemas
2. Basic Request & Response Formats
3. Authentication
4. Authorization
5. Error Codes
6. Data Formats & Encodings
7. Filtering
8. Versioning
9. Paging
10. Extensibility
11. Serving Hubs over HTTP
12. Future Work
Each of these sections is a basic proposal for a v1 implementation of identity hubs. Each section will require more detail and richness to be added over time.
Base Schemas
---
A hub request is represented by a JSON object:
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "BaseRequest",
"iss": "did:example:123456", # the sender of the request
"aud": "did:example:some-hub", # the hub deployment that recieves the request
"sub": "did:example:abc123", # the DID that owns the target hub
}
```
A hub response is a JSON object as well:
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "BaseResponse",
"developer_message": "hello" # optional informational message
}
```
## Basic Requests & Responses
TODO: A blurb about how all objects in hubs are represented as a series of commits.
### Write Requests
To create an object, the client constructs and signs a commit, then sends it to a hub for storage:
#### Object creation request
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "WriteRequest",
"iss": "did:example:123456",
"aud": "did:example:some-hub",
"sub": "did:example:abc123",
# each commit is a JWT using JSON serialization
# the JWT contents are displayed below in decoded form
"commit": {
# commit metadata, included in signature computation
"protected": {
"interface": "Collections",
"context": "http://schema.org",
"type": "MusicPlaylist",
"operation": "create", # must be one of {create, update, delete}
"committed_at": "2018-10-24T18:39:10.10:00Z",
"commit_strategy": "basic", # indicates the structure of commits
"sub": "did:example:abc123", # the DID that the object is owned by
"kid": "did:example:123456#key-abc",
"meta": {
"name": "Sample playlist",
# "tags": []
}
}
# commit metadata, not included in signature computation
"header": {
# the rev is the unique ID for this commit, it is
# the hash of the protected & payload portions
"rev": "3a9de008f526d239...",
# the DID that created this commit. This must validate against the kid
"iss": "did:example:123456"
},
# commit contents, format specified by commit_strategy
"payload": {
"@context": "http://identity.foundation",
"@type": "MusicPlaylist",
"@id": "foo",
"name": "A playlist",
"tracks": []
},
# can be optionally used for non-repudiation of commits
"signature": "j3irpj90af992l..."
}
}
```
> The commit revision algorithm comes from the [storage and replication](https://hackmd.io/EesqN9cXQEKEeXamCR90BA?view#Commits) proposal.
#### Object creation response (success)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "WriteResponse",
"developer_message": "completely optional",
"revisions": ["3a9de008f526d239..."]
}
```
#### Object creation response (error)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ErrorResponse",
"error_code": "ABC123", # A standard value that can be handled in code
"error_url": "https://mydocumentation.com/errors/ABC123" # more info
"developer_message": "A helpful description for a developer",
"user_message": "Completely optional", # can be used for error messages
"target": "email", # the property in the request that caused error
"inner_error": { # a custom object specific to the hub provider
"request_id": "",
"timestamp": "",
"trace": "",
"custom-field": "",
}
}
```
To update an object, you send another commit that modifies the object:
#### Object update request
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "WriteRequest",
"iss": "did:example:123456",
"aud": "did:example:some-hub",
"sub": "did:example:abc123",
# each commit is a JWT using JSON serialization
# the JWT contents are displayed below in decoded form
"commit": {
"protected": {
"interface": "Collections",
"context": "http://schema.org",
"type": "MusicPlaylist",
"operation": "update", # must be update for an update request
"committed_at": "2018-10-24T18:39:10.10:00Z",
# the object this commit modifies
# each object's ID is the rev of its create commit
"object_id": "3a9de008f526d239...",
"commit_strategy": "basic",
"sub": "did:example:abc123",
"kid": "did:example:123456#key-abc",
"meta": {
"name": "Sample playlist",
# "tags": []
}
},
"header": {
"rev": "fe4fd3240ff1c68a...",
"iss": "did:example:123456"
},
"payload": {
"@context": "http://identity.foundation",
"@type": "MusicPlaylist",
"@id": "foo",
"name": "A modified playlist",
"tracks": []
},
"signature": "j3irpj90af992l..."
}
}
```
#### Object update response (success)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "WriteResponse",
"developer_message": "completely optional",
"revisions": [ # all known commits for this object
"fe4fd3240ff1c68a...",
"3a9de008f526d239..."
]
}
```
#### Object update response (error)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ErrorResponse",
"error_code": "ABC123",
"error_url": "https://mydocumentation.com/errors/ABC123",
"developer_message": "A helpful description for a developer",
"user_message": "Completely optional",
"target": "email",
"inner_error": {
"request_id": "",
"timestamp": "",
"trace": "",
"custom-field": "",
}
}
```
To delete an object from a hub entirely, you send another commit that deletes the object:
#### Object delete request
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "WriteRequest",
"iss": "did:example:123456",
"aud": "did:example:some-hub",
"sub": "did:example:abc123",
# each commit is a JWT using JSON serialization
# the JWT contents are displayed below in decoded form
"commit": {
"protected": {
"interface": "Collections",
"context": "http://schema.org",
"type": "MusicPlaylist",
"operation": "delete", # must be delete for a delete request
"committed_at": "2018-10-24T18:39:10.10:00Z",
"object_id": "3a9de008f526d239...", # the object this commit deletes
"commit_strategy": "basic",
"sub": "did:example:abc123",
"kid": "did:example:123456#key-abc"
}
"header": {
"rev": "cc2bd8f09bb88b5d...",
"iss": "did:example:123456"
},
# different commit strategies might use different values here
"payload": {
},
"signature": "j3irpj90af992l..."
}
}
```
#### Object delete response (success)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "WriteResponse",
"developer_message": "completely optional",
"revisons": [
"cc2bd8f09bb88b5d...",
"fe4fd3240ff1c68a...",
"3a9de008f526d239..."
]
}
```
#### Object delete response (error)
```jsonld=
# Error Response
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ErrorResponse",
"error_code": "ABC123",
"error_url": "https://mydocumentation.com/errors/ABC123",
"developer_message": "A helpful description for a developer",
"user_message": "Completely optional",
"target": "email",
"inner_error": {
"request_id": "",
"timestamp": "",
"trace": "",
"custom-field": "",
}
}
```
### Querying objects
To read data from a hub, a client will likely begin by querying the available objects. The `Object/Query` request accepts parameters for searching for available objects in a user's hub.
#### Object query request
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ObjectQueryRequest",
"iss": "did:example:123456",
"aud": "did:example:some-hub",
"sub": "did:example:abc123",
"query": {
"interface": "Collections",
"context": "http://schema.org",
"type": "MusicPlaylist",
# Optional
"object_id": ["3a9de008f526d239..", "a8f3e7..."]
}
}
```
#### Object query response (success)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ObjectQueryResponse",
"developer_message": "completely optional",
"objects": [
{
# object metadata
"interface": "Collections",
"context": "http://schema.org",
"type": "MusicPlaylist",
"id": "3a9de008f526d239...",
"created_by": "did:example:abc123",
"created_at": "2018-10-24T18:39:10.10:00Z",
"[other metadata fields]": "from storage specification",
"sub": "did:example:abc123",
"commit_strategy": "basic",
},
# ...more objects
]
}
```
#### Object query response (error)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ErrorResponse",
"developer_message": "A helpful description for a developer",
"error_code": "ABC123",
"error_url": "https://mydocumentation.com/errors/ABC123"
"user_message": "Completely optional",
"target": "email",
"inner_error": {
"request_id": "",
"timestamp": "",
"trace": "",
"[custom-field]": "",
}
}
```
### Reading a single object
Once a specific object of interest has been identified, you can submit a request to read all of the commits for that object. The client can then apply its commit strategy logic to produce the current state of the object based on the commits.
Currently the only supported fields in the `query` object are `object_id` (to retrieve all commits for an object) and `revisions` (to retrieve a specific list of revisions). Further options will be added in the future - see the Filtering section below.
The `fields` object allows the query to return only a subset of information. This is useful for discovering new commits to an object which has been synced previously.
#### Single object read request
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "CommitQueryRequest",
"iss": "did:example:123456",
"aud": "did:example:some-hub",
"sub": "did:example:abc123",
"query": {
"object_id": ["3a9de008f526d239..."],
"revision": ["abc", "def", ...]
},
"fields": ["rev"]
}
```
#### Single object read response (success)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "CommitQueryResponse",
"developer_message": "completely optional",
"commits": [
{
"protected": {
"interface": "Collections",
"context": "http://schema.org",
"type": "MusicPlaylist",
"operation": "create",
"committed_at": "2018-10-24T18:39:10.10:00Z",
"commit_strategy": "basic",
"sub": "did:example:abc123",
"kid": "did:example:123456#key-abc",
"meta": {
"name": "Sample playlist",
# "tags": []
}
},
"header": {
"rev": "3a9de008f526d239...",
"iss": "did:example:123456"
},
"payload": {
"@context": "http://identity.foundation",
"@type": "MusicPlaylist",
"@id": "foo",
"name": "A playlist",
"tracks": []
},
"signature": "j3irpj90af992l..."
},
{
"protected": {
"context": "http://schema.org",
"type": "MusicPlaylist",
"operation": "update",
"committed_at": "2018-10-24T18:39:10.10:00Z",
"object_id": "3a9de008f526d239...",
"commit_strategy": "basic",
"sub": "did:example:abc123",
"kid": "did:example:123456#key-abc",
"meta": {
"name": "Sample playlist",
# "tags": []
}
}
"header": {
"rev": "fe4fd3240ff1c68a...",
"iss": "did:example:123456"
},
"payload": {
"@context": "http://identity.foundation",
"@type": "MusicPlaylist",
"@id": "foo",
"name": "A modified playlist",
"tracks": []
},
"signature": "j3irpj90af992l..."
},
# ...
},
# pagination fields here
}
```
#### Single object read response (error)
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ErrorResponse",
"error_code": "ABC123",
"error_url": "https://mydocumentation.com/errors/ABC123"
"developer_message": "A helpful description for a developer",
"user_message": "Completely optional",
"target": "email",
"inner_error": {
"request_id": "",
"timestamp": "",
"trace": "",
"custom-field": "",
}
}
```
> [name=Danny Strockis] In the future we may consider introducing a "partition" concept, where we ask the client to send Commits/Read requests to each partition individually, to make it easier for hub implementations to return all commits.
Authentication
---
All requests and responses above are encoded into a JWE format that is both signed and encrypted by the sender using the DID keys of the hub and the client. This allows the hub to authenticate the client and secure any messages exchanged. Documentation on hub authentication [is available here](https://github.com/decentralized-identity/did-auth-jose/blob/master/docs/Authentication.md).
Authorization
---
Client authorization to restrict access to various API calls and data in a hub is described in full [in this authorization document](https://hackmd.io/j_hVcMyNRAmDrTN2E9gUIw).
Error Codes
---
When apps read and write data from identity hubs, they need to be able to support any hub provider the user chooses. Hub implementations should therefore return a finite and consistent set of error cases that can be handled by developers.
The table below enumerates a set of errors that hub implementations can return. Hub providers are able to add their own custom, non-standard error codes and debug information in the `inner_error` object.
Our design principles for introducing new error codes are as follows:
- We strive to have as few unique error codes as possible.
- Apps are expected to react differently to each error code. So new error codes should only be introduced when a developer needs to handle an error case differently from other error cases.
- No app should ever need to parse the `developer_message`, `user_message`, or `inner_error` fields to handle an error case.
- The `developer_message`, `user_message`, and `inner_error` fields are provided to assist in debugging.
| Error Code | Description |
| -------- | -------- |
| `bad_request` | The request could not be completed because it was malformed. Cases include:<br/>- the request JWE could not be decrypted<br/>- the response JWE could not encrypted<br/>- a required property was missing<br/>- an invalid value was provided in request<br/>|
| `authentication_failed` | The client could not be authenticated, becuase the request JWS could not be verified using the DID keys of the DID provided in the `iss` property. |
| `permissions_required` | The request is denied because the DID in the `iss` field has not be granted sufficient permission to perform the operation. |
| `not_found` | The resource requested could not be found in the hub. |
| `too_many_requests` | The client is being throttled, and should wait before retrying any requests. |
| `server_error` | An unexpected or unhandled error occurred on the hub. |
| `not_implemented` | The operation requested includes a standard hub feature that is not supported by this implementation of the hub. |
| `service_unavailable` | The hub service is temporarily not available and reqeusts should not be retried. |
| `temporarily_unavailable` | The hub service experienced a transient error and the request should be retried at a later time. |
We'll add more error cases over time as we discover cases that need to be handled by client applications.
Data Formats & Encodings
---
At this time, the only data format supported by hubs is JSON objects. JSON objects are encapsulated into JWSs and JWEs according to the [Authentication](#Authentication) section. Over HTTP, requests use the media types described in the [Serving requests over HTTP](#Serving-hub-requests-over-HTTP). Images, videos, streams, and other MIME types are not currently supported.
Filtering
---
Read requests for multiple resources can include additional criteria that scope down the set of resources that should be returned:
```jsonld=
Request
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ObjectQueryRequest",
"iss": "did:example:123456",
"aud": "did:example:some-hub",
"sub": "did:example:abc123",
"interface": "Collections",
"query": {
"context": "http://schema.org",
"type": "MusicPlaylist",
"filters": [
{
"type": "eq",
"field": "{metadata-property-name}",
"value": "{value}"
}
]
}
}
```
Currently filtering is only supported via "shortcut" properties directly on the `query` field. These properties allow filtering based on specific values of known static object properties.
In the future, we plan to support more advanced filtering via an array of `filters` objects in the `query` field.
Currently the only filter supported by identity hubs is the `eq` filter which works against a single property of an object or its metadata. Logical combinations like `and` and `or` are not supported at this time. The format and meaning of the filter string value is taken directly from the [OData v4.0 specifications](http://docs.oasis-open.org/odata/odata/v4.01/cs01/part2-url-conventions/odata-v4.01-cs01-part2-url-conventions.html#_Toc505773218).
> TODO: rewrite the paragraph on filters
Versioning
---
We expect that the format for hub requests and responses will evolve over time to support new developer scenarios and user requirements. The hub request/response standard will always strive to introduce changes in a way that does not break existing clients, and remains compatible with deployments of identity hubs on previous versions. With that said, the hub standard needs a mechanism that allows breaking changes to occur if absolutely necessary.
Hub API versioning will be implemented using semantic versioning in the URI of `https://schema.identity.foundation` schema, as displayed below. This implies that all objects described by this schema will need to be revised together - the `ReadRequest`, `WriteRequest`, `BaseRequest`, `BaseResponse`, `MultiObjectResponse`, `ErrorResponse`, and so forth. It also includes the format of the JWT, JWS, and JWEs that are used to encapsulate hub requests and responses. Any change to the JWE version should be considered a breaking change to the hub API.
WriteRequest
```jsonld=
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "BaseRequest",
"iss": "did:example:123456",
"aud": "did:example:some-hub",
"sub": "did:example:abc123",
}
```
Minor version increments should contain only non-breaking changes such as property additions and other object extensions. Any breaking changes, such as property renames, object restructuring, or data type modifications must only occur in major version increments.
Introducing a new major version is a radical change. Because of the decentralized nature of the standard, a new version will take significant amount of time to be implemented and adopted by all hub deployments and client applications. Therefore major versions should be supported by hub deployments for a long period of time, on the order of years. Introducing a new major version is not a decision to be taken lightly.
Hub API versioning does not pertain to the the versions of the objects stored by hubs that are described outside of the `https://schema.identity.foundation` schema. These objects can implement their own versioning strategies in accordance with JSON-LD or whichever data format they choose to adhere to.
As new versions get introduced, different hub deployments may adopt/deprecate versions at different rates. We therefore need a way for a hub to declare to clients which versions of the hub request/response APIs it supports. As a refresher, here is the (unmodified) format of the DID document for an identity hub provider (**not** the DID document for a user):
```jsonld
// Partial identity hub DID Document:
...
"service": [{
"type": "IdentityHub",
"publicKey": "did:btcs:456#key-1",
"serviceEndpoint": {
"@context": "https://schema.identity.foundation/1.0/hub",
"@type": "HostServiceEndpoint",
"locations": ["https://hub.azure.com/"]
}
}]
...
```
To declare supported versions, hubs can use a `location` value with the suffix `/.well-known/hub-configuration`:
```jsonld
// Partial identity hub DID Document:
...
"service": [{
"type": "IdentityHub",
"publicKey": "did:btcs:456#key-1",
"serviceEndpoint": {
"@context": "https://schema.identity.foundation/1.0/hub",
"@type": "HostServiceEndpoint",
"locations": ["https://hub.azure.com/.well-known/hub-configuration"]
}
}]
...
```
When clients see this suffix, they should fetch a simple JSON "configuraiton" document at the URL. We've chosen to host this hub configuration information in a dedicated document (rather than directly in the DID document) in an effort to keep the size of the DID document at a minimum.
The configuration document will take the following format:
```jsonld=
{
"@context": "https://schema.identity.foundation/1.0/hub",
"@type": "IdentityHubConfiguration",
"endpoints": [
{
"@context": "http://schema.identity.foundation/0.1/hub",
"@type": "InterfaceMap",
"interfaces": {
...
}
},
{
"@context": "http://schema.identity.foundation/1.4/hub",
"@type": "InterfaceMap",
"interfaces": {
...
}
}],
"signature": {
"kid": "did:btcs:456#key-1",
"value": "jv9323lavjsdav0d9ada2...",
"timestamp": "2018-10-24T18:39:10.10:00Z"
}
}
```
Each entry in the `values` JSON array indicates a protocol version that is supported. The version is indicated in the `@context` value, which references a specific hub request/response API format. A discovery document should contain a maximum of one entry per major version.
By parsing a hub configuration document, a client who does not support a hub's version or interfaces will fail faster, during parsing, not during a request.
The configuration document can be optionally signed using one of the hub's DID keys so that its integrity can be verified. Clients may, but do not need to, throw an error if a signature is absent.
The rest of the content of this discovery document is described in the sections below.
Paging
---
An identity hub may use pagenation in `MultiObjectResponse`s. Identity Hubs may define a maximum page size; if the returned results exceeds this size, an `skip_token` key value is included. The `skip_token` value should be treated as opaque by the client.
A paged result from a read reques:t
```jsonld=
Success Response
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ObjectQueryResponse", # multiple results return pagination
"developer_message": "completely optional",
"objects": [
{
# object metadata
"context": "http://schema.org",
"type": "MusicPlaylist",
"id": "3a9de008f526d239...",
"created_at": "2018-10-24T18:39:10.10:00Z",
"[other metadata fields]": "from storage specification",
"sub": "did:example:abc123",
"commit_strategy": "basic",
},
...
],
"skip_token": "ajfl43241nnn1p;u9390",
}
```
To use a `skip_token` key value to get additional results, include the `skip_token` key and key value in the `query` property. If a response does not contain a `skip_token`, then there are no more results for the client to fetch from the hub.
A read request to a hub's collection interface that uses the `skip_token` property:
```jsonld=
Request
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ObjectQueryRequest",
"iss": "did:example:123456",
"aud": "did:example:some-hub",
"sub": "did:example:abc123",
"interface": "Collections", # one of {collections, actions, ...}
"query": {
"context": "http://schema.org",
"type": "MusicPlaylist",
"skip_token": "ajfl43241nnn1p;u9390"
}
}
```
> [name=Logan Girvin] Context and Type seem excessive and could lead to improper store implementations (for instance changing the context and type while including the skip token could result in missed or improper results if the skip token is not checked against the query.)
Extensibility
---
Hub implementations can choose to add additional request and response features that are not part of the standard as needed. In accordance with JSON-LD, clients should ignore any additional properties present in objects that are not specified as part of the schema of `schema.idenity.foundation` objects. For instance, if a hub implementation returns the following response:
```jsonld=
Success Response
{
"@context": "https://schema.identity.foundation/0.1",
"@type": "ObjectQueryResponse",
"developer_message": "completely optional",
"objects": [
{
"id": "5f6a8a3c-d22c-43f3-9984-a27ce0557185",
"created_at": "2018-10-24T18:39:10.10:00Z",
"[other metadata fields]": "from storage specification",
"commit_strategy": "basic",
"custom-field": {
"foo": "bar"
}
}
]
}
```
A client programmed against the standard hub API should not fail. Similarly, if a client includes a non-standard property in the request, a hub implementation should not fail the request.
Serving hub requests over HTTP
---
While hubs can run in different environments, our first implementation will be a web service exposed over HTTP (and associated client SDKs). This section details how requests and responses can be submitted to such a hub service.
To make life easier for large scale hub deployments, we'll first add some richness to the discovery document outlined in the Versioning section above. To communicate with a hub, a client must get the endpoint(s) for a hub from the hub's discovery document as follows:
```jsonld=
{
"@context": "https://schema.identity.foundation/1.0/hub",
"@type": "IdentityHubConfiguration",
"endpoints": [{
"@context": "http://schema.identity.foundation/1.0/hub",
"@type": "InterfaceMap",
"interfaces": {
"Collections": "https://hub.azure.com/Collections",
"Profile": "https://www.foo.com/.identity/Profile",
"Actions": "https://id.bar.org/.identity/Actions",
...
}
},
{
"@context": "http://schema.identity.foundation/1.4/hub",
"@type": "InterfaceMap",
"interfaces": {
"Collections": "https://hub.azure.com/Collections",
"Profile": "https://www.foo.com/whatever-path/Profile",
"Actions": "https://id.bar.org/.identity/Actions",
...
}
}],
"signature": {
"kid": "did:btcs:456#key-1",
"value": "jv9323lavjsdav0d9ada2...",
"timestamp": "2018-10-24T18:39:10.10:00Z"
}
}
```
Hub implementations can choose to use a single URL for all interfaces they support, or may have different URLs for each. The format of each interface URL is not specified in this document.
This format allows for a hub provider to support multiple versions at different endpoints. Additionally hubs may dispatch interfaces to different paths for microservice routing, or explicitly to the same path for privacy.
If the `interface` property or the version of the schema in a request does not match correct URL for a hub deployment, the hub can fail the request with one of the error codes from above.
Once a client has discovered the URL(s) for a hub, it can send requests to the hub over HTTP. All requests to a hub are submitted using the HTTP POST verb:
```HTTP
POST /identity/0.1/ HTTP/1.1
Host: hub.com
Content-Type: application/jose
Content-Length: 534
eyJraWQiOiJkaWQ6dGVzdDpodWIuaWQjS...
```
Responses are returned as JWEs as well, and hubs usually use a status code of `200`, but may use other `2**` error code values to indicate success. If a `2**` status code is returned, the response body must contain a properly formed JWE. Note that a `200` does not necessarily indicate a successful hub request; the JWE returned may still contain an error response inside.
```HTTP
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2018 12:28:53 GMT
Content-Length: 501
Content-Type: application/jose
Connection: Closed
eyJraWQiOiJkaWQ6dGVzdDpodWIuaWJrR...
```
> [name=Danny Strockis] OPEN QUESTION: Brandon thinks we should return RESTful HTTP status codes here, rather than always returning a `200` and then relying on the interior error codes for communicate request status. He also thinks we should adopt POST/PUT/PATCH/GET/DELETE semantics for CRUD operations. Personally I agree, but others are concerned about privacy-related information leakage.
A JWE cannot be returned, however, in the following cases:
- if the client's DID public keys cannot be resolved.
- if the client's request was improperly encrypted, so the client's DID is unknown.
- if the client's request was improperly signed, so the client could not be authenticated.
- if a server error occurred while retreiving keys or encrypting the JWE response.
- if the client and hub do not support the same signing or encryption algorithms.
- if the client is being throttled by a reverse proxy/load balancer/gateway.
In these cases, the HTTP response is returned with an error status and a JSON formatted response containing additional information. If there was a problem with the client's request, or any problem fetching or using the client's DID keys:
```HTTP
HTTP/1.1 400 Bad Request
Date: Mon, 27 Jul 2018 12:28:53 GMT
Content-Length: 124
Content-Type: application/json
Connection: Closed
{
"error_code": "ABC123",
"error_url": "https://mydocumentation.com/errors/ABC123",
"developer_message": "Unable to verify signature of JWE in request",
"user_message": "Completely optional",
"target": null,
"inner_error": {
"request_id": "",
"timestamp": "",
"trace": "",
"custom-field": "",
}
}
```
If the server encountered an error during JWE parsing or construction:
```HTTP
HTTP/1.1 500 Server Error
Date: Mon, 27 Jul 2018 12:28:53 GMT
Content-Length: 124
Content-Type: application/json
Connection: Closed
{
"error_code": "ABC123",
"error_url": "https://mydocumentation.com/errors/ABC123",
"developer_message": "Temporary server error",
"user_message": "Completely optional",
"target": null,
"inner_error": {
"request_id": "",
"timestamp": "",
"trace": "",
"custom-field": "",
}
}
```
If the server is throttling the client due to too many reqeusts:
```HTTP
HTTP/1.1 429 Too Many Requests
Date: Mon, 27 Jul 2018 12:28:53 GMT
Content-Length: 124
Content-Type: application/json
Retry-After: 120
Connection: Closed
{
"error_code": "ABC123",
"error_url": "https://mydocumentation.com/errors/ABC123",
"developer_message": "Too many requests",
"user_message": "Completely optional",
"target": null,
"inner_error": {
"request_id": "",
"timestamp": "",
"trace": "",
"custom-field": "",
}
}
```
Hubs may also use other `4**` and `5**` status codes to indicate client and server error responses, respectively. When returning error responses outside of JWEs, the hub should be careful that error reponses do not contain information that might violate a user's privacy.
Future work
---
- Subscribing to changes (delta queries, link to replication docs)
- Modeling relationships between entities (navigation links)
- Multi-Resource Queries ($expand, GraphQL)
- Response Customization ($top, $orderBy, $select, $skip, $take)
- Batching
- Function/Actions
- Async Operations
- Computations ($sum, $count)