Workspaces
A workspace here is a catentio deployment record keyed by the Huudis workspace id (huudis_workspace_id, e.g. acc_01HX...) — it tracks whether that Huudis workspace has a provisioned catentio instance (internal/managed/byo plan), and what state that provisioning is in. It is not a generic "tenant" object with a display name and an owner user id.
Catentio is multi-tenant — workspaces, per-tenant runners, billing, and customer onboarding are all shipped. There is no single-user gate on workspace creation; ignore any older reference to one.
For the wire-level reference, see API → Workspaces.
Auth.
list/show/create/destroyuseRequireSessionOrAgent, which honours a real Huudis-issued JWT inAuthorization: Bearer— one of the few namespaces where that's true.setStateis different:PATCH /v1/workspaces/{id}/stateis gated byRequireAdmin(anX-Catentio-AdminHMAC or the legacyx-operator-token), which no session or Bearer JWT can satisfy. This SDK'sapiKeymode (cat_pk_...) is not a Huudis JWT and isn't honoured on any of these routes. See SDKs → Auth: what actually works.
Namespace
catentio.workspaces — every method on this namespace:
catentio.workspaces.list()
catentio.workspaces.show(id)
catentio.workspaces.create(input)
catentio.workspaces.destroy(id)
catentio.workspaces.setState(id, { state, reason? })
There is no update for the mutable fields.
Methods
workspaces.list
Signature. catentio.workspaces.list(token?): Promise<{ rows: WorkspaceDeployment[] }>
The response shape is
{ rows: [...] }, not{ data, meta }and not a bare array —ListResponseis the one list endpoint in this SDK that doesn't follow the usualdata/metaconvention.
import { CatentioSaasClient } from '@forjio/catentio-saas-node';
const catentio = new CatentioSaasClient({ session });
const { rows } = await catentio.workspaces.list();
console.log(rows[0].huudis_workspace_id, rows[0].status);
workspaces.show
Signature. catentio.workspaces.show(id, token?): Promise<WorkspaceDeploymentDetail>
id is the huudis_workspace_id. 404 {"detail": "not_found"} if there's no deployment row for it.
const ws = await catentio.workspaces.show('acc_01HXAB7K3M9N2P5QRS8TVWXY3Z');
console.log(ws.status, ws.plan, ws.runtime_base_url);
workspaces.create
Signature. catentio.workspaces.create(input, token?): Promise<WorkspaceDeploymentDetail>
Real required fields: huudis_workspace_id (1–64 chars), plan ("managed" or "byo" — "internal" is rejected with 400 invalid_plan for customer calls). config is optional and plan-specific.
const ws = await catentio.workspaces.create({
huudis_workspace_id: 'acc_01HX...',
plan: 'managed',
});
There is no displayName or ownerHuudisUserId field — those don't exist. Real failure modes: 409 {"code": "already_exists", "status": ...} if a non-archived deployment already exists for that Huudis workspace; 402 {"code": "insufficient_credit", ...} if a managed plan needs a wallet top-up that hasn't happened; 400 {"code": "managed_anthropic_not_offered"} if config.manage_anthropic is set (Anthropic is BYO-only).
workspaces.destroy
Signature. catentio.workspaces.destroy(id, token?): Promise<WorkspaceDeploymentDetail>
Synchronously tears the deployment down — the CP awaits the full destroy sequence before responding, it does not return early and finish in the background. 400 {"detail": "cannot_destroy_internal"} on the internal/seed deployment. 502 {"code": "destroy_failed", ...} if teardown fails partway (partial progress is still committed).
await catentio.workspaces.destroy('acc_01HX...');
workspaces.setState — admin-only
Signature. catentio.workspaces.setState(id, input, token?): Promise<WorkspaceDeploymentDetail>
This route requires
RequireAdmin, not a session or Bearer JWT. A normal caller of this SDK cannot authenticate against it at all, regardless of credential type.The real body is also different from what the type suggests:
{ status: "pending" | "provisioning" | "ready" | "failed" | "archived", runtime_base_url?, error_code?, error_message? }. There is nostatefield (it'sstatus) and noreasonfield — those don't exist.
// Only works with an admin-level credential this SDK cannot produce:
await catentio.workspaces.setState('acc_01HX...', {
status: 'ready',
runtime_base_url: 'https://acme.catentio.internal',
});
Types
interface WorkspaceDeployment {
huudis_workspace_id: string; // primary key, e.g. 'acc_01HX...' — never 'ws_...'
customer_id: string;
plan: 'internal' | 'managed' | 'byo';
status: 'pending' | 'provisioning' | 'ready' | 'failed' | 'archived';
runtime_base_url: string | null;
error_code: string | null;
error_message: string | null;
created_at: string;
ready_at: string | null;
archived_at: string | null;
}
interface WorkspaceDeploymentDetail extends WorkspaceDeployment {
config: Record<string, unknown> | null; // present on show/create/destroy; secrets redacted
}
There is no id (it's huudis_workspace_id), displayName, state (it's status, and the enum values differ — no active/suspended), ownerHuudisUserId, runtimeDropletId (it's runtime_base_url, a URL not a droplet id), plugipayPartnerKey, or updatedAt field (there's no updated_at at all) — none of those exist.
Common patterns
Resolve "my" deployment at boot
async function resolveDeployment(huudisWorkspaceId: string) {
const ws = await catentio.workspaces.show(huudisWorkspaceId);
if (ws.status !== 'ready') {
throw new Error(`Deployment for ${huudisWorkspaceId} is ${ws.status}, not ready`);
}
return ws.runtime_base_url;
}
Errors
The real error body is FastAPI's {"detail": ...} or a nested {"detail": {"code": ..., ...}}. 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 | detail |
Cause |
|---|---|---|
| 404 | "not_found" |
show()/destroy() on an id with no deployment row. |
| 400 | "invalid_plan" |
create() with plan: "internal". |
| 400 | "cannot_destroy_internal" |
destroy() on the internal/seed deployment. |
| 402 | {"code": "insufficient_credit", ...} |
create() for a managed plan without enough wallet balance. |
| 409 | {"code": "already_exists", ...} |
create() for a Huudis workspace that already has a live deployment. |
| 502 | {"code": "destroy_failed", ...} |
destroy() failed partway. |
Next
- Agents — what runs inside a workspace's runtime.
- Billing — the Plugipay subscription tied to a workspace.
- API → Workspaces — HTTP-level reference.