Working with Graph Data
This tutorial shows how to read the data in a knowledge graph: searching Business Objects by their shared attributes, opening a Business Object, finding related Business Objects, tracing a shared attribute back to the documents that mention it, and grouping entities with aggregate queries. It assumes you already have a v2 graph database (see Setting Up the Knowledge Graph).
Prerequisites
- A valid access token (see Authentication)
- The base URL of your environment
- The ID of a
v2graph database (graphDBId).v1graph databases are deprecated: see graph database versions content-lake-api.graphs.read(for example through the Content Lake ECE User role)- A user token: every result is limited to the documents the user can read (see Authentication)
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:
- Search Business Objects by shared attribute
- Get a Business Object
- Find related Business Objects
- List a Business Object's shared attributes
- Trace a shared attribute to its sources (provenance)
- Group entities with an aggregate query
Key Concepts
| Term | Meaning |
|---|---|
| Business Object | A business record assembled in the graph from your documents, such as a contract or a statement of work. Each has an id, a name, an objectType and a description. |
| Shared attribute | A Global Entity (such as an organization or a person) attached to a Business Object, with a type, a canonical value and an optional role. Business Objects that share an attribute are related. |
| Provenance | Where a shared attribute was found: one record per mention, with the text around the mention (snippet), the chunk it was found in (chunkId, chunkIndex), and the source document (documentId, documentTitle). |
All five retrieval endpoints share these conventions:
- The path starts with
/api/ece/business-objects/{graphDBId}. Pass the literaldefaultas{graphDBId}to use the tenant's default graph database. If no graph database is marked as the default,defaultreturns404ECE-404-NOT_FOUND("Unknown graphDBId: default"). - Responses are JSON (
Accept: application/vnd.hxcs+jsonorapplication/json). Fields without a value are left out. - Lists are paged with
offset(default0) andlimit(default25, maximum100), and returnoffset,limit,totalCount,countanditems. timeoutMs(orlimits.timeout_msin the search body) sets a per-request deadline. Values above the server maximum are reduced to it rather than rejected. A request that runs past its deadline currently returns500ECE-500-INTERNAL, so use generous values.- Errors use the ECE error format:
{"code", "message", "details", "traceId"}.
Business Object, shared attribute and chunk IDs are graph node IDs such as 0x1396cf1. Take them from earlier responses and send them unchanged, in this hexadecimal form, rather than building or converting them yourself. documentId is the Cloud Content Repository document ID, which you can pass to the Download APIs. The examples below are real responses from a test graph built from sample commercial insurance documents, shortened with ....
Step 1: Search Business Objects
Find Business Objects by the shared attributes they carry and, optionally, by object type. This example finds the Business Objects that share the insurance carrier Continental Harbor Indemnity Company:
- HTTP
- curl
POST {{baseUrl}}/api/ece/business-objects/{graphDBId}/search
Authorization: Bearer {{accessToken}}
Accept: application/json
Content-Type: application/json
{
"filter": {
"sharedAttribute": { "type": "ORGANIZATION", "value": "Continental Harbor Indemnity Company" }
},
"page": { "offset": 0, "limit": 5 }
}
curl -X POST "$BASE_URL/api/ece/business-objects/{graphDBId}/search" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data @- <<'JSON'
{
"filter": {
"sharedAttribute": { "type": "ORGANIZATION", "value": "Continental Harbor Indemnity Company" }
},
"page": { "offset": 0, "limit": 5 }
}
JSON
Search Request
| Field | Type | Required | Description |
|---|---|---|---|
filter | object | Yes | The filter tree (see Filter rules). Must contain at least one sharedAttribute predicate. |
page.offset | integer | No | Zero-based offset. Default 0. |
page.limit | integer | No | Page size, 1–100. Default 25. If you send page, set limit too: a page without limit is rejected with "limit must be > 0, got: 0". |
limits.timeout_ms | integer | No | Per-request deadline in milliseconds. |
Filter Rules
Each filter node normally sets one of these keys; if a node sets several, they are combined with AND:
| Key | Matches |
|---|---|
sharedAttribute | Business Objects with a shared attribute matching every field you set: type (exact and case-sensitive, for example ORGANIZATION), value (exact and case-sensitive match on the canonical value) and role (case-insensitive, and matches if any of its words match). Set at least one of them. |
objectType | Business Objects whose objectType is in the in list (exact and case-sensitive). An empty list is ignored. |
and | Every child node |
or | At least one child node |
- The filter must contain at least one
sharedAttributepredicate. A filter onobjectTypealone is rejected, because it would scan every Business Object. objectTypemay appear only once, and not inside anor. It applies to the whole filter.- Every
orbranch must contain asharedAttributepredicate. - The filter is expanded into an AND of OR-groups. One that expands to more than 64
sharedAttributepredicates is rejected.
Example: document Business Objects that mention either the carrier or a specific form identifier:
{
"filter": {
"and": [
{ "or": [
{ "sharedAttribute": { "type": "ORGANIZATION", "value": "Continental Harbor Indemnity Company" } },
{ "sharedAttribute": { "type": "IDENTIFIER", "value": "CHI DEC 00 01 24" } }
] },
{ "objectType": { "in": ["document"] } }
]
}
}
Search Response
200 OK with one page of Business Objects. Filtering runs before paging, so totalCount and the pages are exact.
{
"offset": 0,
"limit": 5,
"totalCount": 22,
"items": [
{
"id": "0x1396cf1",
"name": "Appalachian_Roofing___Sheet_Metal_Contractors_LLC_GACP-60472...",
"objectType": "document",
"description": "Commercial package policy declarations issued for Appalachian Roofing & Sheet Metal Contractors LLC."
},
{
"id": "0x1396d03",
"name": "CA 00 01 10 13 - Business Auto Coverage Form",
"objectType": "document",
"description": "Business Auto Coverage Form attached to the policy at inception."
},
...
],
"count": 5
}
Search returns each Business Object's id, name, objectType and description only, even if you send include. Use Get a Business Object and List shared attributes for the rest.
Search Errors
| Status | Code | When |
|---|---|---|
400 | ECE-400-INVALID_PARAM | objectType inside an or, or an or branch with only objectType: "objectType inside OR is not supported" |
400 | ECE-400-INVALID_PARAM | More than one objectType: "Multiple objectType predicates are not supported" |
400 | ECE-400-INVALID_PARAM | An or branch that isn't a sharedAttribute predicate: "OR leaves must be sharedAttribute predicates" |
400 | ECE-400-INVALID_PARAM | Too complex: "Filter is too complex: it expands to 65 shared-attribute predicates (max 64). ..." |
400 | ECE-400-INVALID_PARAM | An unknown include value: "Unknown include option: <value>". Valid include values are accepted but have no effect on search. |
400 | ECE-400-INVALID_PAGE_SIZE | page.limit is above 100: "limit 500 exceeds the maximum of 100" |
400 | ECE-400-INVALID_PARAM | An objectType-only filter: "filter must supply at least one sharedAttribute predicate: ... An objectType-only filter is not sufficient." |
404 | ECE-404-NOT_FOUND | Unknown graph database: "Unknown graphDBId: <id>" |
Step 2: Get a Business Object
Open one Business Object. Add include to also return its shared attributes and a provenance sample for each.
- HTTP
- curl
GET {{baseUrl}}/api/ece/business-objects/{graphDBId}/0x1396cf1?include=sharedAttributes,provenance
Authorization: Bearer {{accessToken}}
Accept: application/json
curl -X GET "$BASE_URL/api/ece/business-objects/{graphDBId}/0x1396cf1?include=sharedAttributes,provenance" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
| Parameter | In | Description |
|---|---|---|
id | path | The Business Object ID |
include | query | Optional. sharedAttributes adds the shared attributes; provenance adds a provenance sample to each one. Repeat the parameter or separate values with commas. |
timeoutMs | query | Optional per-request deadline in milliseconds |
200 OK:
{
"id": "0x1396cf1",
"name": "Appalachian_Roofing___Sheet_Metal_Contractors_LLC_GACP-60472...",
"objectType": "document",
"description": "Commercial package policy declarations issued for Appalachian Roofing & Sheet Metal Contractors LLC.",
"createdAt": "2026-10-17T16:55:44.348694Z",
"sharedAttributes": [
{
"id": "0x1396a7b",
"type": "IDENTIFIER",
"value": "CHI DEC 00 01 24",
"role": "Commercial Package Policy Declarations identifier",
"provenance": [
{
"chunkId": "0x1396bd0",
"chunkIndex": 8,
"snippet": "Commercial Package Policy Declarations (CHI DEC 00 01 24)",
"documentId": "d0374e69-...__01156a43-...",
"documentTitle": "CLI Ingestion Name"
}
],
"provenanceCount": 6
},
{
"id": "0x1396a71",
"type": "CODE",
"value": "English",
"role": "Document language",
"provenance": [],
"provenanceCount": 0
},
...
]
}
Without include, the response has the Business Object's own fields only. include=provenance has an effect only together with sharedAttributes, and each attribute's provenance sample holds at most 3 records. The provenance sample can list the same chunk more than once when an attribute is mentioned several times in it; use Step 5 to page through all records. An unknown ID and an ID the user is not allowed to read both return 404 ECE-404-NOT_FOUND ("Business Object not found: <id>").
Step 3: Find Related Business Objects
List the Business Objects that share at least one shared attribute with this one, ranked by how many they share (sharedEntityCount, highest first).
- HTTP
- curl
GET {{baseUrl}}/api/ece/business-objects/{graphDBId}/0x1396cf1/related?limit=5
Authorization: Bearer {{accessToken}}
Accept: application/json
curl -X GET "$BASE_URL/api/ece/business-objects/{graphDBId}/0x1396cf1/related?limit=5" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
| Parameter | In | Description |
|---|---|---|
offset, limit | query | Paging. Defaults 0 and 25; limit maximum 100 |
objectType | query | Optional. Only return related objects of these types. Repeat the parameter or separate values with commas. |
include | query | Optional. provenance adds, on each related object, the shared attributes it has in common with this one, with provenance. |
timeoutMs | query | Optional per-request deadline in milliseconds |
200 OK:
{
"offset": 0,
"limit": 5,
"totalCount": 55,
"items": [
{
"id": "0x1396b26",
"name": "Cascade_Electrical___Low_Voltage_Contractors_LLC_NVCP-407183...",
"objectType": "document",
"description": "The source insurance schedule issued by Continental Harbor Indemnity Company for Cascade Electrical...",
"sharedEntityCount": 3
},
{
"id": "0x1396aad",
"name": "Continental Harbor Indemnity Company",
"objectType": "organization",
"description": "The insurance carrier identified as the issuing organization for the policy materials.",
"sharedEntityCount": 3
},
...
],
"count": 5
}
Relatedness is based on shared attributes, not on extracted relationships. The source object, the candidates and the shared attributes are all limited to documents the user can read.
Step 4: List a Business Object's Shared Attributes
Page through all the shared attributes of a Business Object.
- HTTP
- curl
GET {{baseUrl}}/api/ece/business-objects/{graphDBId}/0x1396cf1/shared-attributes?limit=5
Authorization: Bearer {{accessToken}}
Accept: application/json
curl -X GET "$BASE_URL/api/ece/business-objects/{graphDBId}/0x1396cf1/shared-attributes?limit=5" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
| Parameter | In | Description |
|---|---|---|
offset, limit | query | Paging. Defaults 0 and 25; limit maximum 100 |
include | query | Optional. provenance adds a provenance sample to each attribute. |
timeoutMs | query | Optional per-request deadline in milliseconds |
200 OK:
{
"businessObjectId": "0x1396cf1",
"offset": 0,
"limit": 5,
"totalCount": 9,
"items": [
{
"id": "0x1396c9a",
"type": "DATE",
"value": "04/22/2025",
"role": "Policy issue date",
"provenance": [],
"provenanceCount": 1
},
{
"id": "0x1396c94",
"type": "ARTIFACT",
"value": "Appalachian_Roofing___Sheet_Metal_Contractors_LLC_GACP-6047283_01_Commercial_Package_Policy___Declarations.pdf",
"role": "Source document",
"provenance": [],
"provenanceCount": 0
},
...
],
"count": 5
}
provenanceCount is the number of provenance records the user can read. provenance is always present, and is an empty list unless you request include=provenance. Use an attribute's id in the next step to page through all of its provenance.
Step 5: Trace a Shared Attribute to Its Sources
List every recorded mention of one shared attribute of a Business Object, with its source document. Use them as citations.
- HTTP
- curl
GET {{baseUrl}}/api/ece/business-objects/{graphDBId}/0x1396cf1/shared-attributes/0x1396c9a/provenance?limit=3
Authorization: Bearer {{accessToken}}
Accept: application/json
curl -X GET "$BASE_URL/api/ece/business-objects/{graphDBId}/0x1396cf1/shared-attributes/0x1396c9a/provenance?limit=3" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
| Parameter | In | Description |
|---|---|---|
attrId | path | The shared attribute ID, from sharedAttributes[].id or items[].id in the previous steps |
offset, limit | query | Paging. Defaults 0 and 25; limit maximum 100 |
timeoutMs | query | Optional per-request deadline in milliseconds |
200 OK:
{
"businessObjectId": "0x1396cf1",
"sharedAttribute": {
"id": "0x1396c9a",
"type": "DATE",
"value": "04/22/2025",
"role": "Policy issue date",
"provenance": []
},
"offset": 0,
"limit": 3,
"totalCount": 1,
"items": [
{
"chunkId": "0x1396d2e",
"chunkIndex": 2,
"snippet": "Issue Date",
"documentId": "d0374e69-...__01cb37b7-...",
"documentTitle": "CLI Ingestion Name"
}
],
"count": 1
}
The attribute is looked up through the given Business Object, so an attrId that belongs to a different Business Object returns an empty page. To open the source file, pass documentId to the Download APIs.
Retrieval Errors
Steps 2 to 5 return these errors, in addition to the search errors above:
| Status | Code | When |
|---|---|---|
400 | ECE-400-INVALID_PARAM | An invalid ID (Business Object and attribute IDs must be graph node IDs such as 0x1396cf1), or a paging or timeoutMs value that isn't a number ("For input string: \"abc\"") |
400 | ECE-400-INVALID_PAGE_SIZE | limit is above 100 ("limit 101 exceeds maximum of 100" on related, "... exceeds the maximum of 100" elsewhere) |
404 | ECE-404-NOT_FOUND | Unknown graph database, or (Step 2) a Business Object that doesn't exist or that the user cannot read |
429 | ECE-429-RATE_LIMITED | Throttled. Retry after the number of seconds in Retry-After. |
500 | ECE-500-INTERNAL | Unexpected error, including a request that ran past its timeoutMs deadline. Report the traceId. |
503 | ECE-503-BACKEND_UNAVAILABLE | The graph backend is unavailable. Retry with backoff after Retry-After. |
For an unknown Business Object, Steps 3 to 5 return 200 with an empty page rather than 404 (Step 5 also returns "sharedAttribute": null). In the search body, malformed JSON, a value of the wrong type (such as a string page.limit) or a missing body is rejected before it reaches these APIs and returns the standard error body (400); so does a missing permission (401).
Step 6: Group Entities with an Aggregate Query
Group entities of a type by a property and return each group's value with its count (API reference). Requests must use Content-Type: application/json.
- HTTP
- curl
POST {{baseUrl}}/api/graph/graphdbs/{graphDBId}/aggregate
Authorization: Bearer {{accessToken}}
Accept: application/json
Content-Type: application/json
{
"type": "BusinessObject",
"property": "object_type",
"limits": { "timeout_ms": 15000 }
}
curl -X POST "$BASE_URL/api/graph/graphdbs/{graphDBId}/aggregate" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data @- <<'JSON'
{
"type": "BusinessObject",
"property": "object_type",
"limits": { "timeout_ms": 15000 }
}
JSON
Aggregate Fields
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | The entity type to aggregate. |
property | string | Yes | The property to group by. It must be a single-valued (non-list) property and one of: Int/Float, a DateTime with any @search, or a String indexed with @search(by: [exact]). |
limits.timeout_ms | integer | No | Query timeout in milliseconds. Defaults to 15000 and is capped at 60000. |
Aggregate Permissions
content-lake-api.graphs.read or hxpr.manage.everything. Use a user token: counts are scoped to the user's ACLs, and administrators see all entities.
Aggregate Response
{
"groups": [
{ "value": "product", "count": 1 },
{ "value": "organization", "count": 13 },
{ "value": "location", "count": 18 },
{ "value": "document", "count": 21 },
{ "value": "financial_fact", "count": 66 }
]
}
Groups are sorted by count, smallest first.
Aggregate Examples
Count global entities by type
{ "type": "GlobalEntity", "property": "entity_type" }
200 {"groups":[{"value":"ASSET","count":1},{"value":"DATE","count":9},...,{"value":"MONETARY_VALUE","count":40},{"value":"TEXT","count":75}]}
Property that isn't groupable
title is single-valued, but it is a String without @search(by: [exact]) in the schema, so it can't be grouped. Being non-list is not enough: a String must also have an exact index.
{ "type": "Document", "property": "title" }
400 {"entity-type":"exception","status":400,"message":"Property 'title' of type 'Document' is not groupable. Only Int/Float properties, properties with @search(by: [exact]), or DateTime properties with an @search directive are supported."}
Aggregate Errors
| Status | When |
|---|---|
400 | Unknown type: "Invalid type: 'Chunk'. Type not found in schema." |
400 | Unknown property: "Invalid property: 'createdAt' not found on type 'BusinessObject'." |
400 | A list property: "... is a list predicate and cannot be used for aggregate grouping ...", or another non-groupable property (message above) |
400 | type or property is blank (a validation message ending in Type is required or similar) |
400 | No graph database with this ID: "Configuration not found for GraphDBConfig: <id>" |
415 | Content-Type is not application/json: "Content-Type '...' is not supported" |
400 | A deprecated v1 graph database: "Aggregate queries are only supported for GQL schema" |
Next Steps
- Setting Up the Knowledge Graph: graph databases, ontologies and routes
- Rebuilding the Graph: re-trigger graph processing for existing documents
- API Reference: every endpoint and schema
- Error Reference: error formats and status codes