CI HUBCI HUB SDK
Building an Adapter

Download

Most adapters don't need a download handler. CI HUB fetches assets directly from the downloadUrl returned in asset data. Implement download only when the URL requires server-side resolution — signed/temporary URLs, proxy downloads, or protected content that needs a fresh token exchange.

CI HUB calls this handler when the plugin needs to download an asset and the adapter has registered a download handler.

Inputs

FieldDescription
requestData.query.assetIdAsset to download
requestData.query.lowres"1" for low-resolution preview
requestData.query.noRedirect"true" to return JSON instead of HTTP redirect
requestData.query.localIdOptional rendition/conversion ID

You can add additional query parameters when constructing the download URL.

Response

Two modes based on noRedirect:

// Default — redirect the client to the download URL
return sendRedirectUri(downloadUrl)

// noRedirect=true — return JSON with the URL
return sendResult(null, { downloadUrl: `${freshUrl}$NO_AUTH$` })

Append $NO_AUTH$ to the URL when it's pre-signed or publicly accessible — tells CI HUB not to inject auth headers when fetching.

Patterns

Example - DAM with versioned assets, parses compound assetId::version format:

download: async (locals, requestData) => {
  const id = requestData.query.assetId
  const parts = id.split('::')
  const assetId = parts[0]
  const version = parts.length === 2 ? parts[1] : -1
  const lowres = requestData.query.lowres === '1'
  const noRedirect = requestData.query.noRedirect === 'true'

  try {
    const downloadUrl = await getDownloadLocation(locals, assetId, version, lowres)

    return noRedirect
      ? sendResult(null, { downloadUrl: downloadUrl && `${downloadUrl}$NO_AUTH$` })
      : sendRedirectUri(downloadUrl)
  } catch (err) {
    return sendError(err, 404)
  }
},

Example - DAM with rendition-based downloads:

download: async (locals, requestData) => {
  const noRedirect = requestData.query.noRedirect === 'true'
  const assetId = requestData.query.assetId
  const localId = requestData.query.localId

  try {
    const downloadUrl = await getRenditionUrl(locals, assetId, localId)

    return noRedirect
      ? sendResult(null, { downloadUrl: downloadUrl && `${downloadUrl}$NO_AUTH$` })
      : sendRedirectUri(downloadUrl)
  } catch (err) {
    return sendResult(err)
  }
},

Example - stock platform, checks availability before redirecting:

download: async (locals, requestData) => {
  const id = requestData.query.assetId
  const lowres = requestData.query.lowres === '1'
  const noRedirect = requestData.query.noRedirect === 'true'

  try {
    const downloadUrl = await getDownloadLocation(system, locals, id, lowres)

    if (!downloadUrl) {
      return sendError('Download not available', 404)
    }

    return noRedirect
      ? sendResult(null, { downloadUrl: downloadUrl && `${downloadUrl}$NO_AUTH$` })
      : sendRedirectUri(downloadUrl)
  } catch (err) {
    const msg = (err || '').toString() || 'unexpected error'
    return sendError(msg, 400)
  }
},

URL Suffixes

Append suffixes to thumbnailUrl and downloadUrl in asset data to control how CI HUB authenticates and caches the request. These are processed by CI HUB's URL parser before fetching.

SuffixAuth behaviorWhen to use
$NO_AUTH$No auth added — URL is used as-isCDN thumbnails, pre-signed S3/Azure URLs
$AUTH_PAYLOAD$Token appended as query parameterLegacy APIs with token-in-URL auth
$AUTH_PAYLOAD_BEARER$Bearer {token} as query parameterLegacy Bearer-in-URL pattern
$AUTH_HEADER_PAYLOAD_BEARER$Authorization: Bearer {token} headerGoogle Drive, Box, Sitecore
$AUTH_HEADER_PAYLOAD_APITOKEN$Authorization: apiToken {token} headerAdmiral Cloud, Picturepark
$AUTH_SESSION_ID$Session ID injectedSession-based auth
$FORCE_DOWNLOAD$Forces Content-Disposition: attachmentThumbnail pipeline, forced save
$REQUEST_HEADERS$Custom headers from info.requestHeadersPicturepark (complex auth headers)
$NO_CACHE$Cache-Control: no-cache header, skip cachingVolatile/expiring URLs

Suffix Examples in Asset Data

// Pre-signed URL — no auth needed
thumbnailUrl: `https://cdn.example.com/thumb/abc123.jpg$NO_AUTH$`

// Bearer token auth via header
downloadUrl: `https://api.example.com/assets/123/download$AUTH_HEADER_PAYLOAD_BEARER$`

// API token auth
thumbnailUrl: `https://api.example.com/renditions/456$AUTH_HEADER_PAYLOAD_APITOKEN$`

// No caching + no auth (expiring CDN URL)
downloadUrl: `https://cdn.example.com/temp/signed-url$NO_AUTH$$NO_CACHE$`

// Force download disposition
thumbnailUrl: `https://api.example.com/preview/789$FORCE_DOWNLOAD$`

Suffixes can be combined. CI HUB strips them from the URL before making the request and applies the corresponding auth/cache behavior.

How CI HUB Processes Suffixes

The auth suffixes are mutually exclusive — CI HUB applies the first match:

  1. $REQUEST_HEADERS$ — parses custom headers from the URL's headers query param
  2. $NO_AUTH$ — strips suffix, sends request without auth
  3. $AUTH_PAYLOAD$ — replaces suffix with URL-encoded token
  4. $AUTH_PAYLOAD_BEARER$ — replaces suffix with URL-encoded Bearer {token}
  5. $AUTH_HEADER_PAYLOAD_BEARER$ — strips suffix, adds Authorization: Bearer {token} header
  6. $AUTH_HEADER_PAYLOAD_APITOKEN$ — strips suffix, adds Authorization: apiToken {token} header

$FORCE_DOWNLOAD$, $NO_CACHE$, and $AUTH_SESSION_ID$ are processed independently and can be combined with any auth suffix.

UI Behavior

  • User places/downloads an asset → CI HUB checks for download handler
  • If handler exists → calls it with assetId, uses response URL
  • If no handler → fetches directly from downloadUrl in asset data, applying URL suffix auth
  • noRedirect=true used internally for JSON-based URL resolution

See Download Schema

On this page