Authentication
The Enterprise Context Engine Cloud Content Repository APIs authenticate every request with an OAuth 2.0 bearer token. This guide covers which kind of token to use, how user-facing applications and service users obtain one, required headers, base URL construction, the JWT claims, the permissions needed for each API group, and common authentication errors.
How Authentication Works
Every request carries a short-lived JWT access token. Which kind of token you need depends on what you are doing:
| Token | Obtained from | Use it for |
|---|---|---|
| User token | The end user's sign-in to your application, using the Authorization Code with PKCE flow | Search and retrieval: the Query, Embeddings and Download APIs, and the ECE Graph Query APIs (which also need content-lake-api.graphs.read, for example through the Content Lake ECE User role). Document ACLs are evaluated against that user, so results contain only what the user is entitled to see. |
| Service user token | The client credentials (client ID and secret) of a service user | Ingestion and graph administration: the GraphDB, Ontology and Graph Rebuild APIs. |
The graph APIs also check the permissions granted to the caller through its roles (see Permissions Required per API Group).
Do not use a service user token for search. A service user is not an end user, so ACL filtering leaves out every document it has not been granted explicitly, and a query usually returns nothing. If the service user holds a role with hxpr.manage.everything (such as Content Lake Administrator), it bypasses ACL filtering and the query returns everything in the repository. Neither gives correct per-user results, and ACLs are not exposed through the API, so results cannot be filtered afterwards. Always search with the token of the user you are searching for.
Key point: The token endpoint, base URL, and scopes are all tied to your specific Content Innovation Cloud environment. You will need your environment_id (a UUID) to construct the API base URL.
Prerequisites
Before you can obtain a token, your environment administrator must have completed the following in the Content Innovation Cloud Administration Portal:
- Environment subscription — The environment must have an active subscription to the Cloud Content Repository / ECE service.
- User groups — User groups with the appropriate roles assigned, for both end users and service users (see Roles and Scopes below).
- Client registration for your user-facing application — An External Application of type Web Server Client (or JavaScript Client) for the application your end users sign in to. Used to obtain user tokens for search and retrieval (see User Authentication).
- Service user (ingestion and graph administration only) — An External Application of type Service Client, which has a service user attached, to obtain OAuth credentials (
client_idandclient_secret).
Important: The client_secret is displayed only once when you register the external client. Save it immediately. If lost, the administrator must reset it.
See Getting Started for step-by-step setup instructions.
IAM Configuration
Both flows use your environment's identity provider. Tokens are requested from its token endpoint:
| Environment | Token Endpoint |
|---|---|
| Hyland Experience Platform | https://auth.iam.experience.hyland.com/idp/connect/token |
| Content Innovation Cloud (US) | https://auth.app.hyland.com/idp/connect/token |
| Content Innovation Cloud (EU) | https://auth.app.hyland.eu/idp/connect/token |
The authorization endpoint used by the user sign-in flow is on the same identity provider. Discover it, and the other endpoints, through the provider's OpenID discovery document: /.well-known/openid-configuration under the issuer (for example https://auth.iam.experience.hyland.com/idp/.well-known/openid-configuration).
Authentication Methods
There are two ways to obtain a token. Use user authentication for anything that reads content on behalf of a person, and service user authentication only for ingestion and graph administration.
User Authentication (Authorization Code with PKCE)
Use Cases:
- Search and RAG applications
- Any user-facing application that queries, retrieves or downloads documents, or queries the graph
User-facing applications authenticate the end user with the OAuth 2.0 Authorization Code with PKCE (Proof Key for Code Exchange) grant, the current OAuth best practice. The user signs in at the identity provider, your application exchanges the returned authorization code for tokens, and the access token carries the user's identity (its sub claim is the signed-in user), so the Cloud Content Repository evaluates document ACLs against that user. Use an established OAuth library for the protocol handshake, and verify that it does what is expected.
Register the client
Register your application as an External Application in the Content Innovation Cloud Administration Portal. Two client types sign in a person, both using the Authorization Code grant (with an optional Refresh Token):
| Client type | Use for |
|---|---|
| Web Server Client | Applications with server-side code, including a JavaScript front end with a Backend for Frontend (BFF). The client is confidential: its secret stays on the server. |
| JavaScript Client | Browser-only applications with no backend. Avoid where possible (see below). |
The client registration must allow the environment_authorization scope. A single-tenant client is also configured with the environment it serves and the app key (content-lake); a multi-tenant client supplies them at sign-in instead (see below).
For ingestion and graph administration, register a Service Client instead (see Service User Authentication). Client secrets are sensitive: share them only through approved secure channels, never in chat, email or screenshots, and rotate a secret that has been exposed.
Choose an application profile
| Application profile | Client type | Guidance |
|---|---|---|
| JavaScript application with a Backend for Frontend (BFF) | Web Server Client | Recommended. The BFF is a confidential OAuth client that talks to the identity provider, keeps the access and refresh tokens in a cookie-based server session (tokens are never exposed to the JavaScript code), and proxies API calls to the Cloud Content Repository, adding the access token. |
| Web server application | Web Server Client | Run the sign-in flow and keep the tokens on the server, in the user's session. Never send the access or refresh token to the browser. |
| Single-page application without a backend | JavaScript Client | Avoid. Code running only in the browser cannot protect tokens against exfiltration or terminate sessions securely. |
| Native application (mobile, desktop) | — | Open the sign-in page in the system browser, never in an embedded browser control, and use PKCE. |
Sign the user in and call the API
-
Discover the authorization and token endpoints from the identity provider's
/.well-known/openid-configurationdocument (see IAM Configuration). -
Send the user to the authorization endpoint with a PKCE challenge (method
S256), requesting the scopesopenid hxp environment_authorization. Addoffline_accessif your application needs a refresh token to renew access without asking the user to sign in again.openidestablishes the user's identity and returns an ID token.hxpgrants standard platform API access.environment_authorizationmakes the identity provider check, at sign-in, that the user has a role for the app in this environment and that the environment's subscription is active. It then adds thehxp_authorizationclaim, with the environment, roles and permissions, to the access token.
A multi-tenant client must also send the environment and app as the
acr_valuesparameter, URI-encoded:environment_key:<environment-key> app_key:content-lake. These values take precedence over the client registration. -
Exchange the returned code and PKCE verifier for tokens at the token endpoint.
-
Send the user's access token in the
Authorization: Bearer <token>header (see Required Request Headers). If you requestedoffline_access, renew it with the refresh token before it expires.
If there is no valid user token, fail the request. Never fall back to a service user token: that would silently replace the user's access controls with the service user's.
Sign-in failures
Because authorization is checked at sign-in, a user without access is stopped before your application calls the API:
| Message | Cause | Fix |
|---|---|---|
| Not authorized: Caller not authorized for requested resource | The user has no role for the app in this environment, or the environment's subscription is not active | Assign the user's group a role (see Roles and Scopes), or check the subscription |
| Unable to perform authorization: Missing authorization context | The app key or the environment was not configured on the client or supplied at sign-in | Configure them on the client, or send acr_values (multi-tenant clients) |
| Unable to perform authorization: Unsupported client type | The client does not exist, or its type does not support sign-in authorization | Register a Web Server Client or JavaScript Client |
| Unable to perform authorization: Invalid authorization scope combination | Both environment_authorization and account_authorization were requested | Request environment_authorization only |
Redirect URIs on http://localhost are blocked. For local testing, register a redirect on a .test host name, for example http://web.test:8889/callback, and map that host to the loopback address in your hosts file (/etc/hosts on Linux and macOS, C:\Windows\System32\drivers\etc\hosts on Windows):
127.0.0.1 web.test
Service User Authentication (Client Credentials)
Use Cases:
- Backend services
- Batch processing jobs
- System integrations
- Scheduled tasks
- Graph administration (graph databases, ontologies, rebuilds)
Service user tokens are not suitable for search; see How Authentication Works.
Use the standard grant_type=client_credentials. The identity provider maps the Service Client to its attached service user, which is why the token's amr claim shows urn:hyland:params:oauth:grant-type:api-credentials. Do not request openid or profile with this grant: the request fails with invalid_scope. If the client is registered for several applications, add app_key and environment_id (below) so that the token's hxp_authorization claim describes the Cloud Content Repository in your environment.
The token request uses the application/x-www-form-urlencoded content type:
- HTTP
- curl
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
curl -X POST https://auth.iam.experience.hyland.com/idp/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
-d "scope=hxp environment_authorization hxp.integrations hxpr"
Request Parameters
| Parameter | Value | Description |
|---|---|---|
client_id | Your registered client ID | The service client identifier from external app registration |
client_secret | Your client secret | The secret issued during registration (shown once) |
grant_type | client_credentials | Always this value for machine-to-machine auth |
scope | hxp environment_authorization hxp.integrations hxpr | Space-separated list of scopes (see below) |
app_key | content-lake | Optional. Use it when the Service Client is registered for several applications, so that the token's hxp_authorization claim (app key, roles and permissions) is issued for the Cloud Content Repository rather than the client's default app. |
environment_id | Your environment's UUID | Optional. Use it together with app_key when the client is not configured for this environment. |
Response
{
"access_token": "eyJhbGciOi...<redacted>",
"expires_in": 900,
"token_type": "Bearer",
"scope": "environment_authorization hxp hxp.integrations hxpr"
}
| Field | Type | Description |
|---|---|---|
access_token | string | The JWT bearer token to include in API requests |
expires_in | integer | Token lifetime in seconds. Client-credentials tokens are issued for 900 seconds (15 minutes). |
token_type | string | Always Bearer |
scope | string | The granted scopes, space-separated. The order may differ from the order you requested. |
Tip: Tokens expire after 15 minutes. In your integration, request a new token before the current one expires. A good pattern is to cache the token and refresh it 60 seconds before expiry.
Base URL
All Cloud Content Repository API requests target your environment-specific base URL:
https://{environment_id}.content-lake.ai.experience.hyland.com
Replace {environment_id} with your environment's UUID (for example 3f2a9c10-0000-4000-8000-000000000000). The same UUID appears in your token as hxp_authorization.environment_id.
Required Request Headers
Only Authorization is required on every request. Set the others as shown for the endpoint you are calling:
| Header | Value | Description |
|---|---|---|
Authorization | Bearer <access_token> | The access token from the token request. Required on every request. |
Content-Type | application/json | For JSON request bodies (POST and PUT). The ontology upload (POST /api/graph/ontologies) uses multipart/form-data instead. |
Accept | application/vnd.hxcs.v1+json, application/vnd.hxcs+json or application/json | For JSON endpoints (Query, Embeddings, GraphDB, Ontology, Graph Rebuild). The streaming download (GET /api/download/{docId}/...) only produces application/octet-stream, application/vnd.hxcs+octet-stream and application/vnd.hxcs.v1+octet-stream, so send one of those or */*. The pre-signed URL endpoint (GET /api/download/url/...) returns the URL as plain text but only accepts */*: both a JSON type and text/plain return 406. |
HXCS-REPOSITORY | default (or your repo name) | Optional. Selects the repository for the Download, Embeddings and waitForFullTextSearchIndexing endpoints; defaults to default. Query request bodies use the repositoryId field instead. An unknown repository returns 500. |
Note: The Cloud Content Repository service does not read an X-Environment-ID header. The target environment is set by the base URL and your token.
Example Request
A search request, sent with the signed-in user's token (see User Authentication):
- HTTP
- curl
POST https://{environment_id}.content-lake.ai.experience.hyland.com/api/query
Authorization: Bearer {{userToken}}
Content-Type: application/json
Accept: application/vnd.hxcs.v1+json
{
"limit": 10,
"offset": 0,
"query": "SELECT * FROM SysContent",
"repositoryId": "default",
"sort": ["sys_title"],
"syntax": "hxql",
"trackTotalCount": true,
"trackTotalCountUpTo": 100
}
curl -X POST \
"https://{environment_id}.content-lake.ai.experience.hyland.com/api/query" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/vnd.hxcs.v1+json" \
-d '{
"limit": 10,
"offset": 0,
"query": "SELECT * FROM SysContent",
"repositoryId": "default",
"sort": ["sys_title"],
"syntax": "hxql",
"trackTotalCount": true,
"trackTotalCountUpTo": 100
}'
Understanding the JWT
The access token is a signed JWT (header {"alg": "PS256", "typ": "at+jwt"}). Decoding its payload shows who the token was issued to, which environment it targets, and the roles and permissions attached to the caller. The example below is a decoded service user (client-credentials) token (identifiers redacted). A user token has the same structure, but identifies the signed-in user, and carries the roles and permissions of that user's groups:
{
"iss": "https://auth.iam.experience.hyland.com/idp",
"nbf": 1790853472,
"iat": 1790853472,
"exp": 1790854372,
"aud": ["hxp.authorization", "hxp", "hxpr"],
"scope": ["environment_authorization", "hxp", "hxp.integrations", "hxpr"],
"amr": ["urn:hyland:params:oauth:grant-type:api-credentials"],
"client_id": "<redacted>",
"sub": "<redacted>",
"idp": "local",
"hxp_account": "{account_id}",
"hxp_authorization": {
"account_id": "{account_id}",
"environment_id": "{environment_id}",
"subscription_id": "{subscription_id}",
"appkey": "content-lake",
"role": ["content-lake.administrator", "cin-context-data-curation-content-lake.user"],
"permission": [
"content-lake-api.documents.create",
"content-lake-api.documents.delete",
"content-lake-api.documents.edit",
"content-lake-api.documents.read",
"hxpr.manage.everything",
"cin-context-api.contentprocessing.read",
"cin-context-api.contentprocessing.write",
"cin-data-curation.api.submit"
],
"source_appkeys": ["content-lake"]
},
"auth_time": 1790853472,
"jti": "<redacted>"
}
| Claim | Description |
|---|---|
iss | The identity provider that issued the token |
aud | Audience: the services this token is valid for |
scope | Granted scopes. In the decoded token this is an array. |
iat / nbf / exp | Issued-at, not-before and expiry times (Unix epoch seconds). exp − iat is 900 for client-credentials tokens. |
amr | How the caller authenticated. urn:hyland:params:oauth:grant-type:api-credentials means a Service Client (client ID and secret) acting as its service user. |
hxp_authorization.environment_id | The environment the token is scoped to. Use the base URL of this same environment. |
hxp_authorization.subscription_id | The subscription the environment belongs to |
hxp_authorization.appkey | The application the token was issued for (content-lake) |
hxp_authorization.role | Role keys assigned to the caller's user groups, for example content-lake.administrator. Informational: the Cloud Content Repository does not authorize on role names. |
hxp_authorization.permission | Permission strings carried by those roles, for example content-lake-api.documents.read or hxpr.manage.everything. The graph APIs authorize on these (see below). |
hxp_authorization.source_appkeys | App keys used in this authorization and, after a token exchange, in earlier ones |
How graph API permissions are resolved
For requests to /api/graph/**, /api/v1/graph/** and /api/ece/**, the Cloud Content Repository exchanges your token for a subscription token and checks the permissions in that token's permission claim (top-level, or nested under hxp_authorization.permission). The exchanged token is issued by the identity provider for the Cloud Content Repository and your service user, so the permissions that count are the ones the service user holds for the Cloud Content Repository. The hxp_authorization claim in the token you send can differ from them, so use it as a hint rather than a guarantee. If the exchange fails, the request is rejected.
The other endpoints need no specific permission, but the Cloud Content Repository still resolves the caller's permissions: a caller holding hxpr.manage.everything is treated as an administrator and bypasses document ACLs.
Debugging tip: Decode the JWT in code by base64url-decoding the middle segment rather than pasting a live token into a website. Check exp to see if your token has expired, hxp_authorization.environment_id to confirm it targets the right environment, and hxp_authorization.permission for graph API permission failures (see Permissions Required per API Group).
# Decode the payload of a token held in $TOKEN (bash + python3)
python3 -c "import sys,json,base64; p=sys.argv[1].split('.')[1]; p+='='*(-len(p)%4); print(json.dumps(json.loads(base64.urlsafe_b64decode(p)),indent=2))" "$TOKEN"
Roles and Scopes
OAuth Scopes
The scope parameter in the token request determines which scopes the identity provider grants. The Cloud Content Repository service itself does not check scope values: access is decided by a valid token, the permissions in its hxp_authorization claim, and document ACLs. The registered client must be allowed every scope it requests.
| Scope | User token (sign-in) | Service user token (client_credentials) | Purpose |
|---|---|---|---|
openid | Required | Do not request | Establishes the user's identity and returns an ID token. Requesting it with client_credentials fails with invalid_scope. |
hxp | Required | Required | Standard platform API access |
environment_authorization | Required | Required | Adds the hxp_authorization claim (environment, roles and permissions). Cannot be combined with account_authorization. |
offline_access | Optional | — | Returns a refresh token |
hxpr, hxp.integrations | Not needed | Used | Integration-level scopes for service clients |
CIC Roles
The CIC uses a role-based access control (RBAC) model. User groups are assigned to application-specific roles in the Admin Portal. A user inherits the permissions of every role their user groups are assigned to. Never assign roles directly to users — always go through user groups.
Cloud Content Repository is registered as a service application: it has no tile of its own, and end users reach its content through Knowledge Discovery, Agent Builder or Knowledge Enrichment. Many of the roles below exist for service users rather than people. Named users can also read Cloud Content Repository content without a Cloud Content Repository role, through the Knowledge Discovery roles.
Content Lake Roles
| Role | Role key | Permissions | Assign to |
|---|---|---|---|
| Content Lake Administrator | content-lake.administrator | hxpr.manage.everything; content-lake-api.documents.read, .create, .edit, .delete. No graph permissions. | Administrators, and internal services such as ingestion and Data Curation. The holder bypasses ACL filtering and sees every document in the environment, so give it to a person only where a tenant-wide view is intended. |
| Content Lake User | content-lake.user | content-lake-api.documents.read, .create, .edit. No delete, no graph permissions. | Regular users. Subject to normal ACL filtering. |
| Content Lake ECE Administrator | content-lake.ece-administrator | Everything in Content Lake Administrator, plus content-lake-api.graphs.read, .create, .edit, .delete; content-lake-api.graph-configs.read, .create, .edit, .delete; content-lake-api.graph-jobs.read, .create | Administrators who build and maintain knowledge graphs. The only assignable role that can create and delete graphs. |
| Content Lake ECE User | content-lake.ece-user | Everything in Content Lake User, plus content-lake-api.graphs.read and content-lake-api.graph-configs.read | Regular users who consume graph output. Cannot change graphs or start graph jobs. |
| Content Lake Account Manager | content-lake.account-manager | Read-only Nucleus IAM and system-integration permissions (iam.user.list, iam.usergroup.list, system-integrations.user-mapping.read, ...). No content access. | Internal service user only. Cloud Content Repository uses it to interpret ACLs. Not for customer assignment. |
Cross-Application Roles
| Role | Role key | Permissions | Assign to |
|---|---|---|---|
| Context, Data Curation and Content Lake Group | cin-context-data-curation-content-lake.user | cin-data-curation.api.submit; cin-context-api.contentprocessing.read, .write; content-lake-api.documents.*; hxpr.manage.everything. No graph permissions. | Service users for the non-graph ingestion path. Usually assigned automatically at deploy time. Never to a person. |
| Enterprise Context Engine Ingestion | ece-ke.user | Everything in the group role above, plus ece-ke-api.knowledgegraph.read, .write, .edit, .delete | Service users for graph-aware ingestion. Never to a person. The environment's subscription must include all four app keys (ece-ke, cin-context-api, cin-data-curation-api, content-lake). |
| Knowledge Discovery Manager | hxai-insight-discovery.manager | Content Lake: content-lake-api.documents.read, content-lake-api.graph-configs.create, plus Knowledge Discovery permissions | Administrators |
| Knowledge Discovery User | hxai-insight-discovery.user | Content Lake: content-lake-api.documents.read, plus Knowledge Discovery permissions | Regular users. The role most named end users hold. |
Permissions Required per API Group
The Cloud Content Repository service checks permission strings, not role names. A role grants access to an endpoint only if it carries the permission listed below. Roles are mapped to permissions in the identity platform, so check a role's permissions in the Admin Portal rather than relying on its name. Every endpoint requires a valid token. hxpr.manage.everything satisfies every check in this table.
| API Group | Endpoint | Required Permission |
|---|---|---|
| Query APIs | All | None (valid user token); results are filtered by the user's document ACLs |
| Download APIs | All | None (valid user token); document ACLs apply |
| Embeddings APIs | All | None (valid user token); document ACLs apply |
| GraphDB APIs | GET /api/graph/graphdbs, GET /api/graph/graphdbs/{graphDBId} | content-lake-api.graph-configs.read |
| GraphDB APIs | POST /api/graph/graphdbsDELETE /api/graph/graphdbs/{graphDBId} | content-lake-api.graph-configs.createcontent-lake-api.graph-configs.delete |
| Ontology APIs | GET /api/graph/ontologies, GET /api/graph/ontologies/{ontologyId}, GET /api/graph/graphdbs/{graphDBId}/config | content-lake-api.graph-configs.read |
| Ontology APIs | POST /api/graph/ontologies | content-lake-api.graph-configs.create |
| Ontology APIs | DELETE /api/graph/ontologies/{ontologyId} | content-lake-api.graph-configs.delete |
| Ontology APIs | PUT /api/graph/graphdbs/{graphDBId}/ontologyroutes | content-lake-api.graph-configs.edit |
| Graph Rebuild APIs | POST /api/v1/graph/rebuild | Both content-lake-api.graph-jobs.create and content-lake-api.graphs.edit |
| Graph Rebuild APIs | GET /api/v1/graph/rebuild, GET /api/v1/graph/rebuild/{jobId} | content-lake-api.graph-jobs.read |
| Graph Data APIs | POST /api/graph/graphdbs/{graphDBId}/aggregate | content-lake-api.graphs.read; results are filtered by the caller's ACLs |
| ECE Graph Query APIs | POST /api/ece/business-objects/{graphDBId}/search, GET /api/ece/business-objects/{graphDBId}/{id}, .../{id}/related, .../{id}/shared-attributes, .../{id}/shared-attributes/{attrId}/provenance | content-lake-api.graphs.read; results are filtered by the caller's ACLs |
Persona-Based Role Assignments
Use this table to assign the right roles based on what the person or service user needs to do:
| Persona | Typical Function | Token | Assign These Roles |
|---|---|---|---|
| End user of a search or RAG application | Searches and reads documents they are entitled to | User | Knowledge Discovery User, or Content Lake User |
| End user of graph-aware applications | Reads graph output | User | Content Lake ECE User |
| Knowledge graph administrator | Creates graph databases, ontologies and routes, runs rebuilds | User or service user | Content Lake ECE Administrator |
| Service user (ingestion) | Machine-to-machine document push from source systems | Service user | Context, Data Curation and Content Lake Group |
| Service user (graph-aware ingestion) | Ingestion that also writes to the knowledge graph | Service user | Enterprise Context Engine Ingestion |
Common mistakes: Searching with a service user token: search needs the end user's token (see How Authentication Works). Giving a person a role that carries hxpr.manage.everything (Content Lake Administrator, Content Lake ECE Administrator, or the cross-application ingestion roles) when they do not need a tenant-wide view. Using a single shared service user for all integrations (one per integration isolates blast radius).
Common Authentication Errors
401 Unauthorized — invalid or missing token
Returned when the token is missing, malformed, or expired. The request is rejected before it reaches the API, and the response has an empty body (no Content-Type), so check the status code rather than trying to parse JSON.
# No Authorization header
curl -i -X POST "https://{environment_id}.content-lake.ai.experience.hyland.com/api/query" \
-H "Content-Type: application/json" -d '{"query": "SELECT * FROM SysContent"}'
HTTP/1.1 401 Unauthorized
(empty body)
# Malformed or expired token: same result
curl -i -X POST ".../api/query" -H "Authorization: Bearer abc.def.ghi" ...
HTTP/1.1 401 Unauthorized
(empty body)
Common causes and fixes:
- No
Authorizationheader — addBearer <token> - Token has expired — request a new one (they last 15 minutes)
- Token was issued against the wrong token endpoint for your environment
- Malformed header — ensure it reads
Bearer <token>with a single space
401 Unauthorized — missing permission
When the token is valid but lacks the permission a graph endpoint requires (see Permissions Required per API Group), the service's error handler maps the access-denied error to 401, not 403. The body uses the standard Cloud Content Repository error format:
{
"entity-type": "exception",
"status": 401,
"message": "<reason the request was denied>"
}
Common causes and fixes:
- The caller's user group has no role carrying the required permission — check role assignments in the Admin Portal
POST /api/v1/graph/rebuildneeds bothcontent-lake-api.graph-jobs.createandcontent-lake-api.graphs.edit
Document access: The Query, Download and Embeddings APIs do not return a permission error for documents the caller cannot see. Query results leave those documents out, and fetching one directly returns 404.
404 Not Found
A valid token that asks for something that does not exist (or that the caller cannot see) gets a 404 with the standard error body. The message varies by endpoint:
GET /api/documents/00000000-0000-0000-0000-00000000dead
HTTP/1.1 404
{"entity-type":"exception","status":404,"message":"00000000-0000-0000-0000-00000000dead"}
GET /api/download/url/{docId}/nosuch_blob (Accept: */*)
HTTP/1.1 404
{"entity-type":"exception","status":404,"message":"No blob found"}
GET /api/graph/ontologies/00000000-0000-0000-0000-000000000000
HTTP/1.1 404
{"entity-type":"exception","status":404,"message":"Ontology not found: 00000000-0000-0000-0000-000000000000"}
GET /api/graph/graphdbs/00000000-0000-0000-0000-000000000000
HTTP/1.1 404
(empty body)
406 Not Acceptable
The Accept header requests a media type the endpoint does not produce. For JSON endpoints use application/vnd.hxcs.v1+json, application/vnd.hxcs+json or application/json. For the streaming download use application/octet-stream, application/vnd.hxcs+octet-stream, application/vnd.hxcs.v1+octet-stream or */*. For the pre-signed URL endpoint use */*. The body is either empty or the standard error object:
# JSON Accept on a binary download
curl -i ".../api/download/{docId}/sysfile_blob" -H "Authorization: Bearer $USER_TOKEN" \
-H "Accept: application/vnd.hxcs.v1+json"
HTTP/1.1 406
{"entity-type":"exception","status":406,"message":"No acceptable representation"}
# Pre-signed URL: only */* works
curl ".../api/download/url/{docId}/sysfile_blob" -H "Accept: text/plain" # 406
curl ".../api/download/url/{docId}/sysfile_blob" -H "Accept: */*" # 200, text/plain URL
# Unsupported type on a JSON endpoint
curl -i -X POST ".../api/query" -H "Accept: application/xml" ...
HTTP/1.1 406
(empty body)
Download error gotcha: If a streaming download fails (unknown document or property) while you send Accept: application/octet-stream, the service cannot write its JSON error body and returns 500 with an empty body. Send Accept: */* to get the real 404: No document found for an unknown document, or No blob found for an unknown property.
Worked Examples
The same request with the variations you are most likely to hit. Set BASE=https://{environment_id}.content-lake.ai.experience.hyland.com, and USER_TOKEN to a fresh access token for a signed-in user (see User Authentication). Search and download calls must use the user's token so that document ACLs are applied to that user.
Get a service user token (ingestion and graph administration only)
Use this token for the graph and ingestion APIs, not for the search and download examples below.
SERVICE_TOKEN=$(curl -s -X POST https://auth.iam.experience.hyland.com/idp/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" \
-d "grant_type=client_credentials" \
-d "scope=hxp environment_authorization hxp.integrations hxpr" | jq -r .access_token)
Success: 200 with a JSON body
curl -s -X POST "$BASE/api/query" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" -H "Accept: application/vnd.hxcs.v1+json" \
-d '{"query": "SELECT * FROM SysContent", "limit": 2}'
Response:
{
"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
}
Success: 200 with a plain-text body (pre-signed URL)
curl -s "$BASE/api/download/url/{docId}/sysfile_blob" \
-H "Authorization: Bearer $USER_TOKEN" -H "Accept: */*"
Response:
https://clake-....s3.us-east-1.amazonaws.com/storage/5d7ce8a8...?response-content-disposition=attachment%3B%20filename%3Djatc.docx&...&X-Amz-Expires=3600&...
Success: binary stream
curl -s -D - -o contract.docx "$BASE/api/download/{docId}/sysfile_blob" \
-H "Authorization: Bearer $USER_TOKEN" -H "Accept: application/octet-stream"
Response:
HTTP/1.1 200
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Length: 59049
Content-Disposition: attachment; filename=jatc.docx
Accept-Ranges: bytes
ETag: "5d7ce8a87cb3809a08f1d3dacc84e7bf5504cb603e7703f7d35056092634cf47"
Last-Modified: 1790853760535
Failure variations at a glance
| What you send | Status | Body |
|---|---|---|
No Authorization header | 401 | Empty |
| Expired or malformed token | 401 | Empty |
JSON Accept on /api/download/{docId}/... | 406 | {"entity-type":"exception","status":406,"message":"No acceptable representation"} |
Accept: text/plain on /api/download/url/... | 406 | Empty |
Unknown document ID on /api/documents/{docId} | 404 | {"entity-type":"exception","status":404,"message":"<docId>"} |
Invalid HXQL (e.g. SELECT * FROM SysContent WHERE) | 400 | {"entity-type":"exception","status":400,"message":"Query: ... mismatched input '<EOF>' ..."} |
Security property in a query (e.g. WHERE sys_acl = 1) | 400 | {"entity-type":"exception","status":400,"message":"Disallowed system property: sys_acl"} |
Quick Checklist
Before your first API call, verify:
- You have your
environment_id(UUID) - You have the client registrations you need: a Web Server Client (or JavaScript Client) for user sign-in, and a Service Client (
client_idandclient_secret) for ingestion and graph administration - You know your token endpoint URL
- You use a user token for search and retrieval, and a service user token only for ingestion and graph administration
- The caller's user group has a role that carries the permissions you need (graph APIs only)
- You can successfully get a 200 response from the token endpoint
- You can decode the JWT and confirm
expis in the future andhxp_authorization.environment_idmatches your base URL