Skip to content

Tools Registry

The Tools Registry lets you register your own external APIs (webhooks, microservices) so that OnceOnly-managed agents can safely call them with built-in validation, authentication, and cost tracking.

Without a registry, agents would call APIs directly:

# ❌ Without Tools Registry
agent.call_api("https://your-company.com/send-email?to=user@example.com")
# No validation, no auth, no cost tracking, no audit trail!

With Tools Registry:

# ✅ With Tools Registry
agent.call_tool("send_email", {"to": "user@example.com"})
# Validated, authenticated, cost-tracked, audited!

A Tool is a registered external API endpoint:

{
"name": "send_email",
"scope_id": "global",
"url": "https://api.example.com/v1/send",
"auth_type": "hmac_sha256",
"timeout_ms": 15000,
"max_retries": 2,
"enabled": true,
"description": "Send email via SMTP service"
}

Tools are scoped for multi-tenant use:

  • global — Available to all agents
  • agent:support_bot — Only for that specific agent

This lets teams manage shared vs. agent-specific integrations.

Tools use HMAC-SHA256 signatures to prove requests came from OnceOnly:

Headers:
X-OnceOnly-Signature: sha256_hmac(body, shared_secret)
X-OnceOnly-Signature-Alg: hmac_sha256
X-OnceOnly-Timestamp: <unix_seconds>

Your backend validates the signature before processing the request.


Terminal window
curl -X POST https://api.onceonly.tech/v1/tools \
-H "Authorization: Bearer once_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "send_email",
"scope_id": "global",
"url": "https://api.example.com/v1/send-email",
"auth": {
"type": "hmac_sha256",
"secret": "your_shared_secret_key_12345"
},
"timeout_ms": 15000,
"max_retries": 2,
"enabled": true,
"description": "Send transactional emails"
}'

Response:

{
"name": "send_email",
"scope_id": "global",
"url": "https://api.example.com/v1/send-email",
"timeout_ms": 15000,
"max_retries": 2,
"enabled": true,
"description": "Send transactional emails",
"has_secret": true,
"secret_id": "sec_a1b2c3d4",
"secret_mask": "***ey_12345"
}

⚠️ Note: The API key is never returned. You get a secret_id for UI display and a secret_mask showing the last 4 characters.

Your endpoint needs to:

  1. Validate HMAC signature from the X-OnceOnly-Signature header
  2. Process the request
  3. Return JSON response

Python Example:

from flask import Flask, request
import hmac
import hashlib
import json
app = Flask(__name__)
SHARED_SECRET = "your_shared_secret_key_12345"
def verify_signature(body: bytes, signature: str) -> bool:
"""Verify OnceOnly request signature"""
expected = hmac.new(
SHARED_SECRET.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route("/v1/send-email", methods=["POST"])
def send_email():
# 1. Verify signature
signature = request.headers.get("X-OnceOnly-Signature", "")
if not verify_signature(request.data, signature):
return {"error": "invalid_signature"}, 401
# 2. Parse request
data = request.get_json()
to = data.get("to")
subject = data.get("subject")
body = data.get("body")
# 3. Send email
try:
# Your email service (SendGrid, AWS SES, etc)
send_email_api(to, subject, body)
return {
"success": True,
"message_id": f"msg_{uuid.uuid4()}",
"recipient": to
}
except Exception as e:
return {"error": str(e)}, 500
if __name__ == "__main__":
app.run(port=5000)

Node.js Example:

const express = require('express');
const crypto = require('crypto');
const app = express();
const SHARED_SECRET = 'your_shared_secret_key_12345';
// Middleware to verify signatures
app.use(express.json({ verify: (req, res, buf) => {
req.rawBody = buf;
}}));
const verifySignature = (body, signature) => {
const expected = crypto
.createHmac('sha256', SHARED_SECRET)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(expected, signature);
};
app.post('/v1/send-email', (req, res) => {
// 1. Verify signature
const signature = req.headers['x-onceonly-signature'];
if (!signature || !verifySignature(req.rawBody, signature)) {
return res.status(401).json({ error: 'invalid_signature' });
}
// 2. Parse request
const { to, subject, body } = req.body;
// 3. Send email
sendEmailViaService(to, subject, body)
.then(result => {
res.json({
success: true,
message_id: `msg_${uuid()}`,
recipient: to
});
})
.catch(err => {
res.status(500).json({ error: err.message });
});
});
app.listen(5000, () => console.log('Tool server running'));

Get all tools in a scope:

Terminal window
curl -H "Authorization: Bearer once_live_xxxxxxxxxxxxx" \
"https://api.onceonly.tech/v1/tools?scope_id=global"

Response:

[
{
"name": "send_email",
"scope_id": "global",
"url": "https://api.example.com/v1/send-email",
"enabled": true,
"description": "Send transactional emails"
},
{
"name": "create_ticket",
"scope_id": "global",
"url": "https://api.example.com/v1/create-ticket",
"enabled": true,
"description": "Create support ticket"
}
]

Terminal window
curl -H "Authorization: Bearer once_live_xxxxxxxxxxxxx" \
"https://api.onceonly.tech/v1/tools/send_email?scope_id=global"

Response:

{
"name": "send_email",
"scope_id": "global",
"url": "https://api.example.com/v1/send-email",
"timeout_ms": 15000,
"max_retries": 2,
"enabled": true,
"description": "Send transactional emails",
"has_secret": true,
"secret_id": "sec_a1b2c3d4",
"secret_mask": "***ey_12345"
}

Terminal window
curl -X POST https://api.onceonly.tech/v1/tools \
-H "Authorization: Bearer once_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "send_email",
"scope_id": "global",
"url": "https://api-v2.example.com/send-email",
"timeout_ms": 20000,
"enabled": true,
"description": "Send emails (v2 endpoint)"
}'

Disable a tool temporarily without deleting it:

Terminal window
curl -X POST https://api.onceonly.tech/v1/tools/send_email/toggle \
-H "Authorization: Bearer once_live_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'

Terminal window
curl -X DELETE https://api.onceonly.tech/v1/tools/send_email?scope_id=global \
-H "Authorization: Bearer once_live_xxxxxxxxxxxxx"

⚠️ Warning: Deleting a tool breaks policies that reference it.


Once registered, agents can call tools through Policies:

{
"agent_id": "support_bot",
"allowed_tools": ["send_email", "create_ticket"],
"blocked_tools": [],
"pricing_rules": [
{"tool": "send_email", "price_usd": 0.001},
{"tool": "create_ticket", "price_usd": 0.01}
]
}

When support_bot calls send_email:

  1. OnceOnly checks the policy → ✓ allowed
  2. Deducts $0.001 from budget
  3. Sends signed request to your endpoint
  4. Your endpoint validates signature
  5. Your endpoint processes the request

┌─────────────────────────────────────┐
│ Team 1: Customer Support │
│ Agent: support_bot │
│ Tools: │
│ - send_email (global) │
│ - create_ticket (team1-specific) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Team 2: Billing │
│ Agent: billing_bot │
│ Tools: │
│ - send_email (global) │
│ - process_payment (team2-specific) │
└─────────────────────────────────────┘

Tool Resolution Order:

When support_bot calls a tool:

  1. Check agent-scoped tools: agent:support_bot
  2. Fall back to global: global

This lets teams have shared tools (send_email) plus custom tools.


POST /v1/send-email HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-OnceOnly-Signature: abc123def456...
X-OnceOnly-Signature-Alg: hmac_sha256
X-OnceOnly-Timestamp: 1705322400
X-OnceOnly-Tool: send_email
X-OnceOnly-Agent-Id: support_bot
{
"tool": "send_email",
"args": {
"to": "customer@example.com",
"subject": "Your order is ready",
"body": "Click here to view",
"idempotency_key": "order_email_ord_123"
},
"agent_id": "support_bot",
"ns": "user_abc",
"key_hash": "k_1234abcd",
"ts": 1705322400,
"lease_id": "lease_abc123xyz",
"scope_id": "global"
}
{
"success": true,
"message_id": "msg_abc123",
"recipient": "customer@example.com",
"timestamp": "2025-01-15T10:30:00Z"
}

If your endpoint returns an error, OnceOnly:

  1. Retries up to max_retries times
  2. If all retries fail, reports error to agent
  3. Logs failure to audit trail
# Your backend
@app.route("/v1/send-email", methods=["POST"])
def send_email():
try:
send_via_service(data)
except ServiceUnavailable:
return {"error": "service_unavailable"}, 503 # Retryable
except ValidationError as e:
return {"error": str(e)}, 400 # Not retryable

Set on tool creation:

{
"name": "send_email",
"max_retries": 2,
"timeout_ms": 15000
}

OnceOnly will retry:

  • On 5xx errors (server errors)
  • On timeout (after 15 seconds)
  • Up to 2 times total

Terminal window
curl -H "Authorization: Bearer once_live_xxxxxxxxxxxxx" \
"https://api.onceonly.tech/v1/agents/support_bot/metrics?period=day"

Response:

{
"agent_id": "support_bot",
"period": "day",
"total_actions": 450,
"blocked_actions": 5,
"total_spend_usd": 125.50,
"top_tools": [
{
"tool": "send_email",
"count": 400
},
{
"tool": "create_ticket",
"count": 50
}
]
}

  1. Use stable URLs — Don’t move your endpoint frequently
  2. Validate signatures — Always check the HMAC header
  3. Handle retries — Implement idempotency on your side too
  4. Set appropriate timeout — Account for slowest calls
  5. Return JSON — Always respond with JSON, even errors
  6. Log requests — For debugging integration issues
  7. Use descriptive namessend_email not tool_1
@app.route("/v1/send-email", methods=["POST"])
def send_email():
# Log all requests
logger.info(f"Received tool call: {request.get_json()}")
# Verify signature
if not verify_signature(request.data, request.headers.get("X-OnceOnly-Signature")):
logger.error("Invalid signature")
return {"error": "invalid_signature"}, 401
# Process with idempotency
data = request.get_json()
# Your code
result = send_email_api(...)
# Log response
logger.info(f"Tool call successful: {result}")
return result
  1. Don’t expose secrets — Keep auth_secret secure
  2. Don’t ignore signature — Always validate
  3. Don’t use GET — POST only for tool calls
  4. Don’t change auth secret — Breaks authentication
  5. Don’t slow endpoint — Keep response time < timeout_ms
  6. Don’t hardcode URLs — Make them configurable

Tool Name: create_support_ticket
URL: https://support.example.com/api/v1/tickets
Auth: HMAC-SHA256
Timeout: 10 seconds
Retries: 2
Purpose: Create a support ticket for customers
register_tool(
name="create_support_ticket",
scope_id="global",
url="https://support.example.com/api/v1/tickets",
auth_type="hmac_sha256",
auth_secret="secret_key_xyz",
timeout_ms=10000,
max_retries=2,
description="Create support ticket"
)
from flask import Flask, request
import hmac, hashlib, json, uuid
app = Flask(__name__)
@app.route("/api/v1/tickets", methods=["POST"])
def create_ticket():
# 1. Verify signature
sig = request.headers.get("X-OnceOnly-Signature", "")
expected = hmac.new(b"secret_key_xyz", request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
return {"error": "Unauthorized"}, 401
# 2. Parse request
data = request.get_json()
# 3. Create ticket
ticket = {
"id": f"TKT-{uuid.uuid4()}",
"subject": data["subject"],
"description": data["description"],
"priority": data.get("priority", "normal"),
"created_at": datetime.now().isoformat()
}
# Save to database
db.tickets.insert(ticket)
# 4. Return response
return {
"success": True,
"ticket_id": ticket["id"],
"created_at": ticket["created_at"]
}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
{
"agent_id": "support_bot",
"allowed_tools": ["send_email", "create_support_ticket"],
"pricing_rules": [
{"tool": "send_email", "price_usd": 0.001},
{"tool": "create_support_ticket", "price_usd": 0.05}
]
}
# Via OnceOnly (with validation, auth, cost tracking)
agent.call_tool("create_support_ticket", {
"subject": "Can't login",
"description": "Forgot password",
"priority": "high"
})

  • Tools Registry lets you register custom APIs
  • Scopes enable multi-tenant tool management
  • HMAC-SHA256 authenticates requests to your backend
  • Pricing rules track costs per tool call
  • Retries handled by OnceOnly
  • Your backend only needs to validate signature + process request

Next, learn about Policies to control which agents can call which tools.