SDK Packages & Exports
Everything you need is available from @ci-hub/integration-sdk. You don't need to install Node.js built-ins or most third-party libraries separately.
import {
// Integration
defineIntegration,
// Response helpers
send, sendResult, sendError, sendStatus, sendAccessToken, sendRedirectUri,
// Auth state helpers
getStateCodeAsync, getStateAdapterDataAsync, setStateAdapterDataAsync,
// Rendering
render, ensureHttps,
// Capabilities
defaultFolderCapabilities, defaultAssetCapabilities,
// Enums
CategoryEnum, HashAlgorithmEnum, FieldTypeEnum,
// Node.js built-ins
fs, path, crypto,
// Third-party
axios, config, mimeTypes, oauth2,
} from '@ci-hub/integration-sdk'Import only what you use.
Integration
defineIntegration(config)
The entry point for every adapter. Accepts a configuration object with metadata and all handler functions. Returns the integration definition.
All handler signatures are fully typed — the SDK infers the correct locals and requestData types for each handler, so you don't need to annotate them manually.
export default defineIntegration({
name: 'My DAM',
version: '1.0.0',
logo,
glyph,
contact,
capabilities: { /* ... */ },
login: async (locals, requestData) => { /* ... */ },
// ... other handlers
})Response helpers
send(data)
Successful response with a payload. Use for all content handlers that return data.
return send({ assets: [], folders: [], filters: [], more: undefined })sendResult(error, data)
Explicit null-error form of send. Pass null as the first argument on success.
return sendResult(null, { filters: [] })sendError(message, statusCode)
Handler failed. The message is shown to the user in the CI HUB UI.
return sendError(`Error searching assets: ${error}`, 400)sendStatus(code)
Success with no response body. Use for deleteAsset, deleteFolder, logout.
return sendStatus(200)sendAccessToken(error?, token?, expiresIn?, refreshToken?, refreshExpiresIn?, adapterData?)
Auth handlers only. Stores the session or reports an auth error.
// Success
return sendAccessToken(null, accessToken, expiresIn, refreshToken, undefined, {
resourceEndpoint: serverUrl,
orgId: selectedOrg,
})
// Failure
return sendAccessToken('Invalid credentials. Please try again.')
// Clean abort (user canceled)
return sendAccessToken()sendRedirectUri(url)
Redirects the user to a URL. Used during OAuth flows to redirect to the authorization endpoint, or to redirect back to the adapter endpoint to initialize state.
return sendRedirectUri(authorizationUrl)Auth state helpers
These persist data between redirect steps in multi-phase login flows. Data is stored server-side only.
getStateCodeAsync()
Generates an opaque, cryptographically random state token.
const state = await getStateCodeAsync()setStateAdapterDataAsync(state, data)
Stores arbitrary data against a state token.
await setStateAdapterDataAsync(state, { serverUrl, verifier })getStateAdapterDataAsync(state)
Retrieves data stored for a state token.
const { serverUrl, verifier } = await getStateAdapterDataAsync(state)Rendering
render(templatePath, data)
Renders an .ect HTML template with the provided data. Returns the rendered HTML as a response. The .ect extension is added automatically.
return render(path.join(import.meta.dirname, 'login'), {
action: locals.endpointUrl,
logo,
contact: config.get('myIntegration.contact'),
})ensureHttps(serverUrl, originOnly, dontExcept)
Normalizes a URL to HTTPS. Use on all user-provided server URLs before storing or calling them.
| Parameter | Type | Description |
|---|---|---|
serverUrl | string | The URL to normalize |
originOnly | boolean | If true, returns only the origin (protocol + hostname), stripping path/query/hash |
dontExcept | boolean | If true, returns empty string on invalid URL instead of throwing |
const normalized = ensureHttps(userUrl, true, true)
// 'https://example.com' — origin only, no throw on bad inputCapabilities
defaultFolderCapabilities
Default capability object for folders. All destructive flags default to false. Spread this first, then override:
const caps = { ...defaultFolderCapabilities, canAddAsset: true, canAddFolder: true }defaultAssetCapabilities
Default capability object for assets. Spread this first, then override with values derived from your system's permission data:
const caps = { ...defaultAssetCapabilities, canUpdateAsset: item.permissions.canEdit }Types
Locals
Type for the first argument of every handler. Inferred automatically inside defineIntegration — only import it when you need to type a helper function outside the integration definition:
import type { Locals } from '@ci-hub/integration-sdk'
async function callApi(locals: Locals, endpoint: string) { /* ... */ }TypedRequestData<Q, B, P>
Generic type for the second argument of handlers. Each handler inside defineIntegration gets a specialized version automatically (e.g., TypedRequestData<SearchQuery, SearchBody> for search).
Import this when you need to type a helper that receives request data:
import type { TypedRequestData, SearchQuery, SearchBody } from '@ci-hub/integration-sdk'
function parseSearchParams(requestData: TypedRequestData<SearchQuery, SearchBody>) {
const query = requestData.query.query || ''
// ...
}IntegrationInfo
Type for the return value of the info handler.
Enums
CategoryEnum
Integration category. Use in capabilities.category:
capabilities: { category: CategoryEnum.DAM }HashAlgorithmEnum
Asset deduplication strategy. Use in capabilities.assetHashAlgorithm:
capabilities: { assetHashAlgorithm: HashAlgorithmEnum.FILE_ATTRIBUTES }FieldTypeEnum
Custom metadata field types. Use in checkToken values and custom metadata declarations:
values: [{ id: 'email', name: 'Email', value: user.email, type: FieldTypeEnum.TEXT }]Node.js built-ins
| Export | Module | Common use |
|---|---|---|
fs | node:fs | fs.readFileSync — loading logo/glyph images |
path | node:path | path.join(import.meta.dirname, 'logo.png') |
crypto | node:crypto | crypto.randomBytes, crypto.createHash — PKCE helpers |
Third-party libraries
axios
Pre-configured Axios instance for HTTP requests.
const { data } = await axios.get(url, { headers, params })
const { data } = await axios.post(url, body, { headers })
await axios.put(uploadUrl, buffer, {
headers: { 'Content-Type': mimeType },
maxContentLength: Infinity,
maxBodyLength: Infinity,
})config
Typed config reader backed by node-config.
config.has('myIntegration.clientId') // boolean — always check optional keys
config.get('myIntegration.clientId') // string | number | object — throws if missing
config.get<number>('myIntegration.limit') // typed getmimeTypes
MIME type lookup from mime-types.
mimeTypes.lookup('photo.jpg') // → 'image/jpeg'
mimeTypes.extension('image/jpeg') // → 'jpeg'
mimeTypes.lookup(filename) || 'application/octet-stream' // safe fallbackoauth2
OAuth 2.0 client from simple-oauth2. Used to implement authorization code + PKCE flows.
const client = new oauth2.AuthorizationCode({
client: { id: 'clientId', secret: '' },
auth: { tokenHost, tokenPath, authorizePath, revokePath },
options: { bodyFormat: 'form' },
})
const authUrl = client.authorizeURL({ redirect_uri, scope, state, code_challenge })
const { token } = await client.getToken({ code, redirect_uri, code_verifier })
const existing = client.createToken({ refresh_token })
const { token: t} = await existing.refresh({ grant_type: 'refresh_token' })
const t = client.createToken({ access_token, refresh_token })
await t.revokeAll()See Login Flow → for the full OAuth 2.0 + PKCE implementation.