CI HUBCI HUB SDK
Building an Integration

Custom Methods

Custom methods let you expose server-side functions from your integration that the Direct Access Helper (or any browser code) can call via the CI HUB server. Because they run on the server, they can reach provider APIs that don't support CORS and can use credentials you never want to expose to the browser.

This is an advanced escape hatch — most integrations don't need it. It becomes relevant when your DAH needs to perform a step that the browser cannot do directly, such as generating a signed upload URL using a secret key or calling an internal API that restricts the allowed origins.

How it works

You add extra keys to the defineIntegration object alongside your standard handlers. Each key becomes a callable method name. When the CI HUB server receives a request at:

GET  /api/v1/system/providerInternal/:methodName
POST /api/v1/system/providerInternal/:methodName

it routes the call to the matching function in your integration. The function receives the same locals as every other handler — including locals.adapterData — and a requestData object containing whatever query, body, and params the caller sent.

src/index.ts
import { defineIntegration, send, sendError } from '@ci-hub/integration-sdk'

defineIntegration({
  name: 'My DAM',
  version: '1.0.0',
  capabilities: { category: 'DAM' },

  // Standard handlers
  login: async (locals, requestData) => { /* ... */ },
  search: async (locals, requestData) => { /* ... */ },

  // Custom method
  sign: async (locals, requestData) => {
    try {
      const { uploadParams } = requestData.body
      const result = await generateSignature(uploadParams, locals.adapterData.secretKey)
      return send(result)
    } catch (err) {
      return sendError(err?.message ?? err, 400)
    }
  }
})

Calling from the DAH

Use the standard integration request headers. The Authorization header carries the CI HUB platform token and Provider-Authorization carries the provider token — both available from the helpers object passed to your DAH function.

src/dah.ts
import type { DahFunction } from '@ci-hub/integration-sdk'

const createAsset: DahFunction = async (axios, _payload, _sessionId, data, onProgress, _adapterData, _providerToken, helpers) => {
  // Call the custom method before uploading
  const { data: signatureData } = await axios.post(
    `${process.env.SERVER_BASE_URL}/api/v1/system/providerInternal/sign`, 
    { uploadParams: { fileName: data.name, parentId: data.parentId } },
    {
      headers: {
        'Authorization': 'Bearer '.concat(helpers.getAccessToken()),
        'Provider-Authorization': 'Bearer '.concat(helpers.getProviderAccessToken() || helpers.getPayload())
      }
    }
  )

  // Use signatureData.signature, signatureData.timestamp, etc.
  await axios.put(signatureData.uploadUrl, data.file, {
    headers: { 'Content-Type': 'application/octet-stream' },
    maxContentLength: Infinity,
    onUploadProgress: (e) => onProgress(e.loaded / data.file.size)
  })

  return { id: signatureData.assetId, name: data.name }
}

export default { createAsset } satisfies import('@ci-hub/integration-sdk').DirectAccessHelper

Inject SERVER_BASE_URL at build time via buildDAH's second argument:

src/index.ts
const dahString = await buildDAH(
  path.join(import.meta.dirname, 'dah.ts'),
  {
    'process.env.SERVER_BASE_URL': JSON.stringify(config.get('serverBaseUrl')) 
  }
)

Custom methods can also be called from any other server-side or browser-side context that can send the required headers — they are not exclusive to the DAH.

Request and response

There is no enforced request schema. The integration receives whatever the caller sends:

SourceAvailable as
URL query stringrequestData.query
Request body (JSON)requestData.body
URL path paramsrequestData.params
Connection datalocals.adapterData

Return a response using send(value) or signal an error with sendError(message, statusCode).

Custom methods are not validated against Zod response schemas at runtime. The caller receives whatever JSON your function passes to send.

Typing

Custom method keys use CustomMethodTypeExtension in the generic type config. The shape differs from standard handler keys: it supports locals, requestData, and returnType (but not oauthState).

src/integration-types.ts
import type { IntegrationTypeConfig } from '@ci-hub/integration-sdk'

export type MyIntegrationConfig = {
  adapterData: { secretKey: string }

  sign: { 
    requestData: {
      body: { uploadParams: Record<string, unknown> }
    }
    returnType: { signature: string; timestamp: number; uploadUrl: string }
  }
}

type _Assert = MyIntegrationConfig extends IntegrationTypeConfig ? true : never
src/index.ts
defineIntegration<MyIntegrationConfig>({
  sign: async (locals, requestData) => {
    locals.adapterData.secretKey       // string
    requestData.body.uploadParams      // Record<string, unknown>
    return send({ signature: '...', timestamp: Date.now(), uploadUrl: '...' })
  }
})

See Typed Integrations — Custom methods for the full type reference.

Reserved names

Do not use these as custom method names — they conflict with standard integration keys:

Metadata: see Metadata

Standard handlers: see Standard handlers

Any other key name is valid.

On this page