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 withAuthorization: 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:
templatesexposeslistonly — template detail, versions, create, new-version PATCH, and archive are REST-only for now.projectscovers 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.runshas no way to start a run — the real way isPOST /v1/runs, which no SDK method targets.agents.invokelooks like the way to do it but targets a runtime route that doesn't exist. See Runs and Agents.webhookslists webhook gates (inbound: pipeline steps blocked until an external system calls back). Onlylistworks —createanddeletetarget 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.setsendsPUT /v1/feature-flags/{flag}; the real route isPATCH /v1/feature-flags/{key}— wrong HTTP verb, so it 405s rather than applying the change.apiKeysreal prefix iscat_pk_..., notcat_ak_...— and this whole namespace requiresx-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'ssession/Sessionoption). The control plane'sRequireSessionOrAgentdependency accepts this — it's used onbilling,chat, and most ofworkspaces(list/show/create/destroy). It is not accepted onagents,runs,projects,tools,skills,memory,apiKeys,integrations,webhooks,scheduledJobs,events, or any other namespace whose routes use the plainRequireSessiondependency — those require thex-catentio-sessionheader (a signed cookie payload), which no SDK sends. - A static API key (
cat_pk_...— notcat_ak_...; that prefix doesn't exist anywhere in the codebase), sent asAuthorization: Bearer cat_pk_...via the SDK'sapiKey/api_key/APIKeyoption. 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(theworkspaces.setStatemethod) isRequireAdmin-gated — it needs anX-Catentio-AdminHMAC or the legacyx-operator-tokenshared 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.runshas{total, limit, offset},agentshas{total, builtin, custom}). One exception:workspaces.listreturns{"rows": [...]}. - Detail/single-object routes return the bare object.
- Errors are FastAPI's plain
{"detail": ...}— there is no{code, message}pair inside anerrorkey. Validation errors are422. - Pagination is
limit/offsetonly. There is no cursor, nonextCursor, nohasMore. The Python SDK'sApiClient.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-Keysupport 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 as403with{"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:
- Node: catentio-saas-node
- Python: catentio-saas-python
- Go: catentio-saas-go
Note:
hachimi-cat/catentio-saas-nodeis 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
- Installation — install the SDK for your stack.
- API authentication — the session/Bearer/API-key model in full.
- Quickstart — end-to-end walkthrough.