CI HUBCI HUB SDK
Getting Started

Installation

Prerequisites

  • Node.js 20.19+ or 22.12+
  • TypeScript 5 or later
  • An existing DAM or content platform with an API you can call

The fastest way to get started is the create command. It scaffolds a ready-to-run TypeScript project with the SDK pre-configured:

npm create @ci-hub/integration my-integration
pnpm create @ci-hub/integration my-integration
yarn create @ci-hub/integration my-integration
bun create @ci-hub/integration my-integration

CLI options

FlagDescription
--overwriteRemove existing files in the target directory before scaffolding
--installAuto-install dependencies (default: interactive prompt)
--no-installSkip dependency installation

After scaffolding, start the development server:

cd my-integration
npm run dev

The SDK starts a local server and exposes your integration at http://localhost:8080. You can point a CI HUB development environment at this URL to test your integration end to end.

Manual install

If you prefer to set up a project from scratch:

npm install @ci-hub/integration-sdk
pnpm add @ci-hub/integration-sdk
yarn add @ci-hub/integration-sdk
bun add @ci-hub/integration-sdk

TypeScript configuration

The scaffolded project includes a tsconfig.json. If you're setting up manually, your config should have at minimum:

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

The SDK ships with full type definitions — you get autocomplete and type checking for all handler signatures, locals, requestData, and response helpers.

The full defineIntegration skeleton

Below is the complete list of handlers you can implement. Start with the ones your platform supports and add more over time. Handlers you don't implement are simply omitted.

Every handler inside defineIntegration is fully typed — the SDK infers the correct locals and requestData types for each handler. For stricter compile-time types on adapterData, per-handler requestData, and OAuth state, pass a type config to defineIntegration. See Typed Integrations.

src/index.ts
import {
  defineIntegration,
  send, sendResult, sendError, sendStatus, sendAccessToken, sendRedirectUri,
  defaultFolderCapabilities, defaultAssetCapabilities,
  config, fs, path, CategoryEnum,
} from '@ci-hub/integration-sdk'

const imageBase64 = (name: string) =>
  `data:image/png;base64,${Buffer.from(fs.readFileSync(path.join(import.meta.dirname, name))).toString('base64')}`

const logo  = { data: imageBase64('logo.png'),  width: 600, height: 134, backgroundColor: '#FFFFFF' }
const glyph = { data: imageBase64('glyph.png'), width: 80,  height: 80,  backgroundColor: '#FFFFFF' }

export default defineIntegration({
  name: 'My DAM',
  version: '1.0.0',
  logo,
  glyph,
  contact: config.get('myIntegration.contact'),

  capabilities: {
    category: CategoryEnum.DAM,
    assetUploadLimitInMB: 500,
    directAssetDownload: true,
    assetSearch: {
      supportsParentId: true,
    },
    description: {
      en: 'Connect to My DAM from CI HUB.',
    },
  },

  // ─── Auth (required) ─────────────────────────────────────────────────────
  login:        async (locals, requestData) => { /* ... */ }, 
  logout:       async (locals, requestData) => { /* ... */ }, 
  checkToken:   async (locals)              => { /* ... */ }, 
  refreshToken: async (locals)              => { /* ... */ }, // optional — OAuth only

  // ─── Content (implement what your platform supports) ──────────────────────
  search:      async (locals, requestData) => { /* ... */ },
  getFolder:   async (locals, requestData) => { /* ... */ },
  download:    async (locals, requestData) => { /* ... */ },
  createAsset: async (locals, requestData) => { /* ... */ },
  updateAsset: async (locals, requestData) => { /* ... */ },
  deleteAsset: async (locals, requestData) => { /* ... */ },

  // ─── Folders ─────────────────────────────────────────────────────────────
  createFolder: async (locals, requestData) => { /* ... */ },
  renameFolder: async (locals, requestData) => { /* ... */ },
  deleteFolder: async (locals, requestData) => { /* ... */ },
  moveFolder:   async (locals, requestData) => { /* ... */ },

  // ─── Asset actions ───────────────────────────────────────────────────────
  lockAsset:        async (locals, requestData) => { /* ... */ },
  renameAsset:      async (locals, requestData) => { /* ... */ },
  moveAsset:        async (locals, requestData) => { /* ... */ },
  getAssetVersions: async (locals, requestData) => { /* ... */ },

  // ─── Tasks ───────────────────────────────────────────────────────────────
  searchTasks:           async (locals, requestData) => { /* ... */ },
  getTask:               async (locals, requestData) => { /* ... */ },
  getTaskAssets:         async (locals, requestData) => { /* ... */ },
  addTaskComment:        async (locals, requestData) => { /* ... */ },
  addTaskAsset:          async (locals, requestData) => { /* ... */ },
  deleteTaskAsset:       async (locals, requestData) => { /* ... */ },
  updateCustomTaskState: async (locals, requestData) => { /* ... */ },

  // ─── Brand Hub ───────────────────────────────────────────────────────────
  getBrandConfig: async (locals, requestData) => { /* ... */ },
  getBrandAssets: async (locals, requestData) => { /* ... */ },

  // ─── Advanced ────────────────────────────────────────────────────────────
  doTransformation:    async (locals, requestData) => { /* ... */ },
  additionalRendition: async (locals, requestData) => { /* ... */ },
  saveAssetRelations:  async (locals, requestData) => { /* ... */ },
})

config.json

The SDK reads configuration from config.json in your project root (or src/config.json). Structure it with a top-level serverBaseUrl and an integration-specific section keyed by your integration name:

config.json
{
  "serverBaseUrl": "http://localhost:8080",
  "myIntegration": {
    "contact": {
      "text": "If you experience any problems, please contact our support team.",
      "url": { "link": "https://your-platform.example.com/help", "email": "support@your-platform.example.com" }
    },
    "authEndpoint": "https://your-platform.example.com/oauth/authorize",
    "tokenEndpoint": "https://your-platform.example.com/oauth/token",
    "clientId": "your-client-id",
    "clientSecret": "your-client-secret",
    "maxQuerySize": 100,
    "maxFileSizeInMB": 5242880
  }
}

Access values with config.get() and config.has() using dot-separated paths:

src/index.ts
config.get('myIntegration.clientId')       // returns the value or throws if missing
config.get('myIntegration.maxQuerySize')   // nested keys work the same way
config.has('myIntegration.clientSecret')   // returns boolean, never throws

config.get() throws if the key doesn't exist. Pass a default value as the second argument to avoid the throw: config.get('myIntegration.timeout', 30000).

Config validation

Validate required config keys at the top of your index.ts, before defineIntegration. This fails fast on startup instead of at runtime when a handler is called:

src/index.ts
{
  const requiredKeys = ['myIntegration.clientId', 'myIntegration.contact', 'myIntegration.authEndpoint', 'myIntegration.tokenEndpoint']
  const missing = requiredKeys.filter(k => !config.has(k))
  if (missing.length) throw new Error(`Missing configuration: ${missing.join(', ')}`) 
}

The block scope {} keeps requiredKeys and missing out of module scope.

Project structure

Keep all integration logic in index.ts. Split only for long static data: GraphQL query strings (queries.ts), static filter/rendition lists (renditions.ts), or .ect login templates.

Available scripts

The scaffolded project includes npm scripts that wrap the CLI:

ScriptCommandDescription
devnpm run devStart the local development server

The CLI (integration-cli) ships inside @ci-hub/integration-sdk and is not installed as a global binary. The dev script runs it for you; for any other command, use your package manager:

npx integration-cli dev
npx integration-cli auth login
npx integration-cli config --init
npx integration-cli submit
pnpm exec integration-cli dev
pnpm exec integration-cli auth login
pnpm exec integration-cli config --init
pnpm exec integration-cli submit
yarn integration-cli dev
yarn integration-cli auth login
yarn integration-cli config --init
yarn integration-cli submit
bunx integration-cli dev
bunx integration-cli auth login
bunx integration-cli config --init
bunx integration-cli submit

For the full command reference (dev, auth, config, submit), see the CLI reference.

On this page