Tool Execute API
All endpoints verified working via curl. Document updated only after curl confirmation.
Accepts either a session cookie (connect.sid) or a Bearer API key. Requires
owner, admin, or editor role.
Only a version whose executor.type is "http" can be executed server-side —
a "client" executor means the customer's own app runs the tool, and this
endpoint returns 422 for it. The target version is resolved as: explicit
versionNumber in the body → named alias in the body → the production
alias by default.
Arguments are validated against the version's parametersSchema before anything
is sent. A wrong type, a value outside an enum, a number outside
minimum/maximum, a string outside minLength/maxLength, and an undeclared
property when the schema sets additionalProperties: false each stop the call and
come back as 200 with an error naming the first thing that did not match —
a recorded tool failure, not an exception, so a model can read the reason and try
again instead of ending the run. A missing required key is the one case that
returns 400. Keywords outside that list (pattern, anyOf, $ref, …) are
ignored rather than rejected, so an unsupported keyword never turns into a false
rejection.
Arguments reach the outgoing request in two ways. {{arg.NAME}} inside a query
value, a headers value or the url is replaced with the argument NAME at call
time. A requestTransform shapes the request body from the whole arguments object.
A value carrying no placeholder stays fixed.
A placeholder in the url is percent-encoded when it is filled in, so the
value stays inside the one path segment or query value you wrote it into. A tool
whose argument legitimately spans two path segments needs one {{arg.NAME}} per
segment. Header and query values are not encoded here — query values are encoded
by the URL builder, and a header value is not a URL.
On top of that encoding, a value substituted into the url may not contain
.. or be a lone ., because a relative path reference changes which resource is
addressed no matter how it is encoded. Such a call is refused before any request
goes out, with the same 200-and-error shape as a schema violation. Ordinary
dotted values are unaffected: report.pdf, 1.2.3 and [email protected] all
reach the upstream as written.
The outgoing HTTP call goes through an SSRF guard (safeFetch): only public
http(s) URLs are reachable, so testing this endpoint against a real target
requires a real public URL (this doc uses https://httpbin.org) — pointing it
at localhost/127.0.0.1 is rejected by design, even in dev. A response body over
1 MB fails the call rather than being truncated — a half-read body is not a
result worth handing to a model. The count is bytes on the wire, so a non-ASCII
body is measured the same way as an ASCII one.
POST /api/v1/tools/:id/execute
Executes the version behind the default ("production") alias.
- curl
- TypeScript
- Python
curl -X POST $ACRUXCORE_BASE_URL/tools/8662ca24-69f2-4431-a7f4-82e9abc77ff1/execute \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"arguments":{"city":"Berlin"}}'
const result = await hub.tools.execute('8662ca24-69f2-4431-a7f4-82e9abc77ff1', {
city: 'Berlin',
});
result = await hub.tools.execute(
'8662ca24-69f2-4431-a7f4-82e9abc77ff1',
{'city': 'Berlin'},
)
Response (status 200):
{
"result": {
"args": {"city": "placeholder"},
"headers": {"Content-Type": "application/json", "Host": "httpbin.org", "X-Amzn-Trace-Id": "Root=1-6a5316bb-2818b2c824233ba1133c94a1"},
"origin": "39.40.215.16",
"url": "https://httpbin.org/get?city=placeholder"
},
"status": 200,
"latencyMs": 874,
"toolVersionId": "b40e86ed-9dfd-44c4-bf86-038e4964be22"
}
Pin to an explicit version number instead of the default alias.
curl -X POST $ACRUXCORE_BASE_URL/tools/8662ca24-69f2-4431-a7f4-82e9abc77ff1/execute \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"arguments":{"city":"Berlin"},"versionNumber":1}'
Response (status 200) — the version carries a failureWhen predicate and the
predicate declared a failure. The HTTP status is still 200 and nothing threw:
error is a field to read, not an exception to catch. The span this call wrote is
red and carries httpStatus: 200 at the same time. error.type is the
classification, one of six fixed values; the slug the predicate returned is on the
span as errorCode. See
versions.mdx
for the predicate that produced this.
{
"result": {"generationtime_ms": 0.22029877},
"status": 200,
"latencyMs": 521,
"toolVersionId": "2bbeb8d3-9162-4e57-90f2-6d55f35e3faa",
"error": {
"type": "tool_declared",
"message": "Open-Meteo has no coordinates for that city."
}
}
The same tool called with a city that exists returns no error key at all — the
field is absent on a healthy call, not null:
{
"result": {
"results": [
{
"id": 1283240,
"name": "Kathmandu",
"latitude": 27.70169,
"longitude": 85.3206,
"elevation": 1317,
"feature_code": "PPLC",
"country_code": "NP",
"admin1_id": 12095449,
"admin2_id": 12095484,
"timezone": "Asia/Kathmandu",
"population": 1442271,
"country_id": 1282988,
"country": "Nepal",
"admin1": "Bagmati Province",
"admin2": "Kathmandu"
}
],
"generationtime_ms": 0.24616718
},
"status": 200,
"latencyMs": 638,
"toolVersionId": "2bbeb8d3-9162-4e57-90f2-6d55f35e3faa"
}
Response (status 400) — missing required argument (per parametersSchema.required):
{"error":{"code":"VALIDATION_ERROR","message":"Missing required argument: city"}}
Arguments that do not match the schema
Every response below came from one tool whose parametersSchema is:
{
"type": "object",
"additionalProperties": false,
"properties": {
"q": {"type": "string", "maxLength": 20},
"limit": {"type": "integer", "minimum": 1, "maximum": 10},
"sort": {"type": "string", "enum": ["asc", "desc"]}
},
"required": ["q"]
}
curl -X POST $ACRUXCORE_BASE_URL/tools/$TOOL_ID/execute \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"arguments":{"q":"ok","limit":"lots"}}'
Each request and the exact body it returned:
arguments | Status | error.message |
|---|---|---|
{"q":"ok","limit":3,"sort":"asc"} | 200 | — |
{"q":"ok","limit":"lots"} | 200 | arguments.limit should be integer, got string |
{"q":"ok","sort":"DROP TABLE"} | 200 | arguments.sort should be one of "asc", "desc" |
{"q":"ok","limit":100000} | 200 | arguments.limit should be <= 10, got 100000 |
{"q":"<100 characters>"} | 200 | arguments.q should be at most 20 characters, got 100 |
{"q":"ok","evil":"x"} | 200 | arguments does not allow the property 'evil' |
{} | 400 | Missing required argument: q |
A refused call returns a null result beside the error, and nothing reaches the
upstream:
{"result":null,"status":0,"latencyMs":0,"toolVersionId":"…","error":{"type":"transport","message":"arguments.limit should be <= 10, got 100000"}}
The one 400, for a missing required argument:
{"error":{"code":"VALIDATION_ERROR","message":"Missing required argument: q"}}
A schema that declares no constraints stays permissive: with
{"type":"object","properties":{}} and no additionalProperties, any argument
is accepted.
Response (status 404) — tool, version, or alias not found:
{"error":{"code":"NOT_FOUND","message":"Tool not found."}}
Response (status 422, code NOT_EXECUTABLE) — resolved version has a "client" executor (no server-side call to make):
{"error":{"code":"NOT_EXECUTABLE","message":"This tool has no server-side executor."}}