Skip to main content

Downloading Content

This tutorial shows how to get a document's binary content out of the Cloud Content Repository. You can stream it through the API, or ask for a temporary pre-signed URL that a browser or another service can fetch without a bearer token. It also covers range requests, conditional re-downloads, integrity digests, and the Accept header rules that decide which errors you see.

Prerequisites​

  • A valid user access token for the signed-in user you are searching for, not a service user token: document ACLs are applied to the caller (see Authentication)
  • The base URL of your environment
  • The sys_id of a document with a blob, for example from a query or the sysembed_docId of a vector-search hit

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 of the signed-in user>"
The Accept header matters on these endpoints

Download endpoints don't produce JSON. If you send the JSON Accept: application/vnd.hxcs.v1+json header used by the other APIs, you get 406 {"entity-type":"exception","status":406,"message":"No acceptable representation"}. Use Accept: */* for the pre-signed URL endpoint, and */* or application/octet-stream for the stream endpoint (see the error notes below for why */* is safer).

Tutorial Overview​

In this tutorial, you'll learn to:

  1. Choose between streaming and a pre-signed URL
  2. Get a pre-signed download URL
  3. Stream the file, a byte range, or only a changed file
  4. Read the response headers
  5. Recognize the common errors

Choose a Download Method​

MethodEndpointUse when
Pre-signed URLGET /api/download/url/{docId}/{propertyXPathAndFilename}Something other than your service fetches the file (a browser, a viewer, a downstream job). The URL works without the bearer token until it expires.
StreamGET /api/download/{docId}/{propertyXPathAndFilename}Your service reads the bytes itself. Supports Range, conditional requests and content digests.

In both endpoints, propertyXPathAndFilename is the XPath to the property holding the blob (e.g., sysfile_blob, cin_blobs/0/blob or sysfiles_blobs/0). On the stream endpoint you can append a filename (e.g., sysfile_blob/contract.docx or cin_blobs/0/blob/statement.pdf) to set the download filename; the pre-signed URL endpoint ignores it. Without a filename, the blob's own filename is used, which for ingested documents can be a generated ID rather than the original filename.


Get a Pre-signed Download URL​

Returns a temporary pre-signed S3 URL for downloading a blob. The URL can be used without the bearer token until it expires.

GET {{baseUrl}}/api/download/url/{docId}/sysfile_blob
Authorization: Bearer {{accessToken}}
Accept: */*

Response​

200 with Content-Type: text/plain;charset=UTF-8. The body is the URL as plain text, not a JSON object. The signed URL carries the filename in response-content-disposition and the lifetime in X-Amz-Expires.

Query Parameters​

ParameterTypeRequiredDescription
expirationintegerNoURL lifetime in seconds. Defaults to the server's configured lifetime (3600 seconds in the tested environment), which is also the maximum: larger values are silently capped, and 0 or negative values use the default. A non-numeric value returns 400. Once the URL expires, the storage service returns 403 (Request has expired).
inlinebooleanNoWhen true, the signed URL serves the content with Content-Disposition: inline instead of attachment. Honoured only for PDF and images. Other types, including text/plain, Office documents, JSON, XML, HTML and application/octet-stream, are always served as attachment.

Headers​

HeaderRequiredDescription
AcceptNo*/* is recommended (omitting Accept works the same). A JSON type returns 406 with a JSON body; text/plain returns 406 with an empty body. With application/octet-stream, errors return an empty 500 instead of the real 404.
HXCS-REPOSITORYNoRepository ID (default: default)

Examples​

1. Default lifetime, served as an attachment

curl -H "Authorization: Bearer <token>" -H "Accept: */*" \
"https://{environment_id}.content-lake.ai.experience.hyland.com/api/download/url/{docId}/sysfile_blob"

# 200 text/plain
https://clake-{bucket}.s3.us-east-1.amazonaws.com/storage/{digest}?response-content-disposition=attachment%3B%20filename%3Dcontract.docx&X-Amz-Algorithm=AWS4-HMAC-SHA256&...&X-Amz-Expires=3600&X-Amz-Signature=...

2. Short-lived URL for in-browser viewing

curl -H "Authorization: Bearer <token>" -H "Accept: */*" \
"https://{environment_id}.content-lake.ai.experience.hyland.com/api/download/url/{docId}/sysfile_blob?expiration=60&inline=true"

# 200 text/plain — for a PDF blob, the URL contains:
...response-content-disposition=inline%3B%20filename%3Dcontract.pdf...&X-Amz-Expires=60...

3. Wrong Accept header

curl -H "Authorization: Bearer <token>" -H "Accept: application/vnd.hxcs.v1+json" \
"https://{environment_id}.content-lake.ai.experience.hyland.com/api/download/url/{docId}/sysfile_blob"

# 406
{"entity-type":"exception","status":406,"message":"No acceptable representation"}

Errors​

StatusBodyWhen
401(empty)Missing or invalid token
404{"entity-type":"exception","status":404,"message":"No document found"}Unknown docId
404{"entity-type":"exception","status":404,"message":"No blob found"}The document exists but has no blob at that property (e.g., a typo such as nosuch_blob)
406{"entity-type":"exception","status":406,"message":"No acceptable representation"}Accept is a JSON type
406(empty)Accept is text/plain
500(empty)Unknown document or blob and Accept: application/octet-stream. Send */* to get the real 404.
501Pre-signed URL not available for this blob providerA pre-signed URL is not available for the storage provider holding this blob

Stream the File​

Streams the blob directly as binary content. Supports range requests, conditional requests and content digests.

GET {{baseUrl}}/api/download/{docId}/sysfile_blob
Authorization: Bearer {{accessToken}}
Accept: */*

Query Parameters​

ParameterTypeRequiredDescription
inlinebooleanNoWhen true, sends Content-Disposition: inline; filename=... instead of attachment; filename=.... Honoured for PDF, images and text/plain; other types, including Office documents, JSON, XML, HTML and application/octet-stream, are always served as attachment.

Request Headers​

HeaderRequiredDescription
AcceptNo*/* (recommended; omitting it works the same), application/octet-stream, application/vnd.hxcs+octet-stream or application/vnd.hxcs.v1+octet-stream. A JSON Accept returns 406 with a JSON body; other types such as text/plain or application/pdf return 406 with an empty body.
HXCS-REPOSITORYNoRepository ID (default: default)
RangeNoRequest a byte range (e.g., bytes=0-99 or a suffix such as bytes=-500); returns 206 Partial Content with Content-Range. A range beyond the end of the blob returns 200 with the full content.
If-None-MatchNoAn ETag from a previous response (or *); returns 304 with no body if it matches
If-Modified-SinceNoA timestamp in epoch milliseconds (the format of this API's Last-Modified header); returns 304 if the document has not been modified since. Last-Modified follows the document's sys_modified, so it changes when the document is updated (for example by enrichment), not only when the file changes.
Want-DigestNoe.g. sha-256. Adds a Digest response header when the blob's digest algorithm matches

Response Headers and Status Codes​

The body is the binary content, with Content-Type set from the blob's MIME type (application/vnd.hxcs.v1+octet-stream if the blob has none). The response headers are:

Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Length: 59049
ETag: "5d7ce8a87cb3809a08f1d3dacc84e7bf5504cb603e7703f7d35056092634cf47"
Last-Modified: 1790853760535
Content-Disposition: attachment; filename=contract.docx
Accept-Ranges: bytes

Last-Modified is in epoch milliseconds, not an HTTP date. Content-Range is added for range requests and Digest when requested. Every response also carries X-Content-Type-Options: nosniff and Cache-Control: no-cache, no-store, max-age=0, must-revalidate; a 304 instead carries Cache-Control: max-age=15, must-revalidate.

StatusDescription
200Full content returned
206Partial content returned for a Range request
304Not modified (If-None-Match / If-Modified-Since matched); no body
401Missing or invalid token (empty body)
404Document or blob not found. Returned only when Accept allows JSON, such as */*; the body is {"entity-type":"exception","status":404,"message":"No document found"}
406Accept is a JSON type (JSON body) or another non-binary type (empty body)
500Document or blob not found and Accept: application/octet-stream: the error can't be rendered in that media type, so you get an empty 500 instead of the 404
Send Accept: */* to see real errors

With Accept: application/octet-stream, an unknown docId or a missing blob comes back as an empty 500. With Accept: */*, the same request returns 404 with a JSON message.

Examples​

1. Download the file

curl -H "Authorization: Bearer <token>" -H "Accept: */*" -o contract.docx \
"https://{environment_id}.content-lake.ai.experience.hyland.com/api/download/{docId}/sysfile_blob"
# 200, Content-Disposition: attachment; filename=contract.docx

2. First 100 bytes only

curl -H "Authorization: Bearer <token>" -H "Accept: */*" -H "Range: bytes=0-99" \
"https://{environment_id}.content-lake.ai.experience.hyland.com/api/download/{docId}/sysfile_blob"
# 206, Content-Range: bytes 0-99/59049, Content-Length: 100

3. Re-download only if changed

curl -H "Authorization: Bearer <token>" -H "Accept: */*" \
-H 'If-None-Match: "5d7ce8a87cb3809a08f1d3dacc84e7bf5504cb603e7703f7d35056092634cf47"' \
"https://{environment_id}.content-lake.ai.experience.hyland.com/api/download/{docId}/sysfile_blob"
# 304, no body

4. Inline with an integrity digest

curl -H "Authorization: Bearer <token>" -H "Accept: */*" -H "Want-Digest: sha-256" \
"https://{environment_id}.content-lake.ai.experience.hyland.com/api/download/{docId}/sysfile_blob?inline=true"
# 200 for a PDF blob: Content-Disposition: inline; filename=contract.pdf
# Digest: SHA-256=XXzoqHyzgJoI8dPazITnv1UEy2A+dwP301BWCSY0z0c=

5. Unknown document

# Accept: */*
404 {"entity-type":"exception","status":404,"message":"No document found"}

# Accept: application/octet-stream
500 (empty body)

HEAD variant: HEAD /api/download/{docId}/{propertyXPathAndFilename} returns 204 with Content-Disposition, ETag, Last-Modified and Accept-Ranges, but no Content-Type, Content-Length or body. With a Range header it returns 206 with Content-Range. It can also return 304 or 404, as above.


Next Steps​