3. API

The tozti core provides an API to perform operations on the database prefixed with /api/store. This API is largely inspired by JSON API so you are encouraged to go take a look at their specification.

3.1. Error format

The format of the errors follows JSON API errors. If a request raised an error, the server will send back a response with status code 500, 404, 406 409 or 400. This response might send back a json object with an entry errors containing a list of json objects with the following properties:

code
The name of the error
status
Status code of the error
title
Short description of the error
detail
More about this error. This entry might not be present.
traceback
Traceback of the error. This entry might not be present and is included only if tozti is launched in dev mode.

3.2. Concepts and Data Structures

3.2.1. Resources

Resources and resource objects are the main concept of the store API. A resource is what we would call an entity in SQL or hypermedia on the web. A resource object is represented as a json object with the following properties:

id
An UUIDv4 which uniquely identifies a resource.
href
A URL to the object itself.
type
The name of a type object.
body
A JSON object where the keys are strings and values are either relationship objects or arbitrary JSON value (ie attributes).
meta
A JSON object containing some metadata about the resource. For now it only contains created and last-modified which are two self-explanatory dates in ISO 8601 format (UTC time zone).

3.2.2. Relationships

A relationship is a way to create a directed and tagged link between two resources. Relationships can be to-one (resp. to-many) in which case they link to one (resp. a sequence) of other resources. Practically, a resource object is a JSON object with the following properties (beware, here we diverge a little from the JSON API spec):

self
An URL pointing to the current relationship object. This URL can be used to operate on this relationship.
data
In the case of a to-one relationship, this is a linkage object, in the case of a to-many relationship, this is an array of linkage objects.

Linkages are simply pointers to a resource. They are JSON objects with three properties:

id
The ID of the target resource.
type
The type of the target resource.
href
An URL pointing to the target resource.

3.2.3. Types

A type object is simply a JSON object with the following properties:

body
A JSON object where keys are allowed (and required) item names for resource objects and values are JSON Schemas. A JSON Schema is a format for doing data validation on JSON. For now we support the Draft-04 version of the specification (which is the latest supported by the library we use).

To the usual JSON schema types, we add the following ones:

"type": "relationship"

This type specifies that the body-item should be a relationship object. It supports the following options:

  • arity, either to-one, to-many or auto.
  • targets, this option is only valid with "arity": "to-one" or "arity": "to-many". It should be the name of a resource type or an array of such names that define which kind of resources can be pointed to by this relationship. If left undefined every type is allowed.
  • pred-type, this option is only valid with "arity": "auto". It should be a resource type. See more info below.
  • pred-relationship, this option is only valid with "arity": "auto". See more info below.
"type": "upload"
This type specifies that the body-item should be a blob. It supports the acceptable option that should be an array of content-types that should be accepted.

3.2.4. Automatic relationships

This of relationship description exists because relationships are directed. As such, because sometimes bidirectional relationships are useful, we would want to specify that some relationship is the reverse of another one. To solve that, we introduced the auto relationships. This will specify a new relationship that will not be writeable and automatically filled by the Store engine. It will contain as target any resource of the given type that have the current resource as target in the given relationship name. In order to fully specify an auto relationship, you need to specify the type of the related object in pred-type, as well as pred-relationship, the name of the relationship in that object, that should be reversed.

Let’s show an example, we will consider two types: users and groups.

// user:
 {
     'body': {
         'name': { 'type': 'string' },
         'email': { 'type': 'string', 'format': 'email' },
         'handle': { 'type': 'string' },
         'hash': {'type': 'string'},
         'groups': {
             'type': 'relationship',
             'arity': 'to-many',
             'targets': 'core/group',
         },

         'pinned': {
             'type': 'relationship',
             'arity': 'to-many',
             'targets': 'core/folder'
         }
     }
 }
// group:
 {
     'body': {
         'name': { 'type': 'string' },
         'handle' : { 'type': 'string' },
         'members': {
             'type': 'relationship',
             'arity': 'auto',
             'pred-type': 'core/user',
             'pred-relationship': 'groups'
         }
     }
 }

Now when creating a group you cannot specify it’s users, but you can specify the groups when creating (or updating) a given user and the system will automagically take care of filling the members relationship with the current up-to-date content.

3.3. Endpoints

We remind that the API is quite similar to what JSON API proposes. In the following section, type warrior is the type defined as:

{
    "body": {
        "name": { "type": "string" },
        "honor": { "type": "number"}
        "weapon": {
            "type": "relationship"
            "arity": "to-one",
            "targets": "weapon"
        },
        "kitties": {
            "type": "relationship"
            "arity": "to-many",
            "targets": "cat"
        }
}

A warrior has a name and a certain amount of honor. He also possesses a weapon, and can be the (proud) owner of several cats (or no cats).

3.3.1. Resources

3.3.1.1. Fetching an object

To fetch an object, you must execute a GET request on /api/store/resources/{id} where id is the ID of the resource.

Error code:
  • 404 if id corresponds to no known objects.
  • 400 if an error occurred when processing the object (for example, one of the object linked to it doesn’t exists anymore in the database).
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back a resource object under JSON format.
Example:

Suppose that an object of type warrior and id a0d8959e-f053-4bb3-9acc-cec9f73b524e exists in the database. Then:

>> GET /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e
200
{
   'data':{
      'id':'a0d8959e-f053-4bb3-9acc-cec9f73b524e',
      'href': 'http://tozti/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e'
      'type':'warrior',
      'body':{
         'name':'Pierre',
         'honor': 9000
         'weapon':{
            'self':'http://tozti/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/weapon',
            'data':{
               'id':'1bb2ff78-cefb-4ce1-b057-333f5baed577',
               'type':'weapon',
               'href':'http://tozti/api/store/resources/1bb2ff78-cefb-4ce1-b057-333f5baed577'
            }
         },
         'kitties':{
            'self':'http://tozti/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/friend',
            'data':[{
               'id':'6a4d05f1-f04a-4a94-923e-ad52a54456e6',
               'type':'cat',
               'href':'http://tozti/api/store/resources/6a4d05f1-f04a-4a94-923e-ad52a54456e6'
            }]
         }
      },
      'meta':{
         'created':'2018-02-05T23:13:26',
         'last-modified':'2018-02-05T23:13:26'
      }
   }
}

3.3.1.2. Creating an object

To create an object, you must execute a POST request on /api/store/resources where the body is a JSON object representing the object you want to send. The object must be encapsulated inside a data entry.

Error code:
  • 404 if one of the object targeted by a relationship doesn’t exists
  • 400 if an error occurred when processing the object. For example, if the json object which was sended is malformated, or if the body of the request is not JSON.
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back a resource object under JSON format.
Example:

Suppose that an object of type warrior and id a0d8959e-f053-4bb3-9acc-cec9f73b524e exists in the database. Then:

>> POST /api/store/resources {'data': {'type': 'warrior',
                'body': {
                    'name': Pierre, 'honor': 9000,
                    'weapon': {'data': {'id': <id_weapon>}},
                    'kitties': {'data': [{'id': <kitty_1_id>}]}
                }}}
200
{
   'data':{
      'id':'a0d8959e-f053-4bb3-9acc-cec9f73b524e',
      'type':'warrior',
      'href':'http://tozti/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/',
      'body':{
         'name':'Pierre',
         'honor': 9000
         'weapon':{
            'self':'/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/friend',
            'data':{
               'id':'1bb2ff78-cefb-4ce1-b057-333f5baed577',
               'type':'weapon',
               'href':'/api/store/resources/1bb2ff78-cefb-4ce1-b057-333f5baed577'
            }
         },
         'kitties':{
            'self':'/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/friend',
            'data': [{
               'id':'6a4d05f1-f04a-4a94-923e-ad52a54456e6',
               'type':'cat',
               'href':'/api/store/resources/6a4d05f1-f04a-4a94-923e-ad52a54456e6'
            }]
         }
      },
      'meta':{
         'created':'2018-02-05T23:13:26',
         'last-modified':'2018-02-05T23:13:26'
      }
   }
}

3.3.1.3. Editing an object

To edit an object, you must execute a PATCH request on /api/store/resources/{id} where id is the ID you want to update. The body of the request must be a JSON object representing the change you want to operate on the object. The object must be encapsulated inside a data entry. Remark: you don’t need to provide every entries.

Error code:
  • 404 if id corresponds to no known objects.
  • 400 if an error occurred when processing the object. For example, if the json object which was sended is malformated, or if the body of the request is not JSON.
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back a resource object under JSON format representing the object (after changes are applied).
Example:

We suppose the object with id a0d8959e-f053-4bb3-9acc-cec9f73b524e exists in the database. Then:

>> PATCH /api/store/resources {'data': {'type': 'warrior',
                'attributes': {
                    'name': 'Luc',
                    'weapon': {'data': {'id': <id_weapon_more_powerfull>}},
                }}}
200
{
   'data':{
      'id':'a0d8959e-f053-4bb3-9acc-cec9f73b524e',
      'type':'warrior',
      'href':'http://tozti/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e',
      'body':{
         'name':'Luc',
         'honor': 9000
         'weapon':{
            'self':'/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/friend',
            'data':{
               'id':'<id_weapon_more_powerfull>',
               'type':'weapon',
               'href':'/api/store/resources/<id_weapon_more_powerfull>'
            }
         },
         'kitties':{
            'self':'/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/friend',
            'data': [{
               'id':'6a4d05f1-f04a-4a94-923e-ad52a54456e6',
               'type':'cat',
               'href':'/api/store/resources/6a4d05f1-f04a-4a94-923e-ad52a54456e6'
            }]
         }
      },
      'meta':{
         'created':'2018-02-05T23:13:26',
         'last-modified':'2018-02-05T23:13:26'
      }
   }
}

3.3.1.4. Deleting an object

To delete an object, you must execute a DELETE request on /api/store/resources/{id} where id is the ID you want to update. Remark: you don’t need to provide every entries.

Error code:
  • 404 if id corresponds to no known objects.
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back an empty JSON object.
Example:

We suppose the object with id a0d8959e-f053-4bb3-9acc-cec9f73b524e exists in the database. Then:

>> DELETE /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e
200
{}

3.3.2. Relationships

In the same way that you can act on resources, you can also act on relationships.

3.3.2.1. Fetching a relationship

To fetch a relationship, you must execute a GET request on /api/store/resources/{id}/{rel} where id is the ID of the resource possessing the relationship you want to access, and rel the name of the relationship.

Error code:
  • 404 if id corresponds to no known objects or rel is an invalid relationship name.
  • 400 if an error occurred when processing the object.
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back a relationship object under JSON format.
Example:

Suppose that an object of type warrior and id a0d8959e-f053-4bb3-9acc-cec9f73b524e exists in the database. Then:

>> GET /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/kitties
200
{
    "data": {
        "self": "/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/kitties", "data": [{
            "id": "93b41bf0-73e8-4b37-b2b9-d26d758c2539",
            "type": "cat",
            "href": "/api/store/resources/93b41bf0-73e8-4b37-b2b9-d26d758c2539"
        }, {
            "id": "dff2b520-c3b0-4457-9dfe-cb9972188e48",
            "type": "cat",
            "href": "/api/store/resources/dff2b520-c3b0-4457-9dfe-cb9972188e48"
        }]
    }
}
>> GET /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/weapon
200
{
    "data": {
        "self": "/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/weapon", "data": {
            "id": "34078dd5-516d-42dd-816d-6fbfd82a2da9",
            "type": "weapon",
            "href": "/api/store/resources/34078dd5-516d-42dd-816d-6fbfd82a2da9"
        }
    }
}

3.3.2.2. Updating a relationship

To update a relationship (which is not an automatic relationship), you must execute a PUT request on /api/store/resources/{id}/{rel} where id is the ID of the resource possessing the relationship you want to access, and rel the name of the relationship. The content of your request is a JSON object containing:

  • for a to-one relationship the ID of the new target
  • for a to-many relationship several IDs representing the new targets
Error code:
  • 404 if id corresponds to no known objects or rel is an invalid relationship name.
  • 400 if an error occurred when processing the object.
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back a relationship object under JSON format.
Example:

Suppose that an object of type warrior and id a0d8959e-f053-4bb3-9acc-cec9f73b524e exists in the database. We also suppose that its relationship kitties possesses two targets having id <id1> and <id2>. The relationship weapon targets <id_sword>. Then:

>> PUT /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/kitties {'data': [{'id': <id3>}]}
200
{
    "data": {
        "self": "/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/kitties", "data": [{
            "id": <id3>,
            "type": "cat",
            "href": "/api/store/resources/<id3>"
        }]
    }
}
>> PUT /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/weapon {'data': {'id': <id_shotgun>}}
200
{
    "data": {
        "self": "/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/weapon", "data": [
            "id": <id_shotgun>,
            "type": "weapon",
            "href": "/api/store/resources/<id_shotgun>"
        ]
    }
}

3.3.2.3. Adding new targets to a relationship

To add new targets to a to-many relationship, you must execute a POST request on /api/store/resources/{id}/{rel} where id is the ID of the resource possessing the relationship you want to access, and rel the name of the relationship. The content of your request is a JSON object containing the ids of the objects you want to add to the relationship.

Error code:
  • 404 if id corresponds to no known objects or rel is an invalid relationship name.
  • 403 if the relationship is not a too-many relationship
  • 400 if an error occurred when processing the object.
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back a relationship object under JSON format.
Example:

Suppose that an object of type warrior and id a0d8959e-f053-4bb3-9acc-cec9f73b524e exists in the database. We also suppose that its relationship kitties possesses one targets having id <id1>. Then:

>> POST /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/kitties {'data': [{'id': <id2>}, {'id': <id3>}]}
200
{
    "data": {
        "self": "/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/kitties", "data": [{
            "id": <id1>,
            "type": "cat",
            "href": "/api/store/resources/<id1>"
        }, {
            "id": <id2>,
            "type": "cat",
            "href": "/api/store/resources/<id2>"
        }, {
            "id": <id3>,
            "type": "cat",
            "href": "/api/store/resources/<id3>"
        }]
    }
}

3.3.2.4. Deleting a relationship

To fetch some targets from a to-many relationship, you must execute a DELETE request on /api/store/resources/{id}/{rel} where id is the ID of the resource possessing the relationship you want to access, and rel the name of the relationship. The content of your request is a JSON object containing the ids of the objects you want to remove from the relationship.

Error code:
  • 404 if id corresponds to no known objects or rel is an invalid relationship name.
  • 403 if the relationship is not a too-many relationship
  • 400 if an error occurred when processing the object.
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back a relationship object under JSON format.
Example:

Suppose that an object of type warrior and id a0d8959e-f053-4bb3-9acc-cec9f73b524e exists in the database. We also suppose that its relationship kitties possesses three targets having ids <id1>, <id2> and <id3>. Then:

>> DELETE /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/kitties {'data': [{'id': <id1>}, {'id': <id3>}]}
200
{
    "data": {
        "self": "/api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/kitties", "data": [{
            "id": <id2>,
            "type": "cat",
            "href": "/api/store/resources/<id2>"
        }]
    }
}
>> DELETE /api/store/resources/a0d8959e-f053-4bb3-9acc-cec9f73b524e/weapon
403
{
    "errors": [{
        "code": "BAD_RELATIONSHIP",
        "title": "a relationship is invalid",
        "status": "403",
        "detail": "to-one relationships cannot be deleted"
    }]
}

3.3.3. Types

3.3.3.1. Fetching all instances of a given type

To fetch all instances of a given type <type>, you must execute a GET request on /api/store/by-type/<type>.

Error code:
  • 404 if the type doesn’t exists
  • 400 if an error occurred when processing the object.
  • 200 if the request was successful.
Returns:
If the request is successful, the server will send back a list of linkage objects encapsulated under a data entry. Each linkage object points toward a ressources having type <type>
Example:

To fetch every warrior present inside our store, you can proceed as following:

>> GET /api/store/by-type/warrior
200
{
    "data": [
    {
        "id": "60f1677b-2bbb-4fd9-9a7a-3a20dbf7b5af",
        "type": "core/user",
        "href": "/api/store/resources/60f1677b-2bbb-4fd9-9a7a-3a20dbf7b5af"
    }, {
        "id": "605ab4bc-172b-416e-8a13-186cf3cd1e2e",
        "type": "core/user",
        "href": "/api/store/resources/605ab4bc-172b-416e-8a13-186cf3cd1e2e"
    }]
}
Remark:
Most of the time, type names are under this form: <ext-name>/<type-name where <ext-name> is the name of the extension defining the type <type-name>. To fetch of instances of this type, send a GET request on /api/store/by-type/<ext-name>/<type-name>.