Integrations

An integration is a third-party service Catentio can talk to (Discord, GitHub, Anthropic, OpenRouter, and the rest of the built-in catalogue, plus operator-declared custom ones). Each integration is keyed by a slug (e.g. discord), and a single integration can hold several distinct credentials keyed by kind, plus free-form "secrets" catalog entries. list() is the only method in this SDK that actually reaches a working route.

For the runtime resolution model, see API → Integrations.

Auth. /v1/integrations/* requires the control plane's x-catentio-session header, which this SDK never sends. Neither apiKey mode nor Bearer-session mode authenticates against any route in this namespace. See SDKs → Auth: what actually works.

Namespace

catentio.integrations — three methods:

catentio.integrations.list()
catentio.integrations.configure(kind, input)
catentio.integrations.delete(kind)

There's no get, even though the control plane has one (GET /v1/integrations/{slug}) — it's just not wrapped here.

Methods

integrations.list

Signature. catentio.integrations.list(token?): Promise<{ data: Integration[]; meta: { total: number; configured: number; by_category: Array<{ category: string; count: number }> } }>

Returns the whole integration catalogue (built-in + custom), each with a rolled-up configured/health status — not per-credential detail. The SDK does not unwrap this response; you get the full {data, meta} object back.

import { CatentioSaasClient } from '@forjio/catentio-saas-node';

const catentio = new CatentioSaasClient({ session });

const { data: integrations } = await catentio.integrations.list();
for (const i of integrations) {
  console.log(i.slug, i.status, `${i.credentials_set}/${i.credentials_required}`);
}

Real row shape:

interface Integration {
  slug: string;                 // 'discord', 'github', 'anthropic', ...
  name: string;
  category: string;
  description: string;
  is_configured: boolean;
  status: 'ok' | 'unconfigured' | 'invalid' | 'expired' | 'rate_limited' | 'unchecked';
  credentials_required: number;
  credentials_set: number;
  credentials_total: number;
  config_count: number;
  extras: string[];
}

There is no kind (as a top-level field), label, connectedAt, or metadata field on this row — those don't exist. (kind is real, but it's a property of an individual credential inside an integration, not of the integration row itself — and this SDK doesn't expose the detail endpoint that shows individual credentials.)

integrations.configure — targets a route that does not exist

Signature. catentio.integrations.configure(kind, input, token?): Promise<Integration>

This call fails. It sends PUT /v1/integrations/{kind}. That route doesn't exist — the real route is PUT /v1/integrations/{slug}/credentials/{kind}, which takes two path segments (which integration, which credential kind), not one. This SDK method only accepts one identifier, so it cannot construct the real URL at all.

The real route is also OTP-gated: without x-catentio-otp-challenge + x-catentio-otp-code headers, it returns 403 with {"otp_required": true, "challenge_id", "scope", "expires_at"} in the body. Neither header is something this SDK sends. If you need to set a credential today, call the real two-segment route directly and handle the OTP challenge/response flow yourself.

integrations.delete — targets a route that does not exist

Signature. catentio.integrations.delete(kind, token?): Promise<void>

This call fails. It sends DELETE /v1/integrations/{kind}. The real route is DELETE /v1/integrations/{slug}/credentials/{kind} (two path segments, plus optional scope and customer_id query params), and it is OTP-gated the same way configure is — see above.

Common patterns

Health-check connected integrations

The one thing this namespace can actually do is read the rollup:

const { data: integrations } = await catentio.integrations.list();
const broken = integrations.filter(i => i.status !== 'ok');
if (broken.length) {
  alertOps(`Catentio integrations need attention: ${broken.map(i => i.slug).join(', ')}`);
}

Inspect what's connected before depending on it

const { data: integrations } = await catentio.integrations.list();
const github = integrations.find(i => i.slug === 'github');
if (!github?.is_configured) {
  throw new Error('Configure the github integration first (via the REST API, not this SDK).');
}

Errors

The real error body is FastAPI's {"detail": ...}. Because the wire response never has an error key, the SDK doesn't parse out a structured code/message — you get CatentioError with a generic HTTP_ERROR code and the HTTP reason phrase as the message.

Status Cause
401 Missing/invalid session — every route here requires x-catentio-session, which this SDK never sends.
403 {"otp_required": true, ...} — the real credential-set/clear routes require an OTP challenge/response this SDK never provides.
404 Always, for configure() and delete() (see the warnings above).

Next

  • Tools — the consumers of integration credentials.
  • API → Integrations — HTTP-level reference, including the real two-segment credential routes and the OTP gate.