Integrations

An integration is a catalog entry for a third-party service the runtime talks to on the caller's behalf: OpenAI/Anthropic/OpenRouter/Gemini keys for models, ElevenLabs for TTS, a GitHub PAT for skill-repo PR flow, Google OAuth for Drive/Calendar/Gmail, a Discord bot token, and a handful of others. Integrations live on the runtime; the CP /v1/integrations API is the read/write surface the portal's Dashboard → Integrations page uses.

The integration object

The list endpoint returns a rolled-up summary, not the raw credential rows:

{
  "slug": "github",
  "name": "GitHub",
  "category": "code",
  "description": "Personal access token for skill-repo PR flow.",
  "is_configured": true,
  "status": "ok",
  "credentials_required": 1,
  "credentials_set": 1,
  "credentials_total": 1,
  "config_count": 0,
  "extras": []
}
Field Type Notes
slug string Integration identifier (e.g. github, openai, elevenlabs, google).
name string Display name.
category string Grouping for the portal.
is_configured bool Every required credential is set. If the integration has zero required credentials (e.g. OpenRouter, where the platform key or a BYO override are both optional), this instead means "at least one credential is set" — otherwise a key-less integration would read as configured.
status enum unconfigured, unchecked, ok, rate_limited, expired, invalid. Rolled up from the worst health status across the integration's customer_token credentials; unconfigured beats everything else.
credentials_required / credentials_set / credentials_total int Counts across the integration's declared credential slots.
config_count int Number of non-secret config fields the catalog entry declares.
extras array Catalog-declared extras (integration-specific).

GET /v1/integrations/{slug} returns the same summary fields plus credentials (the per-slot detail: label, required, where, and slot-specific fields — is_set/health_status/token_id for customer_token slots, is_set/dotted for secrets slots, is_set/account_count for oauth_files slots), config, tokens, and extras. Secret values are never returned — only whether a slot is_set.

There is no credential_kinds/configured_kinds/configured_at/configured_by field. Those names describe an older shape. The real contract is the is_configured/status/credentials_* rollup above, plus the per-slot credentials[] array on the detail endpoint.

Endpoints

List integrations

GET /v1/integrations

Returns the catalog rollup for every integration the runtime knows about.

const { data } = await catentio.integrations.list();
curl 'https://catent.io/api/v1/cp/v1/integrations' \
  -H "Cookie: catentio_session=<session cookie set at login>"

Retrieve an integration

GET /v1/integrations/{slug}

One integration with its full credentials[]/config/tokens/extras detail.

Set a credential

PUT /v1/integrations/{slug}/credentials/{kind}

Set or rotate a customer_token-backed credential. Only credential slots whose catalog where is customer_token are writable through this route — the rest (secrets-backed slots, formerly secrets.ini) go through /secrets/{dotted} below, and oauth_files-backed slots (e.g. Anthropic Max accounts) aren't settable via this API at all.

Body is a single generic value, regardless of what the credential actually looks like — the runtime doesn't distinguish an API key from a token by shape:

{ "value": "sk-...", "scope": "shared", "customer_id": "internal" }

value is required (a non-empty string). scope defaults to "shared"; customer_id defaults to "internal".

This endpoint is OTP-gated unconditionally — every customer_token write requires a fresh OTP, not just a "sensitive" subset. See the OTP gate (the payload shape, headers, and email-delivery mechanism are identical to memory's). On success:

{ "otp_required": true, "challenge_id": "V3x9k2mQpR7z", "scope": "integration.set:github:pat:shared", "expires_at": 1778662020.512, "delivery": "email:adi…@gmail.com" }

Re-call with X-Catentio-OTP-Challenge + X-Catentio-OTP-Code headers to actually write the credential.

curl -X PUT 'https://catent.io/api/v1/cp/v1/integrations/github/credentials/pat' \
  -H "Cookie: catentio_session=<session cookie set at login>" \
  -H "Content-Type: application/json" \
  -d '{"value":"ghp_...","scope":"shared"}'

Clear a credential

DELETE /v1/integrations/{slug}/credentials/{kind}?scope=shared&customer_id=internal

Remove a stored customer_token credential. Same OTP gate as set.

Set a secrets-backed credential

PUT /v1/integrations/{slug}/secrets/{dotted}

The sibling route for catalog credentials whose where is secrets — these used to be read-only secrets.ini values (e.g. a platform-level OpenAI key); they're now writable here and land in Secronna at platform/secrets/<KEY>, the same place the runtime reads them back from. {dotted} is the catalog credential's section.key (e.g. openai.api_key). Same OTP gate as the customer_token route.

DELETE /v1/integrations/{slug}/secrets/{dotted}

Clears it. Same OTP gate.

Catalog roster

The real v1 catalog (GET /v1/integrations), 18 entries: anthropic, openrouter, openai, gemini, elevenlabs, supabase, pexels, pixabay, pypi, google, github, discord, slack, telegram, whatsapp, huudis, resend, do_api. The exact list per deployment is whatever GET /v1/integrations returns — runtime versions may add or remove slugs.

tailscale, plugipay, and cloudflare are not in the catalog. If you've seen those slugs referenced as integrations, that's stale — they don't exist as _INTEGRATION_CATALOG entries in the current runtime.

Where integrations are used

  • Custom tools with a header value referencing an integration resolve it against the integrations store at invocation time (see Tools).
  • Agents can be bound to a voice_id that the runtime resolves against the ElevenLabs integration on every TTS call.

Errors

Status When
400 value missing/empty on a credential write; wrong body shape.
403 otp_required — write attempted without OTP headers. Detail carries the challenge.
404 Integration slug, or the specific credential kind/dotted key, doesn't exist on this runtime's catalog.
502 CP couldn't reach the runtime.

There is no 422 unsupported_scope or 422 validation_error on this resource — bad input on these routes comes back as 400, not 422.

Events

Integration writes don't fire outbound webhook events — the credential store is private. The OTP email is the only user-visible side effect.

Next

  • Tools — custom tools that resolve credentials from here.
  • Skills — can require an integration as a prerequisite.
  • Memory — the OTP gate pattern this resource also uses.