Tools
A tool is a discrete capability an agent can invoke during a run: "search memory", "send a Discord message", "call a custom HTTP endpoint", "run a sandboxed script". The Catentio runtime exposes a registry of built-in tools; the /v1/tools API lets you list them, inspect what each one does, and (for custom tools) create / update / delete your own.
The tool object
List row:
{
"slug": "memory_search",
"description": "Semantic search across the workspace's RAG store.",
"module": "app.tools.memory.search",
"category": "memory",
"sensitive": false,
"param_names": ["query", "limit"],
"param_count": 2,
"is_builtin": true,
"used_by": 6
}
A custom (DB-backed) row additionally carries kind, shape, access, and (list-only) owner_customer_id/version:
{
"slug": "my_internal_search",
"description": "Hit our internal docs search endpoint.",
"module": "(db)",
"category": "custom",
"sensitive": false,
"param_names": ["q"],
"param_count": 1,
"is_builtin": false,
"kind": "webhook",
"shape": "api",
"access": "both",
"owner_customer_id": "internal",
"version": "1",
"used_by": 0
}
| Field | Type | Notes |
|---|---|---|
slug |
string | Unique tool handle. |
description |
string | What the tool does. There is no separate display name field — slug + description is the whole identity. |
module |
string | Python module path for built-ins; "(db)" for DB-backed custom tools. Redacted to "" in public-visibility mode for built-ins. |
category |
string | Derived from the module path for built-ins (e.g. app.tools.workspace.docx → workspace); always "custom" for DB-backed tools. |
sensitive |
bool | The real name for what other resources call a "dangerous" flag — true for a static denylist of slugs plus any tool that self-reports sensitive=True, and true-by-default for custom tools of kind script/command/container/miniapp. |
param_names / param_count |
array / int | Top-level property names from the tool's JSON Schema. |
is_builtin |
bool | false for anything created through POST /v1/tools. |
kind |
enum | Custom tools only. One of stub (metadata-only placeholder), webhook (runtime POSTs params to body.url), script (runs body.script in a sandboxed subprocess), command (installed CLI binary), container (sandboxed container image), miniapp (operator-authored service). Not http/function — those names aren't part of the shipped model. |
shape |
enum | Custom tools only. The operator-facing creation flow that produced the tool: cli, api, or miniapp. |
access |
enum | Custom tools only. Who may invoke it: agent (MCP surface only), human (portal run panel only, hidden from agents), both (default). |
used_by |
int | How many skills declare this tool via a uses_tool graph edge. |
GET /v1/tools/{slug} detail adds id (the tool's graph-memory node id, for /v1/memory/entries/{id}/links), params_schema (the full JSON Schema — not a field named schema on the public shape), and recent_invocations (up to 50 recent tool_call events: {id, ts, run_id, agent, transport, params_preview}). For a custom tool, detail also returns the edit-form fields body (the kind-specific config — {url, method?, headers?} for webhook, {script, timeout_s?} for script), integration, schema (same content as params_schema, under the name the edit form expects), and version.
There is no
name,bound_agents,handler,is_dangerous,created_at, orupdated_atfield, and no per-agent "tool budget." Older references to those are stale. The real dangerous-flag name issensitive; there is nohandler.kindofhttp/function, no$VARIABLEheader templating (custom-tool headers use{param}-style Pythonstr.format_mapsubstitution, not a dollar-sign syntax); and tools aren't capped per-agent — the only cap is the workspace-widecustom_toolstier limit (see Errors below).
Endpoints
List tools
GET /v1/tools
Returns the full tool registry, redacted. Powers Dashboard → Tools. Meta: {"total", "builtin", "custom"}.
const { data } = await catentio.tools.list();
tools = catentio.tools.list()
curl 'https://catent.io/api/v1/cp/v1/tools' \
-H "Cookie: catentio_session=<session cookie set at login>"
Retrieve a tool
GET /v1/tools/{slug}
const tool = await catentio.tools.get('memory_search');
curl 'https://catent.io/api/v1/cp/v1/tools/memory_search' \
-H "Cookie: catentio_session=<session cookie set at login>"
Errors: 404 if the slug doesn't exist as either a registered tool or a DB row.
Create a custom tool
POST /v1/tools
Mint a custom tool. Body:
{
"slug": "my_internal_search",
"description": "Hit our internal docs search endpoint.",
"kind": "webhook",
"schema": { "type": "object", "properties": { "q": {"type":"string"} }, "required": ["q"] },
"body": { "url": "https://internal.example.com/search", "method": "POST" }
}
A tier cap on custom_tools applies. Requires a scheduler/runtime restart before the tool becomes dispatchable to agents.
Update a tool
PATCH /v1/tools/{slug}
Partial update on custom tools. Built-in tools refuse with 400 — their schemas are owned by the runtime release.
Delete a tool
DELETE /v1/tools/{slug}
Hard-deletes a custom tool. Built-in tools refuse with 400.
Errors
| Status | When |
|---|---|
| 400 | Slug missing/too long; malformed schema (not a JSON object); attempted edit/delete of a builtin tool. |
| 403 | plan_limit_exceeded — the workspace hit its custom_tools tier cap on create. |
| 404 | Tool slug doesn't exist. |
| 409 | Slug collision on create. |
| 502 | CP couldn't reach the runtime. |
There is no 422 invalid_schema, 409 builtin_tool, or 422 tool_budget_exceeded on this resource — the real codes are the ones above.
Events
Tool CRUD isn't currently emitted as outbox events — reserved for a later release. Per-invocation activity shows up as tool_call events on the parent run (see Runs); there is no tool_call_pending_approval event or in-line approval-modal workflow for sensitive tools today — treat sensitive as a UX/listing signal, not an enforcement gate.
Next
- Agents — what binds a tool's capability to a persona.
- Skills — playbooks; a higher-level abstraction than a single tool.
- Integrations — where a custom tool's headers can resolve a credential from.