Skip to main content

Getting Started

This guide helps you set up your environment and make your first calls to the Enterprise Context Engine (ECE) Cloud Content Repository APIs. By the end you will have an access token, a verified connection, and a complete retrieval flow from a metadata filter to a citation link.

Prerequisites​

Before you begin, ensure you have the following:

Required Access​

  • Admin Portal Access - Administrative access to the Content Innovation Cloud (CIC) Administration Portal to register your application's OAuth client, and a service user for ingestion or graph administration
  • Active Subscription - An environment with an active subscription to the ECE service

Required Knowledge​

  • Basic understanding of RESTful APIs
  • Familiarity with JSON format
  • Understanding of OAuth 2.0: the Authorization Code with Proof Key for Code Exchange (PKCE) flow for signing in users, and client credentials for service users

Required Tools​

  • HTTP Client - curl, Postman, Bruno, or any HTTP client library

Optional Tools​

  • jq - used below to extract the token from the token response

Account Setup Process​

To set up your CIC account to use the ECE service, complete the following steps:

Step 1: Choose the Right Token​

Using the following information as a guide, choose and set up the appropriate type of token for your ECE use case.

The Cloud Content Repository APIs accept the following types of tokens (see How Authentication Works):

TokenHow you get itUse it for
User tokenThe end user signs in through your application's front end (OAuth 2.0 Authorization Code with PKCE)All search and retrieval: Query, Embeddings, Download and the ECE Graph Query APIs (which also need content-lake-api.graphs.read). Results are filtered by that user's document ACLs.
Service user tokenOAuth 2.0 client credentials of a service userIngestion and graph administration only
Search needs the user's context

User context only comes from the end user's sign-in. A service user is not an end user, so its searches do not respect the end user's access controls. A service user's searches only return what the service user was granted, which is usually nothing at all. However, if the service user holds hxpr.manage.everything, its searches return every document in the repository.

User token (search and retrieval)​

To set up an end user token, do the following:

  1. Register your application as an External Application of type Web Server Client (or JavaScript Client) in the Administration Portal.
  2. Sign the end user in with the Authorization Code with PKCE flow.
  3. Send the user's access token to the Cloud Content Repository APIs.

When setting up the user token, also note the following:

  • Using a JavaScript application with a Backend-for-Frontend (BFF) is recommended. The BFF is a confidential OAuth client, keeps access and refresh tokens server-side in a cookie-based session, and proxies API calls, adding the user's token.
  • Avoid using single-page applications without a backend. They cannot protect tokens from exfiltration or terminate sessions securely.
  • Native applications (mobile, desktop) must launch the system browser for sign-in, not an embedded browser control.
  • Using an established OAuth library rather than implementing the flow yourself is recommended. See User Authentication for the client types to register and the sign-in steps.

Service user token (ingestion and graph administration)​

To set up a service user token, your environment administrator can do the following in the CIC Administration Portal:

  1. Service user — Register a service user in the target environment.
  2. User group — Add the service user to a user group with the appropriate role assigned. The graph APIs check permissions (see Roles and Scopes).
  3. External client registration — Register an External Application of type Service Client to obtain OAuth credentials (client_id and client_secret).
warning

The client_secret is displayed only once when you register the external client, so it is important to save it immediately. If the secret is lost, the administrator must reset it.

Once the service user setup is complete, you will have the following:

  • Token endpoint - for example, https://auth.iam.experience.hyland.com/idp/connect/token (see Token Endpoint for other regions)
  • Environment ID - your environment's UUID
  • Base API URL - https://{environment_id}.content-lake.ai.experience.hyland.com
  • Client ID - your client identifier
  • Client Secret - your client secret

Step 2: Configure Environment Variables​

Set up your environment variables for secure credential storage by adding the following to your shell or .env file:

# In your shell or .env file
export ENVIRONMENT_ID="your-environment-id"
export CLIENT_ID="your-client-id"
export CLIENT_SECRET="your-client-secret"
export TOKEN_URL="https://auth.iam.experience.hyland.com/idp/connect/token"
export BASE_URL="https://${ENVIRONMENT_ID}.content-lake.ai.experience.hyland.com"
# Access token of a signed-in user, from your application's sign-in (used by the Quick Start)
export USER_TOKEN="<access token of the signed-in user>"

Step 3: Retrieve a Service User Token​

Exchange the service user's client credentials for a JWT access token. Tokens are valid for 900 seconds (15 minutes).

Authentication Details

For the JWT claims, the permissions each API needs, and authentication errors, see the Authentication documentation.

POST https://auth.iam.experience.hyland.com/idp/connect/token
Content-Type: application/x-www-form-urlencoded

client_id={{client_id}}&client_secret={{client_secret}}&grant_type=client_credentials&scope=hxp environment_authorization hxp.integrations hxpr

Response (200 OK):

{
"access_token": "eyJhbGciOi...<redacted>",
"expires_in": 900,
"token_type": "Bearer",
"scope": "environment_authorization hxp hxp.integrations hxpr"
}

Step 4: Verify API Access​

Test your token and base URL by running a small query:

POST {{baseUrl}}/api/query
Authorization: Bearer {{accessToken}}
Content-Type: application/json
Accept: application/vnd.hxcs.v1+json

{
"query": "SELECT * FROM SysContent",
"limit": 2
}

Response (200 OK):

{
"repositoryId": null,
"query": "SELECT * FROM SysContent",
"limit": 2,
"offset": 0,
"totalCountIsTruncated": true,
"documents": [ { "sys_id": "...", "sys_primaryType": "SysFile", "sys_title": "..." }, { ... } ],
"totalCount": 10000,
"nextSearchAfter": "eyJzb3J0VmFsdWVzIjpb...",
"count": 2
}
note

A 200 only confirms that the token and base URL are valid; it does not verify search. With a service user token, documents is normally empty, because no document ACL grants the service user access or contains everything if the service user holds hxpr.manage.everything. Run searches with a user token, as in the Quick Start Guide.

If you are working with the Postman or Bruno application, you must also create the following environment variables:

  • baseUrl: your BASE_URL
  • accessToken: your service user token from Step 3
  • userToken: the access token of a signed-in user, for the Quick Start Guide
First Time Setup

If no content has been ingested into the environment yet, documents is an empty array and totalCount is 0. This is normal, as you must ingest content first (through the Ingestion APIs, for example).

Quick Start Guide​

Follow this end-to-end example: a typical retrieval flow over ingested contracts from a metadata filter to a citation link. The following responses are trimmed, and IDs are shortened.

tip

For more detailed walkthroughs and advanced scenarios, see the Tutorials section.

Prerequisites​

  • A user access token for a signed-in user (see the Account Setup Process's Step 1), in USER_TOKEN / {{userToken}}
  • Content ingested into the environment, with chunks and embeddings created by Data Curation

Step 1: Find Documents by Ingest Property​

POST {{baseUrl}}/api/query
Authorization: Bearer {{userToken}}
Content-Type: application/json
Accept: application/vnd.hxcs.v1+json

{
"query": "SELECT * FROM SysContent WHERE cin_ingestProperties/BuyerOrg = 'Evergreen Retail Cooperative' AND cin_ingestProperties/Status = 'Executed'",
"limit": 1,
"trackTotalCountUpTo": -1
}

Response (200 OK):

{
"query": "SELECT * FROM SysContent WHERE cin_ingestProperties/BuyerOrg = 'Evergreen Retail Cooperative' AND cin_ingestProperties/Status = 'Executed'",
"limit": 1,
"offset": 0,
"documents": [
{
"sys_id": "54dfe6ac-...__000ec471-...",
"sys_primaryType": "SysFile",
"cin_ingestProperties": {
"BuyerOrg": "Evergreen Retail Cooperative",
"SellerOrg": "Crestline Data Systems, Inc.",
"DocType": "SOW",
"Status": "Executed",
"EffectiveDate": "2024-08-04",
"Jurisdiction": "Ohio",
"PageCount": 3,
...
},
"cin_blobs": [ { "label": "content", "originalFilename": "SOW-SOW-1844-A_DOC-001158.pdf", "blob": { ... } } ],
...
}
],
"totalCount": 51,
"nextSearchAfter": "eyJzb3J0VmFsdWVzIjpb...",
"count": 1
}

totalCount is the exact number of matches because trackTotalCountUpTo is -1. This is how you count in HXQL; there is no COUNT(*).

Step 2: Break the Matches Down with a Terms Aggregation​

POST {{baseUrl}}/api/query/termsAggregation
Authorization: Bearer {{userToken}}
Content-Type: application/json
Accept: application/vnd.hxcs.v1+json

{
"query": { "query": "SELECT * FROM SysContent WHERE cin_ingestProperties/BuyerOrg = 'Evergreen Retail Cooperative'" },
"termsAggregationProperty": "cin_ingestProperties.DocType",
"limit": 5
}

Response (200 OK):

{
"documents": [],
"aggregationsBuckets": [
{ "key": "Contract", "docCount": 12 },
{ "key": "Amendment", "docCount": 9 },
{ "key": "ApprovalMemo", "docCount": 9 },
{ "key": "SLA", "docCount": 9 },
{ "key": "EmailExport", "docCount": 6 }
],
...
}

Bucket counts come from a sample of the matching documents, so treat them as approximate for large result sets. String (keyword) properties aggregate; numeric properties such as PageCount are rejected with HTTP 400 Bad Request.

Step 3: Search the Document's Chunks by Similarity​

The query here does not fetch the document. It is a filter that restricts the vector search to the chunks (embeddings) of the documents it matches: here the single document found in Step 1, because sys_id is the document ID and each chunk records it as sysembed_docId. The vector then ranks those chunks by similarity. Drop the WHERE clause to search the chunks of every document the user can see.

POST {{baseUrl}}/api/query/embeddings
Authorization: Bearer {{userToken}}
Content-Type: application/json
Accept: application/vnd.hxcs.v1+json

{
"vector": [0.0123, -0.0456, ...],
"query": "SELECT * FROM SysContent WHERE sys_id = '54dfe6ac-...__000ec471-...'",
"limit": 1
}

Response (200 OK):

{
"repositoryId": null,
"vector": [0.0123, -0.0456, ...],
"query": "SELECT * FROM SysContent WHERE sys_id = '54dfe6ac-...__000ec471-...'",
"limit": 1,
"offset": 0,
"embeddings": [
{
"sysembed_id": "8b11384c-498d-5e82-9b4d-aff66ee20000",
"sysembed_docId": "54dfe6ac-...__000ec471-...",
"sysembed_type": "cohere.embed-multilingual-v3",
"sysembed_vector": [ ... 1024 values ... ],
"sysembed_text": "# STATEMENT OF WORK (SOW SOW-1844-A) ... Fixed Fee: USD $1,800,000. Invoices issued by milestone subject to acceptance. ...",
"sysembed_blobref": null,
"sysembed_location": {
"text": { "page": 1, "paragraph": 0 },
"position": { "left": 80, "top": 130, "right": 177, "bottom": 145 },
"timestamp": null,
"spreadsheet": null
},
"sysembed_score": 0.4778
}
],
"count": 1
}

The vector is the embedding of your question, produced with the same model as sysembed_type (1024 values for cohere.embed-multilingual-v3). Use sysembed_text as the grounding passage and sysembed_location (page and bounding box) to highlight the citation.

GET {{baseUrl}}/api/download/url/54dfe6ac-...__000ec471-.../cin_blobs/0/blob?expiration=300
Authorization: Bearer {{userToken}}
Accept: */*

Response (200 OK):

https://clake-....s3.us-east-1.amazonaws.com/storage/5f2feff7...?response-content-disposition=attachment%3B%20filename%3D07d228cf-...&X-Amz-Expires=300&...

The response is the URL as plain text. expiration is in seconds and capped at 3600, which is also the default. Documents ingested through the pipeline keep their file in cin_blobs/{index}/blob; documents created directly in the Cloud Content Repository use sysfile_blob. Send Accept: */*, as JSON and text/plain Accept headers return a 406 error.

Common Issues​

400 on a LIKE 'prefix%' query

On keyword fields such as ingest properties and sys_name, a pattern with a single trailing wildcard returns 400 Error performing OpenSearch query: failed to create query: Can only use phrase prefix queries on text fields .... Use LIKE '%Prefix%', an _ wildcard, or = instead. See Querying with HXQL.

401 Unauthorized with an empty body

The token is missing, malformed, or expired; the request is rejected before it reaches the API, so there is no JSON body to parse. Tokens last 15 minutes — request a new one, and check that the header reads Bearer <token> with a single space.

406 Not Acceptable

The Accept header asks for a media type the endpoint does not produce. Use application/vnd.hxcs.v1+json for JSON endpoints, application/octet-stream or */* for the streaming download, and */* for the pre-signed URL endpoint (GET /api/download/url/...): both a JSON type and text/plain return a 406 error there.

500 with an empty body on a download

A streaming download that fails (unknown document or property) while you send Accept: application/octet-stream returns a 500 error with an empty body. Send Accept: */* to get the real 404 error: No document found for an unknown document or No blob found for an unknown property.

Query returns no documents

Use SELECT *. Naming fields in SELECT makes POST /api/query return an empty documents list and totalCount: 0. If you just ingested the document, call GET /api/query/waitForFullTextSearchIndexing first.

Need Help?

If you encounter issues not covered here, consult the Error Reference or contact Hyland Support.

Next Steps​

You have retrieved documents, counted them per value, found the matching chunks, and linked to the source file. Here's what to explore next:

🔐 Authentication

Learn about the JWT claims, roles, and the permissions each API group requires.

📚 Tutorials

Write HXQL queries, run semantic search, and set up and rebuild the Knowledge Graph.