Skip to main content

Tool Resolve API

All endpoints verified working via curl. Document updated only after curl confirmation.

POST /tools/resolve turns a list of tool names into the function blocks you put in an LLM request. It is the read counterpart to sync.mdx: the SDKs call sync on start-up to register what the code defines, then call resolve to get back what the model should actually see.

Names, not ids — the caller is a decorated function in a source file and has no id to hand. It is a POST because the payload is a list of objects, not because it writes anything; resolve requires only an authenticated role, not editor.

One call resolves up to 50 refs. Batching matters: an agent with a dozen tools would otherwise make a dozen round-trips before it can send its first prompt.


POST /api/v1/tools/resolve

curl -X POST $ACRUXCORE_BASE_URL/tools/resolve \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"refs":[{"name":"get_weather","alias":"production"}]}'

Response (status 200):

{
"data": [
{
"toolId": "eb1df7c0-778c-4b89-8536-8c08a1f6d406",
"versionNumber": 2,
"executorType": "client",
"function": {
"name": "get_weather",
"description": "Get the current weather, in Celsius, for a city.",
"parameters": {"type":"object","required":["city"],"properties":{"city":{"type":"string","description":"City name, e.g. Lahore."}}}
}
}
]
}

alias defaults to production, so it can be left out. Results come back in the order the refs were sent, and a batch may mix executor types freely:

curl -X POST $ACRUXCORE_BASE_URL/tools/resolve \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"refs":[{"name":"get_weather"},{"name":"lookup_order"}]}'

Response (status 200):

{
"data": [
{
"toolId": "eb1df7c0-778c-4b89-8536-8c08a1f6d406",
"versionNumber": 2,
"executorType": "client",
"function": {
"name": "get_weather",
"description": "Get the current weather, in Celsius, for a city.",
"parameters": {"type":"object","required":["city"],"properties":{"city":{"type":"string","description":"City name, e.g. Lahore."}}}
}
},
{
"toolId": "801b77d2-29c9-4050-9488-31a311eb7e68",
"versionNumber": 1,
"executorType": "http",
"function": {
"name": "lookup_order",
"description": "Look up an order by id.",
"parameters": {"type":"object","required":["order_id"],"properties":{"order_id":{"type":"string"}}}
}
}
]
}

Request fields

FieldRequiredNotes
refsyes1–50 refs.
refs[].nameyesTool name, resolved within your team.
refs[].aliasnoAlias to follow. Defaults to production. Not with version.
refs[].versionnoExact version to pin, instead of following an alias. Not with alias.

Response fields

FieldNotes
toolIdUseful for linking to the tool in the dashboard.
versionNumberThe immutable version the alias resolved to. Log it and a trace tells you exactly which spec the model saw.
executorTypeclient or http. Tells the caller who runs the tool.
functionReady to drop into an OpenAI-style tools[].function.

Pinning one exact version

A ref normally follows an alias, so it resolves to whatever that alias points at right now. Send version instead to name one exact build — the alias is never consulted, which is the whole point of a pin: a prompt pinned to v1 keeps getting v1 after production moves on to v3.

curl -X POST $ACRUXCORE_BASE_URL/tools/resolve \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"refs":[{"name":"get_weather","version":1}]}'

Response (status 200) — v1, even though production points at v3:

{
"data": [
{
"toolId": "0d84d6c3-dc86-4db2-aee3-776bdd35ea34",
"versionNumber": 1,
"executorType": "client",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {"type":"object","required":["city"],"properties":{"city":{"type":"string","description":"City name, e.g. Lahore."}}}
}
}
]
}

alias and version on the same ref is a 400 rather than a precedence rule: a caller sending both has two different builds in mind, and picking one would silently run the wrong tool.

curl -X POST $ACRUXCORE_BASE_URL/tools/resolve \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"refs":[{"name":"get_weather","alias":"production","version":1}]}'

Response (status 400):

{"error":{"code":"VALIDATION_ERROR","message":"a ref takes either alias or version, not both."}}

A pinned ref that does not resolve is reported by its version, not by a phantom alias it never asked for:

{
"error": {
"code": "TOOL_REF_NOT_FOUND",
"message": "Could not resolve 1 tool ref(s): 'get_weather' v99",
"refs": [{"name":"get_weather","version":99}]
}
}

Why executorType is here and the executor is not

executorType is the one thing a tool loop needs in order to route a tool call:

  • client — your own code runs it. The loop looks the function up in its local registry and calls it.
  • http — the platform runs it. The loop posts the arguments to execute.mdx and never touches the target service itself.

The executor definition is deliberately absent from the response. A tool's http executor holds its target url, its headers, and {{secret.NAME}} references that resolve to real credentials. Those stay server-side. Returning them so a client could call the service directly would make every tool's credentials readable by anything holding an API key, and would defeat the point of running http executors on the platform at all.

Resolving the http tool above returns its schema and nothing else — no url, no header names, no secret name, no executor key:

{"toolId":"801b77d2-29c9-4050-9488-31a311eb7e68","versionNumber":1,"executorType":"http","function":{"name":"lookup_order","description":"Look up an order by id.","parameters":{"type":"object","required":["order_id"],"properties":{"order_id":{"type":"string"}}}}}

Every failure in one 404

A partial success would be worse than useless here: a tool loop that silently starts without one of its tools produces a model that cannot do what it was asked and no error explaining why. So resolve is all-or-nothing, and when refs fail it reports all of them at once rather than stopping at the first — one round-trip tells you everything that is wrong.

curl -X POST $ACRUXCORE_BASE_URL/tools/resolve \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"refs":[{"name":"ghost_one"},{"name":"get_weather"},{"name":"shell_no_version"},
{"name":"get_weather","alias":"canary"},{"name":"get_weather","version":99}]}'

Response (status 404) — note get_weather@production resolved fine and is not listed:

{
"error": {
"code": "TOOL_REF_NOT_FOUND",
"message": "Could not resolve 4 tool ref(s): 'ghost_one'@production — no tool named 'ghost_one' in this team; 'shell_no_version'@production — tool 'shell_no_version' has no versions, so alias 'production' does not exist yet — commit one with POST /tools/:id/versions; 'get_weather'@canary — tool 'get_weather' has no alias 'canary'; it has 'production', 'staging'; 'get_weather' v99 — tool 'get_weather' has no version 99; the latest is 1",
"refs": [
{"name":"ghost_one","alias":"production"},
{"name":"shell_no_version","alias":"production"},
{"name":"get_weather","alias":"canary"},
{"name":"get_weather","version":99}
],
"failures": [
{
"name":"ghost_one","alias":"production",
"reason":"no_such_tool",
"message":"no tool named 'ghost_one' in this team"
},
{
"name":"shell_no_version","alias":"production",
"reason":"no_versions",
"message":"tool 'shell_no_version' has no versions, so alias 'production' does not exist yet — commit one with POST /tools/:id/versions"
},
{
"name":"get_weather","alias":"canary",
"reason":"unknown_alias",
"message":"tool 'get_weather' has no alias 'canary'; it has 'production', 'staging'",
"availableAliases":["production","staging"]
},
{
"name":"get_weather","version":99,
"reason":"unknown_version",
"message":"tool 'get_weather' has no version 99; the latest is 1",
"latestVersion":1
}
]
}
}

Which of the four things went wrong

refs carries each failure as it was effectively looked up — a pin by its version, an alias-follower with the default production filled in — so an SDK can name them without parsing the message.

failures is that same list with the reason attached. There are four, and they are four different mistakes with four different fixes:

reasonWhat it meansWhat to do
no_such_toolNothing in your team has that name, including a soft-deleted toolFix the name, or create the tool
no_versionsThe tool exists but nothing has been committed to it, so it has no aliases at allPOST /tools/:id/versions
unknown_aliasVersions exist; that alias was never promoted. availableAliases lists the ones that wereUse one of those, or promote
unknown_versionA pinned version number that was never committed. latestVersion is the highest that wasPin an existing version

no_versions is worth knowing about specifically, because it is the most likely failure on a tool you just made. Creating a tool is two calls: POST /tools makes the shell, and POST /tools/:id/versions commits the version that makes it callable. Aliases are minted by that first version, so a shell has none — and "no alias production" on a tool that plainly exists reads as "no such tool" unless the error says otherwise.

Response (status 400) — an empty refs array:

curl -X POST $ACRUXCORE_BASE_URL/tools/resolve \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"refs":[]}'
{"error":{"code":"VALIDATION_ERROR","message":"refs must contain at least one ref."}}