SDKs

Catentio ships SDKs in three languages — Node, Python, Go — with 1:1 parity: same 24 resource namespaces, same method names modulo language casing, same thin HTTP-client plumbing underneath.

Read this before anything else. These SDKs were generated against the internal control-plane protocol, and most of their methods target routes the public API does not expose. They predate the public API and have not been regenerated against it yet.

The public API is live and small — nine endpoints at https://catent.io/v1/, authenticated with Authorization: Bearer cat_pk_.... Until the SDKs are regenerated, call it directly; it is a plain JSON REST API and needs no client library:

# list what you can invoke
curl https://catent.io/v1/agents -H "Authorization: Bearer cat_pk_..."

# dispatch a run
curl -X POST https://catent.io/v1/runs \
  -H "Authorization: Bearer cat_pk_..." \
  -H "Content-Type: application/json" \
  -d '{"agent": "fumi", "message": {"content": "..."}}'

# poll it
curl https://catent.io/v1/runs/<run_id> -H "Authorization: Bearer cat_pk_..."

See the API reference for the full nine. Treat the SDK code samples below as documentation of the control-plane call shape, not as something that works against the public API today.

Language Package Install
Node.js @forjio/catentio-saas-node npm install @forjio/catentio-saas-node
Python catentio-saas (PyPI) pip install catentio-saas
Go github.com/hachimi-cat/catentio-saas-go go get github.com/hachimi-cat/catentio-saas-go

The 24 namespaces

Namespace Surface
workspaces list, show, create, destroy, setState (admin-only — see below)
agents list, get (broken), invoke (broken)
runs list, get (broken), cancel
projects list, get, create, plus per-project: listAttachments, addAttachment, listTasks, getTask, listSubtasks, getSubtask, listAttempts, listArtifacts, listEvents, cost, pause, resume, abandon, runStep, retrySubtask
templates list
tools list, get, create, update, delete
skills list, get
memory stats, listEntries, getEntry, createEntry, updateEntry (OTP-gated)
apiKeys list, create, delete
billing catalog, summary, subscribe
cost summary
integrations list, configure (broken), delete (broken)
webhooks list (create / delete are present but not implemented server-side)
scheduledJobs list, create, update, delete
featureFlags list, set (broken)
files list (broken), upload (broken), delete (broken)
outputDestinations list, get, create, update, delete
discord status (broken), sendMessage (broken)
gojo sessions
heartbeats list, config (broken), updateConfig (broken)
system info, health (unauth)
chat send, history
events list
inboundWebhooks list (broken)

"Broken" means the method's underlying route doesn't exist on the control plane (or the control plane's route proxies to a runtime route that doesn't exist), so the call fails end-to-end. This is called out plainly on each resource's page — see Agents, Runs, Integrations, and Webhooks for the ones covered in this section.

Notably:

  • templates exposes list only — template detail, versions, create, new-version PATCH, and archive are REST-only for now.
  • projects covers the read surface plus lifecycle (pause / resume / abandon / runStep / retrySubtask). Gate resolution (resolve-gate) and the backtrack surface (phase retrigger, artifact revert, edit sessions) are REST-only.
  • runs has no way to start a run — the real way is POST /v1/runs, which no SDK method targets. agents.invoke looks like the way to do it but targets a runtime route that doesn't exist. See Runs and Agents.
  • webhooks lists webhook gates (inbound: pipeline steps blocked until an external system calls back). Only list works — create and delete target routes the control plane never implemented. Outbound webhooks are a separate feature on the public API and are not wrapped by this SDK — call them directly: Webhooks (outbound).
  • featureFlags.set sends PUT /v1/feature-flags/{flag}; the real route is PATCH /v1/feature-flags/{key} — wrong HTTP verb, so it 405s rather than applying the change.
  • apiKeys real prefix is cat_pk_..., not cat_ak_... — and this whole namespace requires x-catentio-session, so neither credential mode this SDK offers authenticates against it either.

Where a page in this section says "via the REST API", the SDK's low-level HTTP client still works — you just call the path yourself (catentio.api.get/post/patch/put/delete) instead of a named method.

Auth: what actually works

There are two credential shapes, and neither is honoured everywhere:

  • A Huudis-issued JWT, sent as Authorization: Bearer (the SDK's session/Session option). The control plane's RequireSessionOrAgent dependency accepts this — it's used on billing, chat, and most of workspaces (list/show/create/destroy). It is not accepted on agents, runs, projects, tools, skills, memory, apiKeys, integrations, webhooks, scheduledJobs, events, or any other namespace whose routes use the plain RequireSession dependency — those require the x-catentio-session header (a signed cookie payload), which no SDK sends.
  • A static API key (cat_pk_... — not cat_ak_...; that prefix doesn't exist anywhere in the codebase), sent as Authorization: Bearer cat_pk_... via the SDK's apiKey/api_key/APIKey option. The control plane never checks API keys at all — they're verified only by the runtime's own guard, for the runtime's own routes, which is a different HTTP surface than the one these SDKs talk to. Passing an API key to any of these SDK clients will not authenticate against the control plane, full stop.
  • PATCH /v1/workspaces/{id}/state (the workspaces.setState method) is RequireAdmin-gated — it needs an X-Catentio-Admin HMAC or the legacy x-operator-token shared secret. Neither a session nor a Bearer JWT satisfies it.

There are no scopes to speak of: the control plane's API-key model accepts a scopes field on creation and silently discards it (v1 always grants full access to the customer's own data). Any older reference to catentio:*:write-style scopes is fiction.

Quick comparison

Same call in three languages — list runs for one agent (a route that exists, GET /v1/runs, subject to the auth caveat above):

Node.js:

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

const catentio = new CatentioSaasClient({ session });

const { data, meta } = await catentio.runs.list({ agent: 'hachimi', limit: 20 });
console.log(`${data.length} of ${meta.total}`);

Python:

from catentio_saas import CatentioClient

with CatentioClient(session=session) as catentio:
    resp = catentio.runs.list({"agent": "hachimi", "limit": 20})
    print(f"{len(resp['data'])} of {resp['meta']['total']}")

Go:

import catentio "github.com/hachimi-cat/catentio-saas-go"

client := catentio.NewClient(catentio.ClientOptions{Session: session})

raw, _ := client.Runs.List(ctx, catentio.Query{"agent": "hachimi", "limit": 20}, "")

The differences are purely idiomatic: camelCase in Node, snake_case in Python, PascalCase in Go. The underlying call is identical — and note that none of the three unwrap the {data, meta} envelope for you; the wire response never carries an error key, so the SDKs' envelope-detection (which requires data + error + meta together) never fires, and you get the raw object back.

For the (non-functional) "invoke an agent" call, see Agents → invoke and Runs for what starting a run actually requires.

Response shape, pagination, and what doesn't exist

  • Lists return {"data": [...], "meta": {...}} (meta's contents vary by route — e.g. runs has {total, limit, offset}, agents has {total, builtin, custom}). One exception: workspaces.list returns {"rows": [...]}.
  • Detail/single-object routes return the bare object.
  • Errors are FastAPI's plain {"detail": ...} — there is no {code, message} pair inside an error key. Validation errors are 422.
  • Pagination is limit/offset only. There is no cursor, no nextCursor, no hasMore. The Python SDK's ApiClient.paginate() helper reads a cursor out of the response to advance — since the control plane never returns one, it always stops after the first page. Don't rely on it.
  • There is no Idempotency-Key support anywhere in the request path.
  • There are no HTTP rate limits (X-RateLimit-*, 429, Retry-After) — the control plane doesn't limiter-gate requests per second. What does exist is a plan-tier cap enforced by the runtime on things like daily run count and resource counts, surfaced as 403 with {"detail": {"code": "plan_limit_exceeded", "resource", "current", "limit", "tier", "message"}}.
  • There is no WebSocket and no SSE anywhere in Catentio. No method in any of these SDKs opens one, and no response field that looks like a stream URL (websocket_url, etc.) is something you can actually connect to.

Versioning

All SDKs follow semantic versioning:

  • MAJOR bumps for breaking changes (rare; batched).
  • MINOR bumps for new features (most releases).
  • PATCH bumps for fixes.

Current versions are 0.x across the three. Pre-1.0 means small breaking changes can ship between minor versions; every break is documented in the changelog. Once we hit 1.0, the contract becomes strict.

Source

The SDKs live in the same repo as Catentio's portal + control plane:

Note: hachimi-cat/catentio-saas-node is not publicly reachable as of this writing — those three links 404 unless you have access to the repo (same caveat as Installation). The paths are accurate for anyone on the team; there is no public mirror to browse today.

Other languages

If your stack is Ruby, PHP, Rust, Elixir, or anything else, the raw REST API has the same auth and reachability constraints described above — there's no additional capability to gain by writing your own client against catent.io today.

We don't currently plan to publish additional language SDKs — the Forjio family has settled on Node / Python / Go as the canonical trio.

Next