REST API

Main Prefixes

This generic REST API allows developers direct access to a SPARQL endpoint via /class or /resource entry points. From there on the developer can navigate the graph by intuitive path queries (alternating between resource and property). The generic REST API transforms the RDF triple world into an object-centric view (an object is represented by all outgoing edges). It however does not hide the fact that the interlinked objects form a graph.

HTTP Methods

GET

A GET on a class will return an array of ids. A GET on a single object will return its properties and values. A GET on a single property will return its values (or ids).

Simple leaf examples

Get all milkruns

GET /bosch/class/mr:Milkrun
{
  "ids": [":m1", ":m2", ":m3"]
}

Get the labels of bert

GET /bosch/class/foaf:Person/:bert/rdfs:label
{
  "values": [
    {"value": "Bert Treb", "datatype": "rdf:langString", "language": "en"},
    {"value": "Bert",      "datatype": "rdf:langString", "language": "en"},
    {"value": "Mr. T",     "datatype": "rdf:langString", "language": "en"}
  ]
}

Get the weights of crate c3

GET /bosch/resource/:c3/mr:crate_weight
{
  "values": [
    {"value": "123.4", "datatype": "weight:kg", "language": ""}
  ]
}

For consistency reasons the result of ids and values is always a list. The result can contain a single element [":m1"] or multiple [":m1", ":m3"].

In order to describe the value's datatype and language, each value is represented by a JSON object. This will become also necessary for correctly POST values.

Simple map examples

"Give me everything about Bert":

GET /bosch/resource/:bert
{
  "id-map": {
    "foaf:name": {
      "values": [{"value": "Bert Treb", "datatype": "xsd:string", "language": ""}]
    },
    "job:drives": {
      "ids": [":m1", ":m2"]
    }
    // ...
  }
}

POST

A POST message appends (never overwrites) (to) objects. We use the same syntax for the body of POST messages, as returned in GET messages.

We will show the changes in the knowledge base for each example with an inline diff . At the beginning the knowledge base is empty (no triples exist).

Create a new person

POST /bosch/class/foaf:Person/
{
  "id-map": {
    "foaf:name": {
      "values": [{"value": "Bert Treb", "datatype": "rdf:langString", "language": "en"}]
    },
    "job:drives": {
      "ids": [":m1", ":m2"]
    }
  }
}
{
  "ids": ["uuid:1234"],
}

Add new names to Bert

POST /bosch/class/foaf:Person/uuid:1234/foaf:name
{
  "values": [
    {"value": "Bertos Trebos", "datatype": "rdf:langString", "language": "es"},
    {"value": "Berto Trebinski", "datatype": "rdf:langString", "language": "pl"}
  ]
}
{}

State that Bert drives also m3

POST /bosch/class/foaf:Person/uuid:1234/job:drives
{
  "ids": [":m3"]
}
{}

Add a new weight to the crate c3

POST /bosch/resource/:c3/mr:crate_weight
{
  "values": [
    {"value": "432.1", "datatype": "weight:kg", "language": ""}
  ]
}
{}

PUT

A PUT, in contrast, replaces and overwrites objects / facts in the knowledge base.

Overwrite Bert's name

PUT /bosch/class/foaf:Person/uuid:1234/foaf:name
{
  "values": [
    {"value": "Herr Treb", "datatype": "rdf:langString", "language": "de"}
  ]
}
{}

Overwrite Bert's object (overwrites all of Bert's properties, left out ones are deleted)

Statements which are not re-written are deleted. Notice however, that statements can be implied by the path (here rdf:type foaf:Person).

PUT /bosch/class/foaf:Person/uuid:1234
{
  "id-map": {
    "job:drives": {
      "ids": [":m4"]
    },
    "foaf:knows": {
      "ids": [":peter", ":marry"]
    }
  }
}
{}

Put a fresh object

PUT /bosch/class/mr:Milkrun/:a_new_uri
{
  "id-map": {
    "rdfs:comment": {
      "values": [{ "value": "Das ist eine deutsche Erklärung.", "datatype": "rdf:langString", "language": "de" }]
    }
  }
}
{}

Put (and Post) allow custom datatypes

PUT /bosch/resource/:610e19ad-e1b6-436f-8c91-f9ba1398364d
{
 "id-map": {
   ":someproperty": {
       "values":[
         {"value": "2016-11-02T12:00:00Z", "datatype": "gtrace:Date", "language":""}
       ]
   }
 }
}
{}

DELETE

Assume the following knowledge base for the next examples:

Remove that Bert drives m4

DELETE /bosch/class/foaf:Person/uuid:1234/job:drives/:m4
{}

State that Bert knows nobody

DELETE /bosch/class/foaf:Person/uuid:1234/mr:knows
{}

Remove Bert from the knowledge base

DELETE /bosch/class/foaf:Person/uuid:1234
{}

Incoming edges are also deleted

Assume the following knowledge base for the next example:

DELETE /bosch/resource/:delbert
{}

Query Expressiveness

In this section we introduce more complex query capabilities: property path and wildcards.

Property Path

We allow the usage of property paths. They can only be used to replace a property id.

We indicate the use of a property path by round brackets ( ).

Get Bert's names and what he drives

GET /bosch/class/foaf:Person/:bert/(foaf:name|job:drives)
{
  "ids": [":m1", ":m2"],
  "values": [
    {"value": "Bert Treb", "datatype": "xsd:string", "language": ""}
  ]
}

Get all of Bert's descendants

GET /bosch/class/foaf:Person/:bert/(foaf:child+)
{
  "ids": [":peter", ":marry", ":paul"]
}

Get all of Bert's descendants (including him)

GET /bosch/class/foaf:Person/:bert/(foaf:child*)
{
  "ids": [":bert", ":peter", ":marry", ":paul"]
}

Get all names of Bert's friends

GET /bosch/class/foaf:Person/:bert/(foaf:hasFriend/foaf:name)
{
  "values": [
    {"value": "Peter", "datatype": "rdf:langString", "language": "en"},
    {"value": "Paul",  "datatype": "rdf:langString", "language": "en"},
    {"value": "marry", "datatype": "rdf:langString", "language": "en"}
  ]
}

Get all crates which are transported by m1

GET /bosch/class/mr:Milkrun/:m1/(^mr:isTransportedBy)
{
  "ids": [":c2", ":c6", ":c8"]
}

Wildcard

The wildcard * can be seen as an unnamed variable which iterates over all possibilities depending on the location in the URI. As a rule of thumb each * will cause the result to contain an additional nesting layer, mostly using the id-map JSON object. Its keys are the ids the * iterated over, its values are nested results for that key.

An example:

GET /bosch/resource/:bert/foaf:knows/*/foaf:name
{
  "id-map": {
    ":peter": {
      "values": [{"value": "Peter Retep", "datatype": "xsd:string", "language": ""}]
    },
    ":hans": {
      "values": [{"value": "Hans Snah", "datatype": "xsd:string", "language": ""}]
    }
  }
}

A nested example:

GET /bosch/resource/*/job:drives/*/rdfs:label
{
  "id-map": {
    ":bert": {
      "id-map": {
        ":m1": {
          "values": [{"value": "Milkrun 1", "datatype": "xsd:string", "language": ""}]
        }
      }
    },
    ":hans": {
      "id-map": {
        ":m2": {
          "values": [
            {"value": "Milkrun 2", "datatype": "xsd:string", "language": ""},
            {"value": "Milkrun Old", "datatype": "xsd:string", "language": ""}
          ]
        }
      }
    }
  }
}

Wildcard example with respect to final * that matches objects and literals

GET /bosch/resource/:bert/*/*
{
  "id-map": {
    "foaf:name": {
      "values": [{"value": "Bert Treb", "datatype": "xsd:string", "language": ""}]
    },
    "rdfs:label": {
      "values": [ // values can't be further expanded by final *
        {"value": "Bert", "datatype": "rdf:langString", "language": "en"},
        {"value": "Bert Treb", "datatype": "rdf:langString", "language": "en"}
      ]
    },
    "job:drives": {
      "id-map": { // the final * leads to the expansion of all objects
        ":m1": {
          "id-map": {
            "mr:drives_to": { "ids": [":hall2"] }
            // ...
          }
        }
      }
    }
  }
}

Value Maps via Property Paths and Wildcards

By default, wildcard expansion will stop at Literals as they don't have outgoing edges. If desired, you can use a Property Path to traverse against the edge direction, which then creates a value-map.

Nested id- and value-maps example:

GET /bosch/resource/*/foaf:name/*/(^rdfs:label)
{
  "id-map": {
    ":bert": {
      "value-map": [
        [
          {"value": "Bert Treb", "datatype": "xsd:string", "language": ""},
          {"ids": [":bert", ":bert2"]}
        ],
        [
          {"value": "Bert", "datatype": "xsd:string", "language": ""},
          {"ids": [":bert"]}
        ]
      ]
    }
  }
}

Result Refinement

This section shows how the query can be manipulated in terms of filtering, sorting and paging. We use RQL to express the manipulation. Like stated above in uri syntax RQL is only usable at the end of the path query.

Filtering

For filtering we introduce regex which is used on string data type properties to search for objects which have values like the given regular expression. In places where usually ids are returned, regex can filter them by constraining a properties value.

Get all person starting with name "Bert"

GET /bosch/class/foaf:Person?regex(foaf:name,^Bert.*)
{
    "ids": [":berthold", ":bert", ":berta"]
}

Get all friends of Bert ending in name "ter"

GET /bosch/class/foaf:Person/:bert/foaf:knows?regex(foaf:name,^.*ter$)
{
    "ids": [":peter", ":walter"]
}

Sorting

We sort with the sort RQL statement. You have to define ascending + or descending - order. The sort statement needs a data type property with comparable value.

Sort all crates by cost (ascending)

GET /bosch/class/mr:Crate?sort(+mr:crate_cost)
{
    "ids": [":c3", ":c4", ":c5", ":c6", ":c1", ":c2"]
}

Sort all persons who Bert knows by name (descending)

GET /bosch/class/foaf:Person/:bert/foaf:knows?sort(-foaf:name)
{
    "ids": [":peter", ":marry", ":hans"]
}

Paging

We page the result with the limit RQL statement. It has an optional offset value and requires a limit value how many results will be returned. To make sure that the result is deterministic, you should use a sort statement before.

Get all persons ordered by their names (ascending)

GET /bosch/class/foaf:Person?sort(+foaf:name)
{
    "ids": [":alfred", ":bert", ":clara", ":daniel"]
}

Page the persons with 2 persons on each page

GET /bosch/class/foaf:Person?sort(+foaf:name),limit(0,2)
{
    "ids": [":alfred", ":bert"]
}
GET /bosch/class/foaf:Person?sort(+foaf:name),limit(2,2)
{
    "ids": [":berta", ":berthold"]
}
GET /bosch/class/foaf:Person?sort(+foaf:name),limit(20,2)
{}

Given only one parameter the offset defaults to 0

GET /bosch/class/foaf:Person?sort(+foaf:name),limit(3)
{
    "ids": [":alfred", ":bert", ":berta"]
}

If you forget to sort the result will not be deterministic

GET /bosch/class/foaf:Person?limit(0,2)
{
    "ids": [":bert", ":daniel"]
}
GET /bosch/class/foaf:Person?limit(0,2)
{
    "ids": [":clara", ":bert"]
}

Aggregating

We allow aggregation of values with the RQL statements count, sum and avg. These statements always operate on the last leaf type. sum and avg can only be applied to numeric values (we throw an error if they are not numeric).

Counting all persons

GET /bosch/class/foaf:Person
{
    "ids": [":bert", ":peter", ":marry"]
}
GET /bosch/class/foaf:Person?count
{
    "count": 11
}

Count on a resource

GET /bosch/resource/:bert
{
  "id-map": {
    "foaf:name": {
      "values": [{"value": "Bert Treb", "datatype": "rdf:langString", "language": "en"}]
    },
    "job:drives": {
      "ids": [":m1", ":m2"]
    }
  }
}
GET /bosch/resource/:bert?count
{
  "id-map": {
    "foaf:name": {
        "count": 2
    },
    "job:drives": {
        "count": 2
    }
  }
}

Average age of all persons

GET /bosch/resource/foaf:Person/(^rdf:type/foaf:age)?avg
{
    "avg": 33.5
}

For each milkrun the sum of pieces loaded in its crates

GET /bosch/class/mr:Milkrun/*/(mr:vehicle_contains/^mr:transported_by/mr:contains_mat_pieces)?sum
{
    "id-map": {
        ":m1": {
            "sum": 1000
        },
        ":m2": {
            "sum": 111
        },
        ":m3": {
            "sum": 12
        }
    }
}

Batch

You can use the /batch prefix to post a list of ids or values (entities) and process them in batch mode. Every given id or value is treated as if you use the /resource/<entity> URI. The /batch... path postfix is treated similarly to /resource/<entity>....

Consequently this means that if /batch is followed by some path expression, the first component will be a property (e.g., /batch/foaf:name).

Simple Use

Invoke GET /resource/<entity> on every given entity

GET /bosch/batch
{
    "ids": [":bert", ":peter", ":marry"]
}
{
    "id-map": {
        ":bert": {
            "id-map": {
                "foaf:knows": { "ids": [":peter"] },
                "foaf:drives": { "ids": [":m1"] },
                "foaf:name": { "values": [{"value": "Bert Treb", "datatype": "rdf:langString", "language": "en"}]}
            }
        },
        ":peter": {
            "id-map": {
                "foaf:knows": { "ids": [":marry"] },
                "foaf:drives": { "ids": [":m3"] },
                "foaf:name": { "values": [{"value": "Peter Retep", "datatype": "rdf:langString", "language": "en"}]}
            }
        },
        ":marry": {
            "id-map": {
                "foaf:knows": { "ids": [":bert"] },
                "foaf:name": { "values": [{"value": "Marry Yrram", "datatype": "rdf:langString", "language": "en"}]}
            }
        }
    }
}

Show all first names of the given persons

GET /bosch/batch/foaf:firstname
{
    "ids": [":bert", ":peter", ":marry"]
}
{
    "id-map": {
        ":bert": {
            "values": [{ "value": "Bert", "datatype": "rdf:langString", "language": "en"}]
        },
        ":peter": {
            "values": [{ "value": "Peter", "datatype": "rdf:langString", "language": "en"}]
        },
        ":marry": {
            "values": [{ "value": "Marry", "datatype": "rdf:langString", "language": "en"}]
        }
    }
}

Batch can also process values

GET /bosch/batch/(^rdfs:label)
{
  "values": [
    {"value": "CX0023", "datatype": "xsd:string", "language": ""},
    {"value": "EA8815", "datatype": "xsd:string", "language": ""}
  ]
}
{
  "value-map": [
    [
      {"value": "CX0023", "datatype": "xsd:string", "language": ""},
      {"ids": [":c23"]}
    ], [
      {"value": "EA8815", "datatype": "xsd:string", "language": ""},
      {"ids": [":c15"]}
    ]
  ]
}

Input from previous results

For convenience /batch is able to process the output of previous operations as input "as-is".

Simple GET and batch

You can use the output of a GET...

GET /bosch/class/foaf:Person
{
    "ids": [":peter", ":marry", ":albert"]
}

... to POST it to the batch processing.

GET /bosch/batch/(foaf:knows/foaf:knows)
{
    "ids": [":berta", ":marry", ":albert"]
}
{
    "id-map": {
        ":marry": { "ids": [":albert"]}
    }
}

GET with wildcards and batch

GET /bosch/class/foaf:Person/*/foaf:knows/*/foaf:name
{
  "id-map": {
    ":bert": {
      "id-map": {
        ":marry": {
          "values": [{ "value": "Marry", "datatype": "rdf:langString", "language": "en"}]
        }
        // ...
      }
    },
    ":peter": {
      "id-map": {
        ":marry": {
          "values": [{ "value": "Marry", "datatype": "rdf:langString", "language": "en"}]
        }
        // ...
      }
    }
    // ...
  }
}

Two wildcards create a two-folded id-map in this case.

For convenience, the post to /batch also supports such mapping results as input. In those cases the first layer of the map, so the keys of the outermost id-map or value-map will be used as batch-entities:

GET /bosch/batch/job:drives
{
  "id-map": {
    ":bert": {
      "id-map": {
        ":marry": {
          "values": [{ "value": "Marry", "datatype": "rdf:langString", "language": "en"}]
        }
      }
    },
    ":peter": {
      "id-map": {
        ":marry": {
          "values": [{ "value": "Marry", "datatype": "rdf:langString", "language": "en"}]
        }
      }
    }
  }
}
{
  "id-map": {
    ":bert": {
      "ids": [":m1"]
    },
    ":peter": {
      "ids": [":m2"]
    }
    // ...
  }
}

GET with wildcards and batch with wildcards (no surprise magic)

Batch path expressions can be complex themselves. However, there is no attempt to match any levels of the maps together, as this would lead to inconsistent behavior and be quite confusing. Batch queries are independent of the previous query. If the convenience of posting the previous query's result is used, the batch query will behave as above: The given ids/values or the outermost map's keys will be used as entities. The /batch/...xyz... query is then performed for each entity as if it was written like this: /resource/<entity>...xyz....

So with the GET results from the previous section you can use batch:

GET /bosch/batch/job:drives/*/rdfs:label
{
  "id-map": {
    ":bert": {
      "id-map": {
        ":marry": {
          "values": [{ "value": "Marry", "datatype": "rdf:langString", "language": "en"}]
        }
      }
    },
    ":peter": {
      "id-map": {
        ":marry": {
          "values": [{ "value": "Marry", "datatype": "rdf:langString", "language": "en"}]
        }
      }
    }
  }
}
{
  "id-map": {
    ":bert": {
      "id-map": {
        ":m1": {
          "values": [{ "value": "Milkrun 1", "datatype": "rdf:langString", "language": "en"}]
        }
        // ...
      }
    },
    ":peter": {
      "id-map": {
        ":m2": {
          "values": [{ "value": "Milkrun 2", "datatype": "rdf:langString", "language": "en"}]
        }
        // ...
      }
    }
    // ...
  }
}

Advanced Topics

The next section covers advanced topics which require more knowledge about semantic web technologies.

Blank Nodes

Blank nodes are handled in the API like you would use normal URIs. They are indicated with the underscore namespace _.

GET /bosch/resource/:bert/foaf:address/_:bertsaddress
{
  "id-map": {
    "foaf:street": {
      "values": [{"value": "bert-street", "datatype": "xsd:string", "language": ""}]
    },
    "foaf:streetnr": {
      "values": [{"value": "42", "datatype": "xsd:string", "language": ""}]
    }
  }
}
PUT /bosch/resource/:peter/foaf:address
{
  "ids": [ "_:petersaddress" ]
}
{}
DELETE /bosch/resource/:peter/foaf:address/_:petersaddress
{}

Additional Features

In this section we list additional features besides the core feature mentioned above.

Namespace Feature

This feature uses the /namespace prefix.

To lists all mappings invoke:

GET /bosch/namespace
{
   "":"urn:bosch_data:",
   "date":"urn:bosch_vocabs:date#",
   "stardog":"tag:stardog:api:",
   "mr":"urn:bosch_vocabs:milkrun#",
   "org":"urn:bosch_vocabs:org#",
   "owl":"http://www.w3.org/2002/07/owl#",
   "xsd":"http://www.w3.org/2001/XMLSchema#",
   "weight":"urn:bosch_vocabs:weight#",
   "rdfs":"http://www.w3.org/2000/01/rdf-schema#",
   "rdf":"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
   "currency":"urn:bosch_vocabs:currency#",
   "job":"urn:bosch_vocabs:job#",
   "foaf":"http://xmlns.com/foaf/0.1/"
}

An additional list selects namespaces (comma-separated). Encoded URIs are allowed. default is the standard namespace "" (empty).

GET /bosch/namespace/http%3A%2F%2Fwww.w3.org%2F2002%2F07%2Fowl%23,default,foaf
{
   "":"urn:bosch_data:",
   "owl":"http://www.w3.org/2002/07/owl#",
   "foaf":"http://xmlns.com/foaf/0.1/"
}

Mappings are given in body of the request to add them with POST.

POST /bosch/namespace
{
    "new":"http://xmlns.com/new/2.0/",
    "new2":"http://xmlns.com/new2/3.0/"
}
{ }

DELETE removes mappings but only prefixes are possible. Multiple removal with given list.

DELETE /bosch/namespace/new,new2
{ }



Data Protection