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_byfield. Those names describe an older shape. The real contract is theis_configured/status/credentials_*rollup above, plus the per-slotcredentials[]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, andcloudflareare not in the catalog. If you've seen those slugs referenced as integrations, that's stale — they don't exist as_INTEGRATION_CATALOGentries 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_idthat 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.