Querying
The standard query mechanism for the Pod KG uses GraphQL (inspired by Ruben Taelman's GraphQL to SPARQL library and the Stardog GraphQL API).
The query endpoint is available at /{podId}/query and accepts POST requests whose body conforms to the GraphQL-over-HTTP specification. The body may contain a @context object to provide aliases for the predicate IRIs used in the query. If no context is provided, the system falls back to the default mapping configured for the pod (see Pod Management).
While a global query endpoint is useful for exploring the entire Knowledge Graph, in practice it will rarely occur that an application requires access to all of a Pod's data. Kvasir introduces the concept of Slices to define restricted subsets of the Knowledge Graph with which clients interact. The query endpoint for a specific Slice is available at /{podId}/slices/{sliceId}/query.
Why GraphQL?
When choosing a query language for the Knowledge Graph, we considered several options: SPARQL, GraphQL, RESTful APIs centred around collections of specific RDF classes, or a proprietary query language (e.g. similar to what Fluree is doing). We chose GraphQL as the main querying mechanism [1] for the following reasons:
GraphQL is a widely adopted query language that is easy to learn and use, especially in the context of modern web applications.
GraphQL is technology-agnostic, meaning it can be used with any backend. This lets us experiment with different storage solutions without coupling the query language to a specific RDF storage technology (as SPARQL would).
GraphQL strikes a nice balance between expressiveness and simplicity. It allows for complex queries while remaining easy to understand, limits the implementation scope, and simplifies the process for third parties to develop a Kvasir-compatible API.
How to read the examples
The query endpoint accepts a JSON body containing a query field and an optional @context field. To keep the examples readable, the full HTTP call is shown once below, and all subsequent code blocks show only the GraphQL query (and where relevant the response data object), assuming the same endpoint and context.
Full example — POST http://localhost:8080/alice/query
Response:
Basic usage
The top-level field in the query represents the RDF class of resources you want to retrieve. The exact GraphQL schema is auto-generated from the inserted content; see Auto-generated schema for details.
The following query retrieves all ex:Person resources that have a schema:givenName and a schema:email:
Nested traversal is also supported:
Response:
Additional features
Namespace prefixes
The examples above use namespace prefixes (ex_, schema_) where the underscore substitutes the colon that GraphQL does not allow in field names. The full prefix mapping is supplied in the @context object of the request body.
Context language-tag
By default, Kvasir returns all values for language-tagged string literals. You can request a specific language by adding @language to the context:
This returns only values tagged en (or values with no language tag).
Arguments
Use GraphQL arguments to filter resources by a specific value. The most common case is filtering by id:
Response:
Pass an array to match multiple values:
Filters
Use @filter directives to narrow down results with RSQL expressions:
If your expression only references a single selected field, you can move the filter down to that field:
In that field-level form, you can shorten the expression by referring to the field value as it:
Response:
Optional fields (@optional)
By default, requesting a field behaves like an inner join on that relation/property: resources for which the field has no value are not included in the result path for that selection.
Use @optional when you want left-join-like behavior: keep matching resources even if the selected field has no value.
Without @optional:
With @optional:
In the second query, persons without schema_email are still returned, with an empty value for that field in the response path.
@optional is also useful on nested relations when you want to keep the parent result even if the relation is missing:
Sorting
Use the orderBy argument with a list of field names to sort the results. Prefix a field name with - to sort descending:
Pagination
Use pageSize to limit the number of results per page and cursor to navigate to the next page. When the result set is larger than pageSize, the extensions.pagination block in the response will contain a next cursor:
Response:
Use the next cursor to fetch the remaining results:
Time travel
Since the Knowledge Graph retains a complete history of all changes, you can query its state at a specific point in time by adding one of these fields to the request body:
atTimestamp— query the state of the KG at the specified ISO 8601 timestamp.atChangeId— query the state right after a specific change request was committed.
Reversing traversal
Kvasir supports the JSON-LD @reverse keyword in the context for introducing reverse relationships:
Response:
Type selection
Query only linked resources of a specific type using inline fragments:
Alternatively, use the special field _types together with a filter (see also Resource):
Introspection
The query endpoint implements the standard GraphQL introspection mechanism, allowing clients to discover the schema of the Knowledge Graph including all available types and fields.
This means you can point GraphiQL or any other GraphQL tooling at the query endpoint to explore the schema and run queries interactively with auto-completion.

Outputting JSON-LD
By default the query endpoint adheres to the GraphQL specification and returns a JSON data object. You can request JSON-LD output by setting the Accept header to application/ld+json:
POST http://localhost:8080/alice/query
Accept: application/ld+json
Response:
Auto-generated schema
A GraphQL interface is defined by its schema, which is typically authored upfront in SDL or generated from programmatic definitions. What makes Kvasir different is that we don't know beforehand what data will be available for querying. A GraphQL schema for the entire KG is auto-generated based on the data inserted via Changes API for the global Pod endpoint /{podId}/query.
This auto-generation mechanism does not apply to Slices. Slice schemas are authored explicitly by the Slice author in GraphQL SDL (with Kvasir directives), and Kvasir then post-processes those definitions with additional system types/fields and helper arguments.
Although accepting only RDF data (which is contextually qualified) helps with schema generation, it is not always possible to deduce the full structure of the incoming data.
The automated schema generation for the KG is a best-effort approach, with the goal of allowing users to quickly explore the entire content. If a specific, stable structure is required, use Slices.
Limitations include:
If type information is spread over multiple change requests (e.g. change 1 adds a relation between A and B, change 2 adds type info for B), Kvasir may not have full type information. Inserting important type info together with the instance data in a single batch helps.
Kvasir does not assume any vocabularies, shapes or ontologies [2]. Predicates used for a Resource are associated with all RDF classes that resource is an instance of.
Complex type hierarchies are abstracted away via supertypes such as
RDFNodeandResource(see next section).
Common supertypes
RDFNode
A common interface for values that can be either a Resource or a Literal. Exposes a single field _rawRDF:
Resource:
{ "@id": "http://example.org/alice" }Literal:
{ "@value": "1024", "@type": "http://www.w3.org/2001/XMLSchema#integer" }
Resource implements RDFNode
Common supertype for RDF resources. Exposes an id field (the IRI) and utility fields for exploration:
_relations— discover relations between resources:
Response:
_predicates— list all predicates a Resource uses._types— list all RDF classes the Resource is an instance of._object— force-retrieve the value for a specific predicate that may not be part of the auto-generated schema:
Response:
BoxedLiteral implements RDFNode
Represents a boxed literal. Useful in combination with RDFNode to support fields that can hold either resources or literals.
For Slice authoring patterns that use these types explicitly, see Fields with multiple types.
Response: