Cookbook
This is a cookbook of all the API calls that are available to the Fluree HTTP API.
If you have a Fluree ledger (self-hosted or on Nexus), you can run the entirety of the following Cookbook through Postman.
Simply update the request URLs to appropriately target your Fluree instance (as the default is set to http://localhost:58090
).
If you are running against a locally-hosted instance, make sure to use Postman for Desktop to enable localhost
requests.
[DO FIRST] SETUP
If you're using the Cookbook examples below, make sure to start by creating a ledger with the following API request.
Ledger Creation
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /create*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:freddy", "@type": "ex:Yeti", "schema:age": 4, "schema:name": "Freddy", "ex:verified": true }, { "@id": "ex:letty", "@type": "ex:Yeti", "schema:age": 2, "ex:nickname": "Letty", "schema:name": "Leticia", "schema:follows": [{ "@id": "ex:freddy" }] }, { "@id": "ex:betty", "@type": "ex:Yeti", "schema:age": 82, "schema:name": "Betty", "schema:follows": [{ "@id": "ex:freddy" }] }, { "@id": "ex:andrew", "@type": "schema:Person", "schema:age": 35, "schema:name": "Andrew Johnson", "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ] } ]}
Querying
Select Statements
The following query examples demonstrate the various ways to use the select
keyword in Fluree Queries.
Graph Queries
We're able to use the select
key in order to express graph crawls across our data. By following property paths that relate one entity to another, we are able to express graph crawls that include interconnected data in our query results.
* (Wildcard)
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*"] }}
Example Response
[ { "@id": "ex:andrew", "@type": "schema:Person", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:name": "Andrew Johnson" }, { "@id": "ex:betty", "@type": "ex:Yeti", "schema:age": 82, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }]
Graph Crawls
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["schema:name", "schema:age"] }}
Example Response
[ { "schema:age": 35, "schema:name": "Andrew Johnson" }, { "schema:age": 82, "schema:name": "Betty" }, { "schema:age": 4, "schema:name": "Freddy" }, { "schema:age": 2, "schema:name": "Leticia" }]
Expanded Graph Crawls
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*", { "schema:follows": ["*"] }] }}
Example Response
[ { "@id": "ex:andrew", "@type": "schema:Person", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }, { "@id": "ex:betty", "@type": "ex:Yeti", "schema:age": 82, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" } ], "schema:name": "Andrew Johnson" }, { "@id": "ex:betty", "@type": "ex:Yeti", "schema:age": 82, "schema:follows": { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, "schema:name": "Betty" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, "schema:name": "Leticia" }]
Graph Crawls by Depth
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*"] }, "depth": 3}
Example Response
[ { "@id": "ex:andrew", "@type": "schema:Person", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, "schema:name": "Leticia" }, { "@id": "ex:betty", "@type": "ex:Yeti", "schema:age": 82, "schema:follows": { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, "schema:name": "Betty" } ], "schema:name": "Andrew Johnson" }, { "@id": "ex:betty", "@type": "ex:Yeti", "schema:age": 82, "schema:follows": { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, "schema:name": "Betty" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "schema:age": 4, "schema:name": "Freddy" }, "schema:name": "Leticia" }]
SelectDistinct
We can replace our select
key with selectDistinct
. As the following queries demonstrate, if our result set might have included identical entries, selectDistinct
will ensure the returned results only include unique entries.
Without Distinct
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:follows": { "schema:name": "?nameOfFollower" } }, "select": "?nameOfFollower"}
Example Response
[ "Freddy", "Freddy", "Freddy", "Leticia", "Betty"]
With Distinct
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:follows": { "schema:name": "?nameOfFollower" } }, "selectDistinct": "?nameOfFollower"}
Example Response
[ "Freddy", "Leticia", "Betty"]
Grouped Aggregates
Often, our query results are more useful to us when grouped according to a particular logic variable. If we want to group our results, we can use the groupBy
key to express to Fluree that query results should be grouped accordingly.
Ungrouped
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:follows": "?follows" }, "select": ["?name", "(count ?follows)"]}
Example Response
[ [ [ "Andrew Johnson", "Andrew Johnson", "Andrew Johnson", "Betty", "Leticia" ], 5 ]]
Grouped
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:follows": "?follows" }, "select": ["?name", "(count ?follows)"], "groupBy": "?name"}
Example Response
[ [ "Andrew Johnson", 3 ], [ "Betty", 1 ], [ "Leticia", 1 ]]
Reverse Graph Crawls
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "from": "cookbook/base", "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "friended": { "@reverse": "schema:follows" } }, "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["@id", "schema:name", { "friended": ["@id", "schema:name"] }] }}
Example Response
[ { "@id": "ex:andrew", "friended": [], "schema:name": "Andrew Johnson" }, { "@id": "ex:betty", "friended": { "@id": "ex:andrew", "schema:name": "Andrew Johnson" }, "schema:name": "Betty" }, { "@id": "ex:freddy", "friended": [ { "@id": "ex:andrew", "schema:name": "Andrew Johnson" }, { "@id": "ex:betty", "schema:name": "Betty" }, { "@id": "ex:letty", "schema:name": "Leticia" } ], "schema:name": "Freddy" }, { "@id": "ex:letty", "friended": { "@id": "ex:andrew", "schema:name": "Andrew Johnson" }, "schema:name": "Leticia" }]
Direct Binding Results
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "select": ["?name", "?age", "?type"], "where": { "@id": "?s", "schema:name": "?name", "schema:age": "?age", "@type": "?type" }}
Example Response
[ [ "Andrew Johnson", 35, "schema:Person" ], [ "Betty", 82, "ex:Yeti" ], [ "Freddy", 4, "ex:Yeti" ], [ "Leticia", 2, "ex:Yeti" ]]
Where Clauses
The following query examples demonstrate the various ways to use the where
keyword in Fluree Queries. The where
keyword sets constraints on the data we're querying for, allowing us to do things like filter and join data based on our query needs. We are also able to capture logic variables that can be re-used in the select
statement.
Multiple Constraints
We can constrain the scope of our query in various ways. The following examples demonstrate how to set constraints on such considerations as properties, types, values, or a combination of all of these.
Property Constraint
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name" }, "select": "?name"}
Example Response
[ "Andrew Johnson", "Betty", "Freddy", "Leticia"]
Type Constraint
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "@type": "ex:Yeti" }, "select": "?s"}
Example Response
[ "ex:betty", "ex:letty", "ex:freddy"]
Value Constraint
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "Freddy" }, "select": "?s"}
Example Response
[ "ex:freddy"]
Multiple Constraints
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@type": "ex:Yeti", "ex:verified": true, "schema:name": "?name" }, "select": "?name"}
Example Response
[ "Freddy"]
Optional Constraints
Some constraints might implicitly filter out certain data unless we express those constraints as optional
. Optional constraints return null
values for logic variables when no fact exists, rather than implicitly dropping results with no matching condition.
Without Optional
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@type": "ex:Yeti", "ex:verified": "?verified", "schema:name": "?name" }, "select": ["?name", "?verified"]}
Example Response
[ [ "Freddy", true ]]
With Optional
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": [ { "@id": "?s", "@type": "ex:Yeti", "schema:name": "?name" }, [ "optional", { "@id": "?s", "ex:verified": "?verified" } ] ], "select": ["?name", "?verified"]}
Example Response
[ [ "Betty", null ], [ "Freddy", true ], [ "Leticia", null ]]
Filtering Functions
While we can implicitly filter data with joins expressed via logic variables, we can also express filter
functions that can filter based on various functions and logical expressions. The following examples demonstrate these capabilities.
Unfiltered
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"]}
Example Response
[ [ "Andrew Johnson", 35 ], [ "Betty", 82 ], [ "Freddy", 4 ], [ "Leticia", 2 ]]
Basic Filtering
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": [ { "schema:name": "?name", "schema:age": "?age" }, ["filter", "(> ?age 10)"] ], "select": ["?name", "?age"]}
Example Response
[ [ "Andrew Johnson", 35 ], [ "Betty", 82 ]]
Multiple Filters
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": [ { "schema:name": "?name", "schema:age": "?age" }, ["filter", "(> ?age 10)", "(< ?age 50)"] ], "select": ["?name", "?age"]}
Example Response
[ [ "Andrew Johnson", 35 ]]
Union / Full Outer Join
Generally, joining data via logic variables performs an inner join. If we want to join data that might match a logic variable via a logical OR
as opposed to a logical AND
, we can use the union
keyword. This effectively performs a full outer join. The following examples demonstrate this capability.
Without Union
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "ex:nickname": "?name" }, "select": "?name"}
Example Response
[]
With Union
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": [["union", { "schema:name": "?name" }, { "ex:nickname": "?name" }]], "select": "?name"}
Example Response
[ "Andrew Johnson", "Betty", "Freddy", "Leticia", "Letty"]
Binding Calculated Values
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "select": ["?name", "?age", "?canVote"], "where": [ { "schema:name": "?name", "schema:age": "?age" }, ["bind", "?canVote", "(>= ?age 18)"] ]}
Example Response
[ [ "Andrew Johnson", 35, true ], [ "Betty", 82, true ], [ "Freddy", 4, false ], [ "Leticia", 2, false ]]
Other Keywords
In the examples above, we explored the various keywords and syntax for select
and where
clause expressions. The following keywords exist outside of those clauses at the top-level of a query. They can perform things like grouping, grouped-filtering, ordering, and value-specification.
Grouping Results
Often, our query results are more useful to us when grouped according to a particular logic variable. If we want to group our results, we can use the groupBy
key to express to Fluree that query results should be grouped accordingly.
Ungrouped
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name" }, "select": "?name"}
Example Response
[ "Andrew Johnson", "Betty", "Freddy", "Leticia"]
Grouped
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@type": "?type", "schema:name": "?name" }, "select": ["?type", "?name"], "groupBy": "?type"}
Example Response
[ [ "schema:Person", [ "Andrew Johnson" ] ], [ "ex:Yeti", [ "Betty", "Freddy", "Leticia" ] ]]
Filtering Grouped Results
If we've used groupBy
to group results according to particular binding, we can also filter grouped results with the having
key. If we use filter
to attempt this, the filtering woudl take place across all values, rather than values specific to a particular grouped subset. The following examples demonstrate this capability.
Unfiltered Group
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"], "groupBy": "?name"}
Example Response
[ [ "Andrew Johnson", [ 35 ] ], [ "Betty", [ 82 ] ], [ "Freddy", [ 4 ] ], [ "Leticia", [ 2 ] ]]
Filtered Group
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"], "groupBy": "?name", "having": "(> (avg ?age) 10)"}
Example Response
[ [ "Andrew Johnson", [ 35 ] ], [ "Betty", [ 82 ] ]]
Ordering Results
Often we want our result set ordered according to a particular logic variable (e.g. ?age
or ?priority
). We can use the orderBy
key to accomplish this, and the following examples demonstrate this capability.
Unordered Results
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"]}
Example Response
[ [ "Andrew Johnson", 35 ], [ "Betty", 82 ], [ "Freddy", 4 ], [ "Leticia", 2 ]]
Ordered Results
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"], "orderBy": "?age"}
Example Response
[ [ "Leticia", 2 ], [ "Freddy", 4 ], [ "Andrew Johnson", 35 ], [ "Betty", 82 ]]
Specifying Values
Often we might want to use a logic variable binding in our where
or select
statements, but we also want to constrain the possibility of values for that logic variable to a certain single or multiple value. The following examples demonstrate that set of capabilities.
Without Specified Values
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"]}
Example Response
[ [ "Andrew Johnson", 35 ], [ "Betty", 82 ], [ "Freddy", 4 ], [ "Leticia", 2 ]]
With Single Specified Value
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"], "values": ["?age", [35]]}
Example Response
[ [ "Andrew Johnson", 35 ]]
With Multiple Values on One Property
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"], "values": ["?age", [35, 82]]}
Example Response
[ [ "Andrew Johnson", 35 ], [ "Betty", 82 ]]
With Multiple Properties
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:name": "?name", "schema:age": "?age" }, "select": ["?name", "?age"], "values": [ ["?name", "?age"], [ ["Andrew Johnson", 35], ["Betty", 82] ] ]}
Example Response
[ [ "Andrew Johnson", 35 ], [ "Betty", 82 ]]
Subclasses
Because Fluree is a semantic graph database, we are able to establish relationships not just across data object entities, but across our vocabulary concepts, which themselves are simply data. When those semantic relationships exist, Fluree can make inferences about our data at query-time. The following examples demonstrate how subclass relationships make it possible to query for entities of a parent class and see query results for data that is only explicitly described as an instance of that parent's subclasses.
Add Parent Class
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "rdfs": "http://www.w3.org/2000/01/rdf-schema#" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:Humanoid", "@type": "rdfs:Class" }, { "@id": "ex:Yeti", "rdfs:subClassOf": { "@id": "ex:Humanoid" } }, { "@id": "schema:Person", "rdfs:subClassOf": { "@id": "ex:Humanoid" } } ]}
Parent Class Query
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "@type": "ex:Humanoid" }, "select": { "?s": ["*"] }}
Equivalent Property
Because Fluree is a semantic graph database, we are able to establish relationships not just across data object entities, but across our vocabulary concepts, which themselves are simply data. When those semantic relationships exist, Fluree can make inferences about our data at query-time. The following examples demonstrate how equivalent proeprty
relationships make it possible to query for values on particular properties, but see results for symmetically- or transitively-qualified equivalent properties.
Populate test data
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "foaf": "http://xmlns.com/foaf/0.1/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:andrew", "schema:givenName": "Andrew" }, { "@id": "ex:freddy", "foaf:name": "Freddy" }, { "@id": "ex:letty", "ex:firstName": "Leticia" }, { "@id": "ex:betty", "ex:firstName": "Betty" } ]}
Map equivalent properties
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "schema": "http://schema.org/", "ex": "http://example.org/", "foaf": "http://xmlns.com/foaf/0.1/", "owl": "http://www.w3.org/2002/07/owl#" }, "ledger": "cookbook/base", "insert": [ { "@id": "schema:givenName", "@type": "rdf:Property" }, { "@id": "ex:firstName", "@type": "rdf:Property", "owl:equivalentProperty": { "@id": "schema:givenName" } }, { "@id": "foaf:name", "@type": "rdf:Property", "owl:equivalentProperty": { "@id": "ex:firstName" } } ]}
Query for property defined to be equivalent
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "schema:givenName": "?name" }, "selectDistinct": "?name"}
Example Response
[ "Betty", "Leticia", "Andrew", "Freddy"]
Query for symmetric property
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/" }, "from": "cookbook/base", "where": { "ex:firstName": "?name" }, "selectDistinct": "?name"}
Example Response
[ "Betty", "Leticia", "Andrew", "Freddy"]
Query for transitive properties
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "foaf": "http://xmlns.com/foaf/0.1/" }, "from": "cookbook/base", "where": { "foaf:name": "?name" }, "select": "?name"}
Example Response
[ "Betty", "Leticia", "Andrew", "Freddy"]
Querying with the graph crawl
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "foaf": "http://xmlns.com/foaf/0.1/" }, "from": "cookbook/base", "where": { "@id": "?s", "foaf:name": "?name" }, "select": { "?s": ["*"] }}
Example Response
[ { "@id": "ex:betty", "@type": "ex:Yeti", "ex:firstName": "Betty", "schema:age": 82, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:firstName": "Leticia", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }, { "@id": "ex:andrew", "@type": "schema:Person", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": "Andrew Johnson" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "foaf:name": "Freddy", "schema:age": 4, "schema:name": "Freddy" }]
Aliasing Queries (Property #1)
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "foaf": "http://xmlns.com/foaf/0.1/", "schema:givenName": "ex:firstName" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:givenName": "?name" }, "select": { "?s": ["*"] }}
Example Response
[ { "@id": "ex:betty", "@type": "ex:Yeti", "schema:age": 82, "schema:follows": { "@id": "ex:freddy" }, "schema:givenName": "Betty", "schema:name": "Betty" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy" }, "schema:givenName": "Leticia", "schema:name": "Leticia" }, { "@id": "ex:andrew", "@type": "schema:Person", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": "Andrew Johnson" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "foaf:name": "Freddy", "schema:age": 4, "schema:name": "Freddy" }]
SPARQL
Because Fluree is a semantic graph database, specifically a JSON-LD database, it is capable of understanding and resolving queries expressed via SPARQL (the SPARQL Protocol and RDF Query Language). The following examples demonstrate some basic SPARQL queries. (NOTE: these requests are expressed as _Content-Type: application/sparql-query_
and not _application-json_
)
Basic SPARQL Query
Request Headers
Content-Type: application/sparql-query
Request Body
/* Action: POST Endpoint: /query*/PREFIX ex: <http://example.org/>PREFIX schema:<http://schema.org/>SELECT ?yeti ?nameFROM <cookbook/base>WHERE { ?yeti <@type> ex:Yeti; schema:name ?name.}
Example Response
[ [ "ex:betty", "Betty" ], [ "ex:letty", "Leticia" ], [ "ex:freddy", "Freddy" ]]
Average Aggregate Query
Request Headers
Content-Type: application/sparql-query
Request Body
/* Action: POST Endpoint: /query*/PREFIX schema:<http://schema.org/>SELECT (AVG(?age) AS ?avgAge)FROM <cookbook/base>WHERE { ?s schema:age ?age.}
Example Response
[ [ 30.75 ]]
Count Aggregate Query
Request Headers
Content-Type: application/sparql-query
Request Body
/* Action: POST Endpoint: /query*/PREFIX schema:<http://schema.org/>SELECT (COUNT(?friend) AS ?numFriends)FROM <cookbook/base>WHERE { ?s schema:follows ?friend.}
Example Response
[ [ 5 ]]
[BUG] Filter Query
Request Headers
Content-Type: application/sparql-query
Request Body
/* Action: POST Endpoint: /query*/PREFIX schema:<http://schema.org/>SELECT ?s ?ageFROM <cookbook/base>WHERE { ?s schema:age ?age . FILTER(?age > 10) .}
Transacting
Inserting Data
When transacting data, we use either the insert
or delete
keys (or a combination of the two to express a kind of update). The following examples demonstrate how to use the insert
keyword to assert new facts against your current data state.
Creating a Ledger
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /create*/{ "@context": { "ex": "http://example.org/" }, "ledger": "cookbook/create", "insert": [ { "@id": "ex:note1", "@type": "ex:Note", "ex:description": "To create a ledger, you need to provide some initial data, just like you would make an 'initial commit' when using git" } ]}
Inserting a Single Record
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:fluree", "@type": "schema:Organization", "schema:description": "We ❤️ Data" } ]}
Inserting Multiple Records
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:w3c", "@type": "schema:Organization", "schema:description": "We ❤️ Internet" }, { "@id": "ex:mosquitos", "@type": "ex:Monster", "schema:description": "We ❤️ Human Blood" } ]}
Deleting Data
When transacting data, we use either the insert
or delete
keys (or a combination of the two to express a kind of update). The following examples demonstrate how to use the delete
keyword to retract facts from your current data state.
[DO FIRST] Add Data to Delete
We include the following new data insert
so that we have sufficient data to subsequently delete
in the examples that follow this one.
Add Data to Delete
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:fluree", "@type": "schema:Organization", "schema:description": "We ❤️ Data" }, { "@id": "ex:w3c", "@type": "schema:Organization", "schema:description": "We ❤️ Internet" }, { "@id": "ex:mosquitos", "@type": "ex:Monster", "schema:description": "We ❤️ Human Blood" } ]}
Deleting Entire Subjects
Often we want to not simply delete an individual set of facts, but every fact across a subject. In SQL, this effectively drops a row, but in JSON-LD / RDF, subjects (aka rows) only exist insofar as they have property-object facts expressed for them. Therefore, deletions of entire subjects are effectively deletions of every property-object fact for that subject. The following examples demonstrate how to express these kinds of delete
statements.
Querying Before Delete
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:description": "?o" }, "selectDistinct": { "?s": ["*"] }}
Example Response
[ { "@id": "ex:fluree", "@type": "schema:Organization", "schema:description": "We ❤️ Data" }, { "@id": "ex:mosquitos", "@type": "ex:Monster", "schema:description": "We ❤️ Human Blood" }, { "@id": "ex:w3c", "@type": "schema:Organization", "schema:description": "We ❤️ Internet" }]
Deleting All Facts on Subject
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/" }, "ledger": "cookbook/base", "where": { "@id": "ex:mosquitos", "?p": "?o" }, "delete": { "@id": "ex:mosquitos", "?p": "?o" }}
Querying After Delete
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:description": "?o" }, "selectDistinct": { "?s": ["*"] }}
Example Response
[ { "@id": "ex:fluree", "@type": "schema:Organization", "schema:description": "We ❤️ Data" }, { "@id": "ex:w3c", "@type": "schema:Organization", "schema:description": "We ❤️ Internet" }]
Deleting Particular Facts
When we want to keep some facts about a subject in our data state, but want to delete or update particular facts, we can use where
clauses to subselect current data state for a subject, and then use the delete
clause to retract those particular data state elements. The following examples demonstrate how to delete particular facts in this way.
Deleting Values on a Single Predicate for a Single Subject
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "where": { "@id": "ex:mosquitos", "schema:description": "?o" }, "delete": { "@id": "ex:mosquitos", "schema:description": "?o" }}
Deleting All Subjects with a Particular Property
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "schema": "http://schema.org/" }, "ledger": "cookbook/base", "where": { "@id": "?s", "schema:description": "?x", "?p": "?o" }, "delete": { "@id": "?s", "?p": "?o" }}
Deleting All Subjects with a Particular Value on a Particular Property
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "schema": "http://schema.org/" }, "ledger": "cookbook/base", "where": { "@id": "?s", "@type": "schema:Organization", "?p": "?o" }, "delete": { "@id": "?s", "?p": "?o" }}
Updating Data
When we think about updating data, we typically mean that we want to replace an existing value or set of values with updated data state. In JSON-LD, this is expressed as a delete
+ insert
. The following examples demonstrate how to perform these kinds of udpates.
[DO FIRST] Add Data to Update
We include the following new data insert
so that we have sufficient data to subsequently delete
in the examples that follow this one.
Add Data to Update
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:fluree", "@type": "schema:Organization", "schema:description": "We ❤️ Data" }, { "@id": "ex:w3c", "@type": "schema:Organization", "schema:description": "We ❤️ Internet" }, { "@id": "ex:mosquitos", "@type": "ex:Monster", "schema:description": "We ❤️ Human Blood" } ]}
Update
Update Regardless of Value
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "where": { "@id": "ex:mosquitos", "schema:description": "?o" }, "delete": { "@id": "ex:mosquitos", "schema:description": "?o" }, "insert": { "@id": "ex:mosquitos", "schema:description": "We ❤️ All Blood" }}
Compare and Swap Update (Only Update if Current Value is X)
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "schema": "http://schema.org/" }, "ledger": "cookbook/base", "where": { "@id": "?s", "schema:description": "We ❤️ All Blood" }, "delete": { "@id": "?s", "schema:description": "We ❤️ All Blood" }, "insert": { "@id": "?s", "schema:description": ["We ❤️ Human Blood", "We ❤️ Animal Blood"] }}
Schema
Default Flexibility
By default, JSON-LD is flexible by design when it comes to schema restrictions. The very premise of JSON-LD or RDF data is that we are not data's only publishers, and we can leverage and join data across the web's data systems. With that in mind, if data were restricted to schema considerations by default, we may not be able to easily merge and join our data. The following examples demonstrate Fluree's support for multiple data types or class types (aka tables in the SQL world).
Multiple @type Values
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /create*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/multiple-type-values", "insert": { "@id": "ex:nessie", "@type": ["schema:Thing", "ex:Cryptid", "ex:SeaMonster"], "ex:name": "Nessie", "ex:age": 1458 }}
Mixed Data Types
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /create*/{ "@context": { "ex": "http://example.org/" }, "ledger": "cookbook/data-types", "insert": { "@id": "ex:nessie", "@type": "ex:Cryptid", "ex:name": "Nessie", "ex:age": [1458, "unknown"] }}
SHACL
While JSON-LD's default posture of flexibility can be of great use when working with multiple producers of data, we may have application obligations to validate our data against certain schema or data shape concerns. The semantic standard for expressing these concerns is SHACL (i.e. the Shapes Contraint Language). The following examples demonstrate how to use SHACL to express constraints like required properties, data types, cardinality, and closed class shapes.
Required Properties
We can use SHACL to express that certain properties may be required on instances of, say, a particular class. The following examples demonstrate how to express required property constraints with SHACL.
Create Ledger w/ SHACL Rule
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /create*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "sh": "http://www.w3.org/ns/shacl#" }, "ledger": "cookbook/required-property", "insert": { "@id": "ex:UserShape", "@type": ["sh:NodeShape"], "sh:targetClass": { "@id": "ex:User" }, "sh:property": [ { "sh:path": { "@id": "schema:name" }, "sh:minCount": 1 } ] }}
[FAILS] Transact Without Required Property
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/required-property", "insert": { "@id": "ex:dolly", "@type": ["ex:User"], "schema:age": 77 }}
Transact With Required Property
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/required-property", "insert": { "@id": "ex:dolly", "@type": ["ex:User"], "schema:name": "Dolly Parton" }}
Data Type Constraints
We can use SHACL to express that certain properties should be restricted to certain data types. The following examples demonstrate how to express data type constraints with SHACL.
Create Ledger w/ SHACL Rule
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /create*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "sh": "http://www.w3.org/ns/shacl#", "xsd": "http://www.w3.org/2001/XMLSchema#" }, "ledger": "cookbook/data-type", "insert": { "@id": "ex:UserShape", "@type": ["sh:NodeShape"], "sh:targetClass": { "@id": "ex:User" }, "sh:property": [ { "sh:path": { "@id": "schema:name" }, "sh:datatype": { "@id": "xsd:string" } } ] }}
[FAILS] Transact Invalid Data Type
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/data-type", "insert": { "@id": "ex:dolly", "@type": "ex:User", "schema:name": [9, 5] }}
Transact Valid Data Type
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/data-type", "insert": { "@id": "ex:dolly", "@type": ["ex:User"], "schema:name": "Dolly Parton" }}
Cardinality
We can use SHACL to express that certain properties should be restricted to single-cardinality (where a default, flexible position is that properties can take any number of values on a particular subject). The following examples demonstrate how to express cardinality constraints with SHACL.
Create Ledger w/ SHACL Rule
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /create*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "sh": "http://www.w3.org/ns/shacl#" }, "ledger": "cookbook/cardinality", "insert": { "@id": "ex:UserShape", "@type": ["sh:NodeShape"], "sh:targetClass": { "@id": "ex:User" }, "sh:property": [ { "sh:path": { "@id": "schema:name" }, "sh:maxCount": 1 } ] }}
Transact Correct Cardinality
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/cardinality", "insert": { "@id": "ex:dolly", "@type": ["ex:User"], "schema:name": "Dolly Parton" }}
[FAILS] Transact Incorrect Cardinality
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/cardinality", "insert": { "@id": "ex:dolly", "schema:name": ["Dolly Parton", "Queen of Country"] }}
Class / Property Constraints (Closed Shape)
We can use SHACL to express that certain classes should be restricted to a close set of properties. The following examples demonstrate how to express closed shape constraints with SHACL.
Create Ledger w/ SHACL Rule
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /create*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "sh": "http://www.w3.org/ns/shacl#", "xsd": "http://www.w3.org/2001/XMLSchema#" }, "ledger": "cookbook/closed-shape", "insert": { "@id": "ex:UserShape", "@type": ["sh:NodeShape"], "sh:targetClass": { "@id": "ex:User" }, "sh:property": [ { "sh:path": { "@id": "schema:name" }, "sh:datatype": { "@id": "xsd:string" } } ], "sh:ignoredProperties": [{ "@id": "@type" }], "sh:closed": true }}
Transact with Valid Shape
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/closed-shape", "insert": { "@id": "ex:dolly", "@type": ["ex:User"], "schema:name": "Dolly Parton" }}
[FAILS] Transact with Excluded Properties
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/closed-shape", "insert": { "@id": "ex:dolly", "@type": ["ex:User"], "schema:name": "Dolly Parton", "ex:quotes": ["If you want the rainbow, you gotta put up with the rain!"] }}
JSON-LD Utilities
Using Context
In JSON-LD and RDF, entities and vocabulary elements are ideally expressed with globally unique IRIs, making it possible to share open-system meaning around our vocabularies and our data. While this is a tremendous value, it is often tedious to work with fully-qualified IRIs. The notion of context (or @context
) allows us to simplify these IRIs and use shorthand to better represent the meaning of our classes, properties, and data entities.
The @context
Keyword
The following examples demonstrate how to use the @context
keyword in queries and transactions to simplify both our requests against Fluree as well as the result sets that our queries return.
Queries
The following examples demonstrate how to query (and see query results) that leverage a @context
or default context.
Without @context
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "from": "cookbook/base", "where": { "@id": "?s", "http://schema.org/name": "Freddy" }, "select": { "?s": ["*"] }}
Example Response
[ { "@id": "http://example.org/freddy", "@type": "http://example.org/Yeti", "http://example.org/verified": true, "http://schema.org/age": 4, "http://schema.org/name": "Freddy", "http://xmlns.com/foaf/0.1/name": "Freddy" }]
With @context
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "from": "cookbook/base", "@context": { "myschema": "http://schema.org/", "myexample": "http://example.org/", "myrdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#" }, "where": { "@id": "?s", "myschema:name": "Freddy" }, "select": { "?s": ["*"] }}
Example Response
[ { "@id": "myexample:freddy", "@type": "myexample:Yeti", "http://xmlns.com/foaf/0.1/name": "Freddy", "myexample:verified": true, "myschema:age": 4, "myschema:name": "Freddy" }]
Transactions
The following examples demonstrate how to submit transactions that leverage a @context
or default context.
Using @context to Specify @type on Properties
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "schema:follows": { "@type": "@id" } }, "ledger": "cookbook/base", "insert": { "@id": "ex:freddy", "schema:follows": ["ex:andrew"] }}
Using @base & @vocab
In addition to the @context
examples above, we have particular keywords--specifically @base
and @vocab
--which make it possible to set prefixes for all of our entity IRIs or vocabulary elements, respectively. The following examples demonstrate how to use @base
and @vocab
.
Without @base or @vocab
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/" }, "ledger": "cookbook/base", "insert": { "@id": "ex:nessie", "@type": "ex:SeaMonster", "ex:isScary": false }}
With @base
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "@base": "http://example.org/" }, "ledger": "cookbook/base", "insert": { "@id": "nessie", "@type": "ex:SeaMonster", "ex:isScary": false }}
With @base and @vocab
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "@base": "http://example.org/", "@vocab": "http://example.org/terms/" }, "ledger": "cookbook/base", "insert": { "@id": "nessie", "@type": "SeaMonster", "isScary": false }}
Using @id
While vanilla JSON does not have a universal concept for the identity of a particular object/entity, JSON-LD requires such a concept, as part of its adherence to RDF graph serialization. We can use @id
to set IRI identifiers for entities, and then leverage those identifiers in queries and transactions. The following examples demonstrate how to do this.
Query by @id
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "select": { "ex:freddy": ["*"] }}
Example Response
[ { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "foaf:name": "Freddy", "schema:age": 4, "schema:name": "Freddy" }]
Using @id To Specify a String as an IRI (Graph Reference)
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": { "@id": "ex:fluree", "@type": "schema:Organization", "schema:description": "We ❤️ Data", "schema:employee": { "@id": "ex:andrew" } }}
Using @id To Specify a String as an IRI (via @context)
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "schema:employee": { "@type": "@id" } }, "ledger": "cookbook/base", "insert": { "@id": "ex:fluree", "@type": "schema:Organization", "schema:description": "We ❤️ Data", "schema:employee": "ex:andrew" }}
Using @type
Where SQL data has tables to represent an entity's adherence to a particular data concept, JSON-LD represents this via the notions of classes. Entities can be associated with one or more classes, expressed via @type
. Being associated with a class can come with schema restrictions (or not). It makes it easier to query by class, and also makes it possible to infer class relationships, when those classes themselves are related (for example, a class of Yeti
might be a sub-class of Humanoid
).
The following examples demonstrate how to use @type
to work with classes in Fluree.
Class / Subclass Inferencing
Because Fluree is a semantic graph database, we are able to establish relationships not just across data object entities, but across our vocabulary concepts, which themselves are simply data. When those semantic relationships exist, Fluree can make inferences about our data at query-time. The following examples demonstrate how subclass relationships make it possible to query for entities of a parent class and see query results for data that is only explicitly described as an instance of that parent's subclasses.
Add Parent Class
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "rdfs": "http://www.w3.org/2000/01/rdf-schema#" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:Humanoid", "@type": "rdfs:Class" }, { "@id": "ex:Yeti", "rdfs:subClassOf": { "@id": "ex:Humanoid" } }, { "@id": "schema:Person", "rdfs:subClassOf": { "@id": "ex:Humanoid" } } ]}
Parent Class Query
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "@type": "ex:Humanoid" }, "select": { "?s": ["*"] }}
Query by @type
When entities are related to a particular class (or to multiple classes), it becomes possible to query for them by leveraging @type
. The following example demonstrates how this can be accomplished.
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "@type": "ex:Yeti" }, "select": { "?s": ["*"] }}
Example Response
[ { "@id": "ex:betty", "@type": "ex:Yeti", "ex:firstName": "Betty", "schema:age": 82, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:firstName": "Leticia", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "foaf:name": "Freddy", "schema:age": 4, "schema:name": "Freddy" }]
Time Travel
Time Travel Queries
Because each data commit in Fluree is immutable and addressed by the specific moment in time when it took place, it is possible to leverage the concept of time when querying against our data state. In fact, each individual ledger contains an immutable and queryable graph database for every single commit against that ledger.
We can specify any query against a moment in time--and we call this a time-travel query. The following examples demonstrate how to issue time-travel queries in Fluree.
With ISO Strings
Time Travel Queries can be expressed in terms of a particular commit number or in terms of a given dateTime instant. We specifically use ISO 8601 dateTime strings to express the latter. The following examples demonstrate how to issue time-travel queries with ISO strings.
Update Data to Use For Time Travel Query
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:yeti-mutation-event", "schema:description": "All humans are yetis now!!" }, { "@id": "ex:andrew", "@type": "ex:Yeti", "schema:name": "Andy the Yeti" } ]}
Query Current Data State
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "select": { "ex:andrew": ["*"] }}
Query at Previous ISO Time
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "select": { "ex:andrew": ["*"] }, "t": "2023-11-01T18:46:44.823Z"}
With Commit Numbers
Time Travel Queries can be expressed in terms of a particular commit number or in terms of a given dateTime instant. The following examples demonstrate how to issue time-travel queries with commit numbers.
Update Data to Use For Time Travel Query
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:yeti-mutation-event", "schema:description": "All humans are yetis now!!" }, { "@id": "ex:andrew", "@type": "ex:Yeti", "schema:name": "Andy the Yeti" } ]}
Query Current Data State Copy
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "select": { "ex:andrew": ["*"] }}
Query at Previous Commit Number
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "from": "cookbook/base", "select": { "ex:andrew": ["*"] }, "t": 1}
History API
Because each data commit in Fluree is immutable and addressed by the specific moment in time when it took place, it is possible to leverage the concept of time when querying against our data state. In fact, each individual ledger contains an immutable and queryable graph database for every single commit against that ledger.
If we want to audit data state changes over time, for a particular subject or for subjects matching particular data shapes, we can use the History API to accomplish this. The following examples demonstrate how to use the History API to audit our data or our individual commits.
Constraining Results
When using the History API, we can constrain our data audits to particular entities or to entities matching particular data conditions. The following examples demonstrate how to use the History API with particular result constraints.
Update Data to Use For History Query
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:yeti-mutation-event", "schema:description": "All humans are yetis now!!" }, { "@id": "ex:andrew", "@type": "ex:Yeti", "schema:name": "Andy the Yeti" } ]}
History of Specific Subject
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": "ex:andrew", "t": { "from": 1 }}
Example Response
[ { "f:assert": [ { "@id": "ex:andrew", "@type": "schema:Person", "id": "ex:andrew", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:name": "Andrew Johnson" } ], "f:retract": [], "f:t": 1 }, { "f:assert": [ { "id": "ex:andrew", "schema:givenName": "Andrew" } ], "f:retract": [], "f:t": 2 }, { "f:assert": [ { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 7 }, { "f:assert": [ { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 8 }]
History of Specific Subject and Specific Property
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": ["ex:andrew", "schema:name"], "t": { "from": 1 }}
Example Response
[ { "f:assert": [ { "id": "ex:andrew", "schema:name": "Andrew Johnson" } ], "f:retract": [], "f:t": 1 }, { "f:assert": [ { "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 7 }, { "f:assert": [ { "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 8 }]
History of Specific Property Regardless of Subject
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": [null, "schema:name"], "t": { "from": 1 }}
Example Response
[ { "f:assert": [ { "id": "ex:andrew", "schema:name": "Andrew Johnson" }, { "id": "ex:betty", "schema:name": "Betty" }, { "id": "ex:freddy", "schema:name": "Freddy" }, { "id": "ex:letty", "schema:name": "Leticia" } ], "f:retract": [], "f:t": 1 }, { "f:assert": [ { "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 7 }, { "f:assert": [ { "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 8 }]
History of Specific Property And Specific Value Regardless of Subject
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": [null, "schema:name", "Andrew Johnson"], "t": { "from": 1 }}
Example Response
[ { "f:assert": [ { "id": "ex:andrew", "schema:name": "Andrew Johnson" } ], "f:retract": [], "f:t": 1 }]
Constraining Time
When using the History API, we can constrain our data audits to moments or to ranges of time. The following examples demonstrate how to use the History API with particular time constraints.
Update Data to Use For History Query
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:yeti-mutation-event", "schema:description": "All humans are yetis now!!" }, { "@id": "ex:andrew", "@type": "ex:Yeti", "schema:name": "Andy the Yeti" } ]}
History Until/To Specific Commit
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": "ex:andrew", "t": { "to": 2 }}
Example Response
[ { "f:assert": [ { "@id": "ex:andrew", "@type": "schema:Person", "id": "ex:andrew", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:name": "Andrew Johnson" } ], "f:retract": [], "f:t": 1 }, { "f:assert": [ { "id": "ex:andrew", "schema:givenName": "Andrew" } ], "f:retract": [], "f:t": 2 }]
History At Specific Commit
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": "ex:andrew", "t": { "at": 1 }}
Example Response
[ { "f:assert": [ { "@id": "ex:andrew", "@type": "schema:Person", "id": "ex:andrew", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:name": "Andrew Johnson" } ], "f:retract": [], "f:t": 1 }]
History Since/From Specific Commit
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": "ex:andrew", "t": { "from": 1 }}
Example Response
[ { "f:assert": [ { "@id": "ex:andrew", "@type": "schema:Person", "id": "ex:andrew", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:name": "Andrew Johnson" } ], "f:retract": [], "f:t": 1 }, { "f:assert": [ { "id": "ex:andrew", "schema:givenName": "Andrew" } ], "f:retract": [], "f:t": 2 }, { "f:assert": [ { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 7 }, { "f:assert": [ { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 8 }]
History Within a Range of DateTime Values
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": "ex:andrew", "t": { "from": "2023-11-08T16:24:25.802Z", "to": "2023-11-08T16:29:25.802Z" }}
Example Response
[ { "f:assert": [ { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 7 }, { "f:assert": [ { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:retract": [], "f:t": 8 }]
History Within a Range of Specific Commits
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": "ex:andrew", "t": { "from": 1, "to": 2 }}
Example Response
[ { "f:assert": [ { "@id": "ex:andrew", "@type": "schema:Person", "id": "ex:andrew", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:name": "Andrew Johnson" } ], "f:retract": [], "f:t": 1 }, { "f:assert": [ { "id": "ex:andrew", "schema:givenName": "Andrew" } ], "f:retract": [], "f:t": 2 }]
Including Commit Details
When using the History API, we can optionally include a flag to include the commit details for each commit returned from the History API. The following examples demonstrate how we can use commit-details
to flag our interest in this additional metadata.
Update Data to Use For History Query
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/" }, "ledger": "cookbook/base", "insert": [ { "@id": "ex:yeti-mutation-event", "schema:description": "All humans are yetis now!!" }, { "@id": "ex:andrew", "@type": "ex:Yeti", "schema:name": "Andy the Yeti" } ]}
History with Commit Details
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "history": "ex:andrew", "t": { "from": 1 }, "commit-details": true}
Example Response
[ { "f:assert": [ { "@id": "ex:andrew", "@type": "schema:Person", "id": "ex:andrew", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:name": "Andrew Johnson" } ], "f:commit": { "@id": "fluree:commit:sha256:bf3w4lfsgjcumhgzalmtoyysyjpp5ns3thrqrqpyzmfd3vmre6m6", "f:address": "fluree:file://cookbook/base/main/commit/a3e94f6ac348e5eaada827aa896b3c2ecdb62861d7bb91622d6c132654a85ef2.json", "f:alias": "cookbook/base", "f:branch": "main", "f:data": { "f:address": "fluree:file://cookbook/base/main/commit/bf16b05ca11c84d00c678a5e9fe25466e81bd9689d27eecc62d9a9d95fc48a83.json", "f:assert": [ { "@type": "schema:Person", "id": "ex:andrew", "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:name": "Andrew Johnson" }, { "@type": "ex:Yeti", "id": "ex:betty", "schema:age": 82, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" }, { "@type": "ex:Yeti", "ex:nickname": "Letty", "id": "ex:letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }, { "@type": "ex:Yeti", "ex:verified": true, "id": "ex:freddy", "schema:age": 4, "schema:name": "Freddy" } ], "f:flakes": 34, "f:retract": [], "f:size": 2251, "f:t": 1 }, "f:defaultContext": { "@id": "fluree:context:b16965119d05f836e6a1e221730adf5db0b7212c831a3f83aa52e449fe7ce6ef" }, "f:time": 1699459809478, "f:v": 0 }, "f:retract": [], "f:t": 1 }, { "f:assert": [ { "id": "ex:andrew", "schema:givenName": "Andrew" } ], "f:commit": { "@id": "fluree:commit:sha256:bf37jqhwcluspckl4umg7icphtnboqr37ygeftzn47alko44lpcc", "f:address": "fluree:file://cookbook/base/main/commit/f52065581b8cf8b0d7fc824e2c5d5b741ca62d8a383568760e8e86f397153226.json", "f:alias": "cookbook/base", "f:branch": "main", "f:data": { "f:address": "fluree:file://cookbook/base/main/commit/e8ac97e208e5a788aa81201d9d7240be7d915d4d900e3fe8bccf538aa024f09f.json", "f:assert": [ { "id": "ex:andrew", "schema:givenName": "Andrew" }, { "ex:firstName": "Betty", "id": "ex:betty" }, { "ex:firstName": "Leticia", "id": "ex:letty" }, { "foaf:name": "Freddy", "id": "ex:freddy" } ], "f:flakes": 69, "f:previous": { "@id": "fluree:db:sha256:bbetc3wntccxlmngw24hjiybrhhqdkabssmfpp3qd6kzttwfo6sdr" }, "f:retract": [], "f:size": 5955, "f:t": 2 }, "f:defaultContext": { "@id": "fluree:context:b16965119d05f836e6a1e221730adf5db0b7212c831a3f83aa52e449fe7ce6ef" }, "f:previous": { "@id": "ex:yeti-mutation-event" }, "f:time": 1699460631766, "f:v": 0 }, "f:retract": [], "f:t": 2 }, { "f:assert": [ { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:commit": { "@id": "fluree:commit:sha256:bt2ee7btm7wbvlyn6u46f5apc6ls2fsvsltvs7zqe235eqzdlv53", "f:address": "fluree:file://cookbook/base/main/commit/471ccf7c0556245de7ae0bd2774908a1b677646252d7e36405fb6c70666eeae2.json", "f:alias": "cookbook/base", "f:branch": "main", "f:data": { "f:address": "fluree:file://cookbook/base/main/commit/a1a37538a92ec5bb76794348f35e07d85b7719bdadaecf81fa067aa64d407596.json", "f:assert": [ { "id": "ex:yeti-mutation-event", "schema:description": "All humans are yetis now!!" }, { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:flakes": 175, "f:previous": { "@id": "fluree:db:sha256:bqf2ccapdj4bcb3zml7exwhxmfpaohrx7l4w6sdjsqazghlpfdni" }, "f:retract": [], "f:size": 15435, "f:t": 7 }, "f:defaultContext": { "@id": "fluree:context:b16965119d05f836e6a1e221730adf5db0b7212c831a3f83aa52e449fe7ce6ef" }, "f:previous": { "@id": "ex:yeti-mutation-event" }, "f:time": 1699460884560, "f:v": 0 }, "f:retract": [], "f:t": 7 }, { "f:assert": [ { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:commit": { "@id": "fluree:commit:sha256:bbqa5mugvma5t6vqh7mod3ic5so4socmokp7rmdfjuhfw67jd4vjh", "f:address": "fluree:file://cookbook/base/main/commit/62a83212d9705f677e90514782756243e837b707a1028f9e1109946961613610.json", "f:alias": "cookbook/base", "f:branch": "main", "f:data": { "f:address": "fluree:file://cookbook/base/main/commit/9cdc867e47c2dff76a79b921d7fda4690f4334a7bf32e2bce9e88571c8dbd39b.json", "f:assert": [ { "id": "ex:yeti-mutation-event", "schema:description": "All humans are yetis now!!" }, { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:flakes": 193, "f:previous": { "@id": "fluree:db:sha256:bvswubri4d6tweapnpzi4qgdjipjcbhsyezs25sqnw327m75dmyh" }, "f:retract": [], "f:size": 17087, "f:t": 8 }, "f:defaultContext": { "@id": "fluree:context:b16965119d05f836e6a1e221730adf5db0b7212c831a3f83aa52e449fe7ce6ef" }, "f:previous": { "@id": "ex:yeti-mutation-event" }, "f:time": 1699460897477, "f:v": 0 }, "f:retract": [], "f:t": 8 }]
Latest Commit Details
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /history*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "from": "cookbook/base", "t": { "from": "latest" }, "commit-details": true}
Example Response
[ { "f:commit": { "@id": "fluree:commit:sha256:bbqa5mugvma5t6vqh7mod3ic5so4socmokp7rmdfjuhfw67jd4vjh", "f:address": "fluree:file://cookbook/base/main/commit/62a83212d9705f677e90514782756243e837b707a1028f9e1109946961613610.json", "f:alias": "cookbook/base", "f:branch": "main", "f:data": { "f:address": "fluree:file://cookbook/base/main/commit/9cdc867e47c2dff76a79b921d7fda4690f4334a7bf32e2bce9e88571c8dbd39b.json", "f:assert": [ { "id": "ex:yeti-mutation-event", "schema:description": "All humans are yetis now!!" }, { "@type": "ex:Yeti", "id": "ex:andrew", "schema:name": "Andy the Yeti" } ], "f:flakes": 193, "f:previous": { "@id": "fluree:db:sha256:bvswubri4d6tweapnpzi4qgdjipjcbhsyezs25sqnw327m75dmyh" }, "f:retract": [], "f:size": 17087, "f:t": 8 }, "f:defaultContext": { "@id": "fluree:context:b16965119d05f836e6a1e221730adf5db0b7212c831a3f83aa52e449fe7ce6ef" }, "f:previous": { "@id": "ex:yeti-mutation-event" }, "f:time": 1699460897477, "f:v": 0 } }]
Policy
Root Access
Fluree makes it possible to enforce data-centric security. By that we mean that we can express data access policy directly as data, and co-resident with our data, such that data in Fluree is empowered to evaluate its own access. The concept of data-centric security is crucial in enabling our data to participate with other datasets, to support multiple data-producers and data-consumers, and to enable virtualized queries across databases without risking data leaks whatsoever.
The following examples demonstrate how something like root
access could be granted to particular roles or to particular identities.
Adding Root Access Policy
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "ledger": "cookbook/base", "insert": { "@id": "ex:rootPolicy", "@type": ["f:Policy"], "f:targetNode": { "@id": "f:allNodes" }, "f:allow": [ { "@id": "ex:rootAccessAllow", "f:targetRole": { "@id": "ex:rootRole" }, "f:action": [{ "@id": "f:view" }, { "@id": "f:modify" }] } ] }}
Adding Root Identity
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "ledger": "cookbook/base", "insert": { "@id": "did:fluree:TfCzWTrXqF16hvKGjcYiLxRoYJ1B8a6UMH6", "ex:user": { "@id": "ex:andrew" }, "f:role": { "@id": "ex:rootRole" } }}
Query as Root Role
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*"] }, "opts": { "role": "http://example.org/rootRole" }}
Example Response
[ { "@id": "ex:andrew", "@type": [ "ex:Yeti", "schema:Person" ], "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": [ "Andrew Johnson", "Andy the Yeti" ] }, { "@id": "ex:andrew", "@type": [ "ex:Yeti", "schema:Person" ], "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": [ "Andrew Johnson", "Andy the Yeti" ] }, { "@id": "ex:betty", "@type": "ex:Yeti", "ex:firstName": "Betty", "schema:age": 82, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "foaf:name": "Freddy", "schema:age": 4, "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:firstName": "Leticia", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }]
Query as Root Identity
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*"] }, "opts": { "did": "did:fluree:TfCzWTrXqF16hvKGjcYiLxRoYJ1B8a6UMH6" }}
Example Response
[ { "@id": "ex:andrew", "@type": [ "ex:Yeti", "schema:Person" ], "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": [ "Andrew Johnson", "Andy the Yeti" ] }, { "@id": "ex:andrew", "@type": [ "ex:Yeti", "schema:Person" ], "schema:age": 35, "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": [ "Andrew Johnson", "Andy the Yeti" ] }, { "@id": "ex:betty", "@type": "ex:Yeti", "ex:firstName": "Betty", "schema:age": 82, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "foaf:name": "Freddy", "schema:age": 4, "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:firstName": "Leticia", "ex:nickname": "Letty", "schema:age": 2, "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }]
Limiting Access
Fluree makes it possible to enforce data-centric security. By that we mean that we can express data access policy directly as data, and co-resident with our data, such that data in Fluree is empowered to evaluate its own access. The concept of data-centric security is crucial in enabling our data to participate with other datasets, to support multiple data-producers and data-consumers, and to enable virtualized queries across databases without risking data leaks whatsoever.
The following examples demonstrate how to express limited access to our data--not just to particular roles or identities--but also how we can leverage relationships in the data itself to determine access in powerful, granular ways (NOTE: we refer to this as RelBAC, or Relationship-Based Access Control)
Adding Limited Access Policy
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "ledger": "cookbook/base", "insert": { "@id": "ex:yetiPolicy", "@type": ["f:Policy"], "f:targetClass": { "@id": "ex:Yeti" }, "f:allow": [ { "@id": "ex:yetiViewAllow", "f:targetRole": { "@id": "ex:yetiRole" }, "f:action": [{ "@id": "f:view" }] } ], "f:property": [ { "@id": "ex:yetisViewOnlyOwnAge", "f:path": { "@id": "schema:age" }, "f:allow": [ { "@id": "ex:ageViewRule", "f:targetRole": { "@id": "ex:yetiRole" }, "f:action": [{ "@id": "f:view" }], "f:equals": { "@list": [{ "@id": "f:$identity" }, { "@id": "ex:user" }] } } ] } ] }}
Adding Identity with Limited Access
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /transact*/{ "@context": { "ex": "http://example.org/", "schema": "http://schema.org/", "f": "https://ns.flur.ee/ledger#" }, "ledger": "cookbook/base", "insert": { "@id": "did:fluree:Tf5M4L7SNkziB4Q5gC8Hjuqu9WQKCwKpU1Y", "ex:user": { "@id": "ex:freddy" }, "f:role": { "@id": "ex:yetiRole" } }}
Query as Limited Role
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*"] }, "opts": { "role": "http://example.org/yetiRole" }}
Example Response
[ { "@id": "ex:andrew", "@type": [ "ex:Yeti", "schema:Person" ], "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": [ "Andrew Johnson", "Andy the Yeti" ] }, { "@id": "ex:andrew", "@type": [ "ex:Yeti", "schema:Person" ], "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": [ "Andrew Johnson", "Andy the Yeti" ] }, { "@id": "ex:betty", "@type": "ex:Yeti", "ex:firstName": "Betty", "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "foaf:name": "Freddy", "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:firstName": "Leticia", "ex:nickname": "Letty", "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }]
Query as Limited Role Identity
Request Headers
Content-Type: application/json
Request Body
/* Action: POST Endpoint: /query*/{ "@context": { "schema": "http://schema.org/" }, "from": "cookbook/base", "where": { "@id": "?s", "schema:name": "?name" }, "select": { "?s": ["*"] }, "opts": { "did": "did:fluree:Tf5M4L7SNkziB4Q5gC8Hjuqu9WQKCwKpU1Y" }}
Example Response
[ { "@id": "ex:andrew", "@type": [ "ex:Yeti", "schema:Person" ], "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": [ "Andrew Johnson", "Andy the Yeti" ] }, { "@id": "ex:andrew", "@type": [ "ex:Yeti", "schema:Person" ], "schema:follows": [ { "@id": "ex:freddy" }, { "@id": "ex:letty" }, { "@id": "ex:betty" } ], "schema:givenName": "Andrew", "schema:name": [ "Andrew Johnson", "Andy the Yeti" ] }, { "@id": "ex:betty", "@type": "ex:Yeti", "ex:firstName": "Betty", "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Betty" }, { "@id": "ex:freddy", "@type": "ex:Yeti", "ex:verified": true, "foaf:name": "Freddy", "schema:name": "Freddy" }, { "@id": "ex:letty", "@type": "ex:Yeti", "ex:firstName": "Leticia", "ex:nickname": "Letty", "schema:follows": { "@id": "ex:freddy" }, "schema:name": "Leticia" }]