Tools are functions the assistant can call mid-conversation. Register a tool
once with the URL of an HTTP endpoint you control, then reference it by ID
from /v1/threads/:id/messages. When the assistant emits a tool_use block,
qlaud POSTs to your webhook, awaits the result, appends a tool_result,
re-calls the assistant, and loops until a non-tool-use turn.
Kills the per-app tool-call state machine. You write one HTTP handler per
tool; qlaud owns the rest.
POST /v1/tools — Register
curl https://api.qlaud.ai/v1/tools \
-H "x-api-key: $QLAUD_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"]
},
"webhook_url": "https://my-app.example/qlaud/tools/weather",
"timeout_ms": 15000
}'
Body
| Field | Type | Required | Default | Description |
|---|
name | string | yes | — | The function name passed to the LLM. Per-account unique among non-revoked tools. |
description | string | yes | — | Forwarded to the LLM as the tool description. |
input_schema | JSON Schema | yes | — | Forwarded as input_schema. |
webhook_url | string | yes | — | Must be https://. qlaud POSTs the tool_use payload here. |
timeout_ms | number | no | 30000 | Per-tool override of the 30 s default. Max 120000. |
Response (201)
{
"id": "tool_f5d6afcef39743bcbb3114ce3b9c8e66",
"object": "tool",
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": { "type": "object", "...": "..." },
"webhook_url": "https://my-app.example/qlaud/tools/weather",
"timeout_ms": 15000,
"secret": "wsk_AbCdEf...XyZ",
"created_at": 1777262997717
}
secret is returned once. You use it to verify the HMAC-SHA256 signature
on every webhook delivery. Lose it and you’ll need to revoke + re-register
the tool to get a new one.
curl https://api.qlaud.ai/v1/tools -H "x-api-key: $QLAUD_API_KEY"
Returns every non-revoked tool you’ve registered. secret is not included.
curl -X DELETE https://api.qlaud.ai/v1/tools/$TOOL_ID \
-H "x-api-key: $QLAUD_API_KEY"
Soft revoke. New thread messages can’t reference the tool by id; existing
thread audits still resolve cleanly.
Webhook contract
When the assistant emits a tool_use block, qlaud POSTs the following
payload to your webhook_url:
X-Qlaud-Timestamp: 1777262997717
X-Qlaud-Signature: <hex hmac-sha256 of "{timestamp}.{body}">
X-Qlaud-Tool-Id: tool_f5d6afcef...
X-Qlaud-Request-Id: msg_xxx
content-type: application/json
Body
{
"tool_id": "tool_f5d6afcef...",
"tool_use_id": "toolu_xxx",
"name": "get_weather",
"input": { "location": "San Francisco" },
"request_id": "msg_xxx",
"thread_id": "2f1d0c7f-..."
}
Expected response
{ "output": "It is 72°F sunny in San Francisco" }
output can be a string or any JSON value (objects/arrays get stringified
before going back to the assistant). To signal a non-fatal error so the
model can decide what to do:
{ "output": "rate limit hit", "is_error": true }
Verifying the signature
import hmac, hashlib
def verify(headers, body_bytes, secret):
ts = headers["X-Qlaud-Timestamp"]
sig = headers["X-Qlaud-Signature"]
payload = f"{ts}.{body_bytes.decode()}".encode()
expected = hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(sig, expected)
Loop semantics
- Iteration cap: 8 by default. Hitting it returns a partial conversation with
stop_reason: "tool_loop_limit".
- Parallel dispatch: when the model emits multiple
tool_use blocks in one turn, qlaud dispatches all of them in parallel via Promise.all. Total latency = max(per-webhook), not sum.
- Retries: 3 attempts with exponential backoff (250 ms / 1 s / 4 s) on 5xx + network errors. 4xx terminates immediately and the result is sent back to the assistant as
is_error: true so it can decide how to proceed.
- Webhook timeout: 30 s default,
timeout_ms per-tool override.
- Cross-provider: same Anthropic-shape
tool_use/tool_result semantics whether the underlying model is Claude or GPT or DeepSeek. You write one handler.
Errors
| Status | Meaning |
|---|
| 400 | Invalid body (missing required field, non-https:// URL, schema not an object) |
| 409 | A non-revoked tool with the same name already exists |
| 401 | Bad / revoked qlk key |
| 404 | Tool not found OR not owned by caller |