Setting Up the Knowledge Graph
This tutorial walks through configuring a knowledge graph in the Enterprise Context Engine: creating a graph database, uploading the ontology that defines its entity types and relationships, and routing documents to that ontology with condition expressions. It ends with verifying the configuration and deleting graph databases and ontologies safely.
Prerequisites
- A valid access token (see Authentication)
- The base URL of your environment
- The permissions listed in Access Requirements
- An ontology definition file in YAML (
.yamlor.yml)
Set two variables before running the curl examples. The HTTP examples use the equivalent {{baseUrl}} and {{accessToken}} placeholders.
export BASE_URL="https://{environment_id}.content-lake.ai.experience.hyland.com"
export TOKEN="<access token from the identity provider>"
Tutorial Overview
In this tutorial, you'll learn to:
- Choose a graph database version
- Create a graph database
- Upload an ontology
- Route documents to the ontology
- Verify the configuration
- List and download ontologies
- Delete ontologies and graph databases safely
Access Requirements
- All endpoints require an authenticated caller with the permission listed per endpoint below. A caller without the required permission receives
401 Unauthorized. - The Content Lake ECE Administrator role carries all of these permissions; Content Lake ECE User carries only
content-lake-api.graph-configs.read(see Roles and Scopes).
| Operation | Required permission |
|---|---|
GET /ontologies, GET /ontologies/{ontologyId}, GET /graphdbs/{graphDBId}/config | content-lake-api.graph-configs.read |
POST /ontologies | content-lake-api.graph-configs.create |
DELETE /ontologies/{ontologyId} | content-lake-api.graph-configs.delete |
PUT /graphdbs/{graphDBId}/ontologyroutes | content-lake-api.graph-configs.edit |
POST /graphdbs | content-lake-api.graph-configs.create |
DELETE /graphdbs/{graphDBId} | content-lake-api.graph-configs.delete |
GET /graphdbs, GET /graphdbs/{graphDBId} | content-lake-api.graph-configs.read |
Errors use the standard Cloud Content Repository error body, {"entity-type":"exception","status":<code>,"message":"..."}. The exceptions are noted below: some 400 and 404 responses have an empty body. Requests whose JSON can't be mapped onto the expected shape (an unknown version, both graphDBName and domainName, and so on) get the generic message "Invalid input".
Step 1: Choose a Graph Database Version
A graph database's version selects its query engine and access-control model. It is set when the graph database is created.
| Version | Engine | Access control |
|---|---|---|
v2 (recommended) | GraphQL schema | ACLs are enforced. Results are scoped to the caller's permissions. |
v1 (deprecated) | DQL schema | No ACL support. Restricted to administrators. |
v1 graph databases are deprecated and will not be supported in future. Create every new graph database as v2. Always send "version": "v2" explicitly: version currently still defaults to v1, and the default will change to v2.
Step 2: Create a Graph Database
- HTTP
- curl
POST {{baseUrl}}/api/graph/graphdbs
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
Content-Type: application/json
{
"graphDBName": "my-knowledge-graph",
"version": "v2"
}
curl -X POST "$BASE_URL/api/graph/graphdbs" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json" \
-H "Content-Type: application/json" \
--data @- <<'JSON'
{
"graphDBName": "my-knowledge-graph",
"version": "v2"
}
JSON
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
graphDBName | string | Yes | Name for the new graph database. Must not be blank and must be unique. |
version | string | No | "v2" (exact, lowercase). Always set it: it currently defaults to the deprecated "v1". See GraphDB Versions. |
defaultGraph | boolean | No | When true, makes this the tenant's default graph database (see The default graph database). Defaults to false. Only one graph database can be the default. |
Permissions
content-lake-api.graph-configs.create
Response
201 Created with the created graph database:
{
"graphDBId": "3f2b6c1e-8d4a-4b7e-9a51-2c6e0f9d7a10",
"graphDBName": "my-knowledge-graph",
"version": "v2",
"defaultGraph": false
}
graphDBId is a generated UUID. No Location header is returned; take the ID from the body. defaultGraph is true only for the tenant's default graph database.
To make retries safe, send an optional Idempotency-Key header with a value unique to the request. The graph database is then created at most once per key, and a repeat call with the same key returns the original result, marked with the Idempotent-Replay response header. POST /api/graph/ontologies accepts the same header.
The default graph database
A tenant can mark one graph database as its default by creating it with "defaultGraph": true. Wherever a path takes a {graphDBId} (for example GET /api/graph/graphdbs/{graphDBId}, /config, /ontologyroutes and DELETE), you can pass the literal default instead, and it resolves to the current default graph database.
More Examples
Duplicate name
400 {"entity-type":"exception","status":400,"message":"GraphDBConfig with name 'my-knowledge-graph' already exists"}
Blank name ({"graphDBName": ""})
400 {"entity-type":"exception","status":400,"message":"GraphDBConfig name must not be null or blank"}
Unknown version ({"graphDBName": "x", "version": "1.0"}), or both name keys ({"graphDBName": "x", "domainName": "y"})
400 {"entity-type":"exception","status":400,"message":"Invalid input"}
Errors
| Status | When |
|---|---|
400 | graphDBName is missing or blank: "GraphDBConfig name must not be null or blank". |
400 | A graph database with the same name already exists: "GraphDBConfig with name '...' already exists". |
400 | The maximum number of graph databases has been reached (100 by default): "Cannot create graphDB: tenant has reached the maximum of 100 graphDB instance(s). Delete an existing graphDB before creating a new one." |
409 | defaultGraph is true but another graph database is already the default: "A default graphDB already exists; only one default graphDB is allowed at a time". |
400 | version is not a valid version, or both graphDBName and the legacy domainName are sent: "Invalid input". |
Step 3: Upload an Ontology
Upload a new ontology definition file. The ontology must be in YAML format and uploaded as multipart/form-data.
- HTTP
- curl
POST {{baseUrl}}/api/graph/ontologies
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
Content-Type: multipart/form-data; boundary=----ece
------ece
Content-Disposition: form-data; name="ontologyName"
benefits-admin-ontology
------ece
Content-Disposition: form-data; name="description"
Administrative services agreements
------ece
Content-Disposition: form-data; name="file"; filename="benefits_admin_ontology.yaml"
Content-Type: application/x-yaml
< ./benefits_admin_ontology.yaml
------ece--
curl -X POST "$BASE_URL/api/graph/ontologies" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json" \
-F "ontologyName=benefits-admin-ontology" \
-F "description=Administrative services agreements" \
-F "file=@benefits_admin_ontology.yaml"
Upload Fields
| Field | Type | Required | Description |
|---|---|---|---|
ontologyName | string | Yes | Name of the ontology. Must not be blank and must be unique. |
description | string | No | Free-text description of the ontology |
file | file (YAML) | Yes | The ontology definition file. Only files with a .yaml or .yml extension (case-insensitive) are accepted. An empty file is rejected with 400 "Request part 'file' is missing or empty". |
Only the file extension is checked. A .yaml file that isn't valid YAML, or isn't a valid ontology, is still accepted with 201. Validate the file yourself before uploading it.
Response Example
201 Created, with a Location header pointing to the new ontology:
HTTP/1.1 201
Location: /api/graph/ontologies/c28a73bd-3212-47d7-a782-f0f7daae3679
{
"ontologyId": "c28a73bd-3212-47d7-a782-f0f7daae3679",
"ontologyName": "benefits-admin-ontology",
"description": "Administrative services agreements",
"createdAt": "2026-10-01T11:35:18.109502204Z"
}
If no description part is sent, description is returned as an empty string ("").
Error Examples
Missing or empty ontologyName. An empty value (-F "ontologyName=") is treated the same as leaving the part out:
400 {"entity-type":"exception","status":400,"message":"Required part 'ontologyName' is not present."}
No file part
400 {"entity-type":"exception","status":400,"message":"Required part 'file' is not present."}
Wrong file extension (-F "file=@notes.txt")
400 {"entity-type":"exception","status":400,"message":"Unsupported file type: 'notes.txt'. Only .yaml and .yml files are accepted."}
Name already used
409 {"entity-type":"exception","status":409,"message":"Ontology with name 'benefits-admin-ontology' already exists"}
Error Responses
| Status | Description |
|---|---|
400 | Missing or empty ontologyName part, missing or empty file part, or an unsupported file extension (messages above) |
409 | An ontology with the given name already exists: "Ontology with name '<name>' already exists" |
413 | Uploaded file exceeds the configured size limit |
Step 4: Route Documents to the Ontology
Replace all ontology routing rules for a graph database with the supplied list. Each rule pairs a boolean condition, evaluated against document properties, with the ontology to apply to documents that match it. An empty or omitted ontologyRoutes list clears all routes.
- HTTP
- curl
PUT {{baseUrl}}/api/graph/graphdbs/{graphDBId}/ontologyroutes
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
Content-Type: application/json
{
"ontologyRoutes": [
{
"condition": "content.sys_primaryType == \"SysFile\" && content.dc_type == \"Contract\"",
"ontologyId": "3f6c2a1e-8b4d-4c2f-9a7e-1d5b6c7e8f90"
},
{
"condition": "content.dc_type == \"Invoice\"",
"ontologyId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
}
]
}
curl -X PUT "$BASE_URL/api/graph/graphdbs/{graphDBId}/ontologyroutes" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json" \
-H "Content-Type: application/json" \
--data @- <<'JSON'
{
"ontologyRoutes": [
{
"condition": "content.sys_primaryType == \"SysFile\" && content.dc_type == \"Contract\"",
"ontologyId": "3f6c2a1e-8b4d-4c2f-9a7e-1d5b6c7e8f90"
},
{
"condition": "content.dc_type == \"Invoice\"",
"ontologyId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
}
]
}
JSON
Route Object
| Field | Type | Required | Description |
|---|---|---|---|
condition | string | Yes | Boolean routing expression. Operators: == != < > <= >= && || ! + - * / ,, ternary ?:, grouping ( ), and list.contains(x). Use ==, not =, and && / ||, not AND / OR. Strings must be double-quoted. Document properties are referenced with the content. prefix (e.g. content.sys_primaryType, content.dc_type), and property paths use . (dc_type, not dc:type). The security properties sys_acl, sys_racl and sys_secpol are not available to conditions. Note that a condition referencing them is not rejected: sys_acl == "x" is accepted with 200, so check conditions yourself. See also condition limitations. |
ontologyId | string | Yes | ID of an ontology that is already registered |
Response Example
200 OK, returning the routes now in effect.
{
"ontologyRoutes": [
{
"condition": "content.sys_primaryType == \"SysFile\" && content.dc_type == \"Contract\"",
"ontologyId": "3f6c2a1e-8b4d-4c2f-9a7e-1d5b6c7e8f90"
},
{
"condition": "content.dc_type == \"Invoice\"",
"ontologyId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
}
]
}
content.A condition that names a property without the prefix, such as sys_id == "...", is accepted by this endpoint with 200, but it fails later. For example, a rebuild for the graph database is rejected with 400 {"entity-type":"exception","status":400,"message":"No global property: sys_id"}. Write content.sys_id == "..." instead.
Condition Limitations
- Complex list properties cannot be traversed: a condition cannot reach into the entries of a list of objects (such as
cin_blobs). - Partial matching is not supported: there is no wildcard, prefix or substring match on a string. Compare whole values with
==or!=.
Condition Examples
| Goal | Condition |
|---|---|
| One specific document (useful for testing a route) | content.sys_id == "{docId}" |
| Files of a given type | content.sys_primaryType == "SysFile" && content.dc_type == "Contract" |
| PDFs only | content.sys_contentType == "application/pdf" |
| Either of two types | content.dc_type == "Invoice" || content.dc_type == "Receipt" |
| Everything except one type | !(content.dc_type == "Draft") |
More Examples
Clear all routes
PUT /api/graph/graphdbs/{graphDBId}/ontologyroutes
{ "ontologyRoutes": [] }
200 { "ontologyRoutes": [] }
Unknown ontology
{ "ontologyRoutes": [ { "condition": "content.dc_type == \"Contract\"", "ontologyId": "nope" } ] }
400 {"entity-type":"exception","status":400,"message":"Unknown ontologyId(s): nope"}
SQL-style = instead of ==
{ "ontologyRoutes": [ { "condition": "sys_id = \"...\"", "ontologyId": "c28a73bd-..." } ] }
400 {"entity-type":"exception","status":400,"message":"Invalid condition for ontologyId 'c28a73bd-3212-47d7-a782-f0f7daae3679': At position 1:7 token recognition error at: '= '"}
Blank condition
400 {"entity-type":"exception","status":400,"message":"Validation failed for argument [1] in public org.springframework.http.ResponseEntity<...OntologyRoutingConfigurationResponse> ...setOntologyRoutes(...)..."}
Route Errors
| Status | Description |
|---|---|
400 | Invalid payload: a blank condition or ontologyId (a long Spring "Validation failed for argument..." message), an unknown ontologyId ("Unknown ontologyId(s): ..."), or a condition that cannot be parsed ("Invalid condition for ontologyId '...': ...") |
404 | Graph database not found (empty body) |
Step 5: Verify the Configuration
Retrieve the configuration for a specific graph database, including its associated ontology routing rules.
- HTTP
- curl
GET {{baseUrl}}/api/graph/graphdbs/{graphDBId}/config
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
curl -X GET "$BASE_URL/api/graph/graphdbs/{graphDBId}/config" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json"
Response Example
{
"graphDBId": "7b1e9c2d-4a3f-4e8b-b6d1-2c9f0a1e3d45",
"graphDBName": "my-graph",
"version": "v2",
"ontologyRoutes": [
{
"condition": "content.sys_primaryType == \"SysFile\" && content.dc_type == \"Contract\"",
"ontologyId": "3f6c2a1e-8b4d-4c2f-9a7e-1d5b6c7e8f90"
}
]
}
A newly created graph database has no routes:
{
"graphDBId": "164e3760-21a3-473b-9443-eddda8c7f0cd",
"graphDBName": "my-contracts-graph",
"version": "v2",
"ontologyRoutes": []
}
Response Fields
| Field | Type | Description |
|---|---|---|
graphDBId | string | The graph database ID |
graphDBName | string | The graph database name |
version | string | Graph database version (v2; v1 for deprecated graph databases) |
ontologyRoutes | array | Current ontology routing rules (see route object below) |
Config Errors
| Status | Description |
|---|---|
404 | Graph database not found (empty body) |
You can also read the graph database itself:
- HTTP
- curl
GET {{baseUrl}}/api/graph/graphdbs/{graphDBId}
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
curl -X GET "$BASE_URL/api/graph/graphdbs/{graphDBId}" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json"
Graph Database Response
{
"graphDBId": "3f2b6c1e-8d4a-4b7e-9a51-2c6e0f9d7a10",
"graphDBName": "production-graph",
"version": "v2",
"defaultGraph": false
}
Graph Database Errors
| Status | When |
|---|---|
404 | No graph database with this ID (empty body). |
Step 6: List and Download Ontologies
Retrieve the metadata of all registered ontologies. The response does not include download URLs; use GET /api/graph/ontologies/{ontologyId} to get a pre-signed download URL for a specific ontology.
- HTTP
- curl
GET {{baseUrl}}/api/graph/ontologies?limit=10&offset=0
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
curl -X GET "$BASE_URL/api/graph/ontologies?limit=10&offset=0" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json"
List Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | No | Max results (default 10; a negative value falls back to the default) |
offset | query | integer | No | Results to skip (default 0; a negative value returns 400) |
Response Example
{
"offset": 0,
"limit": 10,
"totalCount": 1,
"count": 1,
"ontologies": [
{
"ontologyId": "3f6c2a1e-8b4d-4c2f-9a7e-1d5b6c7e8f90",
"ontologyName": "invoice-ontology",
"description": "Entities and relationships for invoices",
"createdAt": "2026-01-15T10:30:00.123456Z"
}
]
}
More Examples
Page through ontologies one at a time
GET /api/graph/ontologies?limit=1&offset=0
{ "offset": 0, "limit": 1, "totalCount": 8, "count": 1, "ontologies": [ { ... } ] }
Negative offset
GET /api/graph/ontologies?offset=-1
400 {"entity-type":"exception","status":400,"message":"'offset' must not be negative"}
Response Fields
| Field | Type | Description |
|---|---|---|
offset | integer | Offset applied to this page |
limit | integer | Limit applied to this page |
totalCount | integer | Total number of registered ontologies |
count | integer | Number of ontologies on this page |
ontologies[].ontologyId | string | Ontology ID (a UUID generated on upload) |
ontologies[].ontologyName | string | Ontology name (unique) |
ontologies[].description | string | Description supplied on upload (empty string if none was given) |
ontologies[].createdAt | string | Creation time as an ISO-8601 UTC timestamp |
Download an ontology file
Get a pre-signed URL to download the ontology definition file.
- HTTP
- curl
GET {{baseUrl}}/api/graph/ontologies/{ontologyId}
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
curl -X GET "$BASE_URL/api/graph/ontologies/{ontologyId}" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json"
Returns a pre-signed URL for downloading the ontology YAML file, wrapped in an ontology object.
{
"ontology": {
"presignedUrl": "https://{bucket}.s3.{region}.amazonaws.com/kgconfig/{ontologyId}?...&X-Amz-Expires=3600&X-Amz-Signature=..."
}
}
The URL is valid for 3600 seconds (one hour). Fetch it without the bearer token to download the YAML file. The storage service may report a Content-Type other than YAML (for example text/turtle), so don't rely on it.
Error Responses
| Status | Description |
|---|---|
404 | {"entity-type":"exception","status":404,"message":"Ontology not found: {ontologyId}"} |
List graph databases
- HTTP
- curl
GET {{baseUrl}}/api/graph/graphdbs
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
curl -X GET "$BASE_URL/api/graph/graphdbs" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json"
Graph Database List Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | No | Maximum number of results (default 10). A negative value falls back to the default. |
offset | query | integer | No | Number of results to skip (default 0). Must not be negative. |
Graph Database List Permissions
content-lake-api.graph-configs.read
Graph Database List Response
{
"offset": 0,
"limit": 10,
"totalCount": 1,
"count": 1,
"graphDBs": [
{
"graphDBId": "3f2b6c1e-8d4a-4b7e-9a51-2c6e0f9d7a10",
"graphDBName": "production-graph",
"version": "v2",
"defaultGraph": false
}
]
}
totalCount is the number of graph databases across all pages; count is the number on this page.
Graph Database List Examples
Second page of two
GET /api/graph/graphdbs?limit=2&offset=1
{ "offset": 1, "limit": 2, "totalCount": 12, "count": 2, "graphDBs": [ {...}, {...} ] }
Negative limit falls back to 10
GET /api/graph/graphdbs?limit=-5
{ "offset": 0, "limit": 10, "totalCount": 12, "count": 10, "graphDBs": [ ... ] }
Negative offset
GET /api/graph/graphdbs?offset=-1
400 {"entity-type":"exception","status":400,"message":"'offset' must not be negative"}
Graph Database List Errors
| Status | When |
|---|---|
400 | offset is negative: "'offset' must not be negative". |
These are the canonical endpoints under /api/graph/graphdbs. The older /api/graph/domain URLs are deprecated aliases (since 2026.06, scheduled for removal) and return the legacy {domainId, domainName, Version} shape (note the capital V). The legacy list, GET /api/graph/domain, returns a bare JSON array rather than a page object, and in testing it ignored limit:
GET /api/graph/domain?limit=2
[
{"domainId":"3f2b6c1e-8d4a-4b7e-9a51-2c6e0f9d7a10","domainName":"production-graph","Version":"v2"},
{"domainId":"9b2575db-c130-4433-86c9-0c23cd06e739","domainName":"legacy-graph","Version":"v1"},
...every graph database...
]
Step 7: Delete Safely
Delete an ontology
An ontology can only be deleted when no graph database routes to it.
- HTTP
- curl
DELETE {{baseUrl}}/api/graph/ontologies/{ontologyId}
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
curl -X DELETE "$BASE_URL/api/graph/ontologies/{ontologyId}" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json"
Response
204 No Content
Error Responses
| Status | Description |
|---|---|
404 | {"entity-type":"exception","status":404,"message":"Ontology not found: {ontologyId}"} |
409 | The ontology is still referenced by the ontologyRoutes of one or more graph databases. Remove those routes first. |
Example: deleting an ontology that is still routed
DELETE /api/graph/ontologies/c28a73bd-3212-47d7-a782-f0f7daae3679
409 {"entity-type":"exception","status":409,"message":"Ontology is still referenced by the ontologyRoutes configuration of graphDB(s): my-contracts-graph"}
To delete it, first PUT a route list that no longer references the ontology (or {"ontologyRoutes": []}) to every graph database named in the message, then retry.
Delete a graph database
Deleting a graph database drops its underlying graph namespace, which permanently removes all associated knowledge graph data. This operation cannot be undone.
- HTTP
- curl
DELETE {{baseUrl}}/api/graph/graphdbs/{graphDBId}
Authorization: Bearer {{accessToken}}
Accept: application/vnd.hxcs.v1+json
curl -X DELETE "$BASE_URL/api/graph/graphdbs/{graphDBId}" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json"
Response
204 No Content on success.
Errors
| Status | When |
|---|---|
400 | graphDBId is blank: "GraphDB ID must not be null or blank". |
404 | No graph database with this ID, including IDs that are not UUIDs (empty body). |
Next Steps
- Working with Graph Data: retrieve Business Objects and provenance, and aggregate entities
- Rebuilding the Graph: apply new routes to documents that are already in the Cloud Content Repository
- API Reference: every endpoint and schema
- Error Reference: error formats and status codes