Tools API
All endpoints verified working via curl. Document updated only after curl confirmation.
All endpoints accept either a session cookie (connect.sid) or a Bearer API key.
Write operations (POST/PATCH/DELETE) require owner, admin, or editor role.
This page covers the Tool shell CRUD only. The rest of the Tool Catalog (Phase 4) domain lives alongside it:
| Page | What it covers |
|---|---|
versions.mdx | Commit immutable versions; description vs changelog vs source. |
aliases.mdx | The production and staging pointers, and promoting between versions. |
sync.mdx | One-call reconcile for tools defined in code — what @acrux.tool uses. |
resolve.mdx | Batch name → model-ready function definition, plus executorType. |
execute.mdx | Running an http executor server-side. |
analytics.mdx | Per-tool call counts, latency, and error rates. |
Reach for sync.mdx and resolve.mdx rather than this
page. Creating the shell here and then committing a version separately is the
dashboard's path; a decorated function does both in one call and stays idempotent
across deploys.
Readiness fields
Every tool in a list or single fetch carries five read-only fields saying whether it can
actually be called. POST /tools creates a shell — a name with no version — and a
shell resolves to nothing, so a prompt bound to one runs with that tool missing and says
so nowhere. These fields are how a caller tells the two states apart in one request,
without a versions fetch per tool.
| Field | Meaning |
|---|---|
callable | true once a version exists and production points at one. This is the resolvable state — the one an unqualified tool_ref needs. |
versionCount | Committed versions. 0 means the tool is a name only. |
latestVersionNumber | Highest committed version, or null. |
executorType | client or http — who runs the call — read from the version production serves, not the newest one. null when not callable. |
aliases | Every alias with the version it points at, production first. |
A tool that is not callable has one of two causes, and they need different fixes:
versionCount: 0 means commit a version (POST /tools/:id/versions), while a non-zero
count with no production entry in aliases means promote one
(POST /tools/:id/aliases/production/promote).
The HTTP response carries all five fields today — every JSON example on this page,
including the curl responses, is real. Both SDKs read them from 0.15.0 onward
(npm i @acruxcoreai/sdk@latest, pip install -U acruxcore).
On 0.14.0 and earlier the two SDKs fail differently. In TypeScript, hub.tools.get()
and hub.tools.list() return the raw response object, so the fields are there at runtime
and only the compiled type is missing them — tool.callable is a type error unless you
cast. In Python they are worse than untyped: ToolDetail.from_dict builds the dataclass
field by field, so the five fields are dropped and never reach the object at all. On an
older SDK, call the HTTP endpoint directly, as the curl examples below do.
POST /api/v1/tools
Creates a new tool shell (no versions/aliases yet — commit a version separately).
name must match ^[a-zA-Z0-9_-]{1,64}$ (kept LLM-function-name-safe for
OpenAI/Anthropic/Gemini tool-calling compatibility).
The name must also be free within the team: POST /tools/sync and every
tool_ref find a tool by name, so two active tools sharing one would make
resolution arbitrary. Soft-deleting a tool releases its name for reuse, and two
different teams may each hold the same name.
- curl
- TypeScript
- Python
curl -X POST $ACRUXCORE_BASE_URL/tools \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"docs_get_weather","description":"Fetch current weather for a city."}'
const tool = await hub.tools.create({
name: 'docs_get_weather',
description: 'Fetch current weather for a city.',
});
tool = await hub.tools.create(
'docs_get_weather',
description='Fetch current weather for a city.',
)
Response (status 201):
{
"id": "d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58",
"name": "docs_get_weather",
"description": "Fetch current weather for a city.",
"teamId": "8d3ceb9b-39d0-463b-8b91-aa2ef20ac9ba",
"createdBy": "18c76b52-ee0d-4001-be8e-c29976488fbb",
"createdAt": "2026-09-13T21:50:39.977Z",
"callable": false,
"versionCount": 0,
"latestVersionNumber": null,
"executorType": null,
"aliases": []
}
The tool is not callable yet — POST /tools writes the shell only. Commit its first
version with POST /tools/:id/versions, which also creates the
production and staging aliases.
Response (status 400) — name fails the safe-name pattern:
{"error":{"code":"VALIDATION_ERROR","message":"name must match ^[a-zA-Z0-9_-]{1,64}$"}}
Response (status 409) — an active tool in this team already holds the name.
PATCH /tools/:id returns the same error when a rename would collide:
{"error":{"code":"TOOL_NAME_TAKEN","message":"A tool named 'docs_get_weather' already exists in this team."}}
GET /api/v1/tools
Returns a paginated list of active (non-deleted) tools for the current team.
Supports ?search=, ?page=, ?limit=.
- curl
- TypeScript
- Python
curl $ACRUXCORE_BASE_URL/tools \
-H "Authorization: Bearer $ACRUXCORE_API_KEY"
const result = await hub.tools.list();
// with search
const filtered = await hub.tools.list({ search: 'weather' });
// readiness fields (callable, versionCount, ...) reach this SDK's types in
// its next release — see "Readiness fields" above
result = await hub.tools.list()
# with search
filtered = await hub.tools.list(search='weather')
# readiness fields (callable, version_count, ...) reach this SDK in its next
# release — see "Readiness fields" above; today they are dropped
Response (status 200):
{
"data": [
{
"id": "d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58",
"name": "docs_get_weather",
"description": "Fetch current weather for a city.",
"teamId": "8d3ceb9b-39d0-463b-8b91-aa2ef20ac9ba",
"createdBy": "18c76b52-ee0d-4001-be8e-c29976488fbb",
"createdAt": "2026-09-13T21:50:39.977Z",
"callable": false,
"versionCount": 0,
"latestVersionNumber": null,
"executorType": null,
"aliases": []
}
],
"total": 1,
"page": 1,
"limit": 20
}
GET /api/v1/tools/:id
Fetches a single active tool by ID.
- curl
- TypeScript
- Python
curl $ACRUXCORE_BASE_URL/tools/d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58 \
-H "Authorization: Bearer $ACRUXCORE_API_KEY"
const tool = await hub.tools.get('d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58');
// readiness fields (callable, versionCount, ...) reach this SDK's types in
// its next release — see "Readiness fields" above
tool = await hub.tools.get('d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58')
# readiness fields (callable, version_count, ...) reach this SDK in its next
# release — see "Readiness fields" above; today they are dropped
Response (status 200):
{
"id": "d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58",
"name": "docs_get_weather",
"description": "Fetch current weather for a city.",
"teamId": "8d3ceb9b-39d0-463b-8b91-aa2ef20ac9ba",
"createdBy": "18c76b52-ee0d-4001-be8e-c29976488fbb",
"createdAt": "2026-09-13T21:50:39.977Z",
"callable": true,
"versionCount": 1,
"latestVersionNumber": 1,
"executorType": "http",
"aliases": [
{ "alias": "production", "versionNumber": 1 },
{ "alias": "staging", "versionNumber": 1 }
]
}
This is the same tool after one http version was committed: callable is now true,
and both aliases point at v1.
Response (status 404) — tool not found, deleted, or belongs to another team:
{"error":{"code":"NOT_FOUND","message":"Tool not found."}}
PATCH /api/v1/tools/:id
Partially updates a tool's name and/or description. At least one field is required.
- curl
- TypeScript
- Python
curl -X PATCH $ACRUXCORE_BASE_URL/tools/d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58 \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"description":"Fetch current weather for a city (v2)."}'
const updated = await hub.tools.update('d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58', {
description: 'Fetch current weather for a city (v2).',
});
updated = await hub.tools.update(
'd41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58',
description='Fetch current weather for a city (v2).',
)
Response (status 200):
{
"id": "d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58",
"name": "docs_get_weather",
"description": "Fetch current weather for a city (v2).",
"teamId": "8d3ceb9b-39d0-463b-8b91-aa2ef20ac9ba",
"createdBy": "18c76b52-ee0d-4001-be8e-c29976488fbb",
"createdAt": "2026-09-13T21:50:39.977Z",
"callable": true,
"versionCount": 1,
"latestVersionNumber": 1,
"executorType": "http",
"aliases": [
{ "alias": "production", "versionNumber": 1 },
{ "alias": "staging", "versionNumber": 1 }
]
}
Response (status 400) — neither name nor description provided:
{"error":{"code":"VALIDATION_ERROR","message":"At least one of name or description must be provided."}}
DELETE /api/v1/tools/:id
Soft-deletes a tool (deleted_at = now()). Version and alias rows are preserved
but the tool no longer appears in list/get, and a subsequent GET returns 404.
- curl
- TypeScript
- Python
curl -X DELETE $ACRUXCORE_BASE_URL/tools/d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58 \
-H "Authorization: Bearer $ACRUXCORE_API_KEY"
await hub.tools.delete('d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58');
await hub.tools.delete('d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58')
Response (status 204) — no body.
Subsequent GET returns 404:
curl $ACRUXCORE_BASE_URL/tools/d41a0bd2-0a55-4e73-a1e2-f9d7d8d84e58 \
-H "Authorization: Bearer $ACRUXCORE_API_KEY"
Response (status 404):
{"error":{"code":"NOT_FOUND","message":"Tool not found."}}