Register a tool
Make your system something an agent can be authorized for — with its own destination allowlist, risk rating and egress guards.
A tool is a registered, callable thing: an endpoint, the capability it requires, the hosts it may contact, its risk rating, and whether it is dangerous. Agents can only call tools, and only tools they are explicitly authorized for.
#Register
bashcurl -X POST https://veefore.example.com/v1/tools \
-H "Authorization: Bearer $VEEFORE_API_KEY" \
-H 'content-type: application/json' \
-H 'Idempotency-Key: register-refund-tool' \
-d '{
"workspace_id": "wsp_01JAV…",
"environment_id": "env_01JAV…",
"key": "refund_customer",
"name": "Refund a customer order",
"description": "Issues a refund against an order in the payments service.",
"kind": "HTTP",
"required_capability": "refund.create",
"endpoint_url": "https://payments.internal.example.com/refunds",
"http_method": "POST",
"destination_allowlist": ["payments.internal.example.com"],
"credential_id": "cred_01JAV…",
"risk_level": "HIGH",
"dangerous": true,
"sensitive_parameters": ["customer.email", "customer.phone"],
"parameter_schema": {
"type": "object",
"required": ["orderId", "reason"],
"properties": {
"orderId": { "type": "string" },
"reason": { "type": "string", "maxLength": 500 }
}
}
}'
workspace_idstringrequiredWhich workspace owns the tool.
environment_idstringoptionalScope it to one environment.
nullmakes it available across the workspace's environments. Scope production tools to production.keystringrequiredWhat the agent passes as
tool. Stable; policy rules reference it.kindenumrequiredHTTPCLIENTMCPA2ADATABASEWEBHOOKSIMULATED. OnlyHTTPperforms real egress. See HTTP or CLIENT.required_capabilitystringrequiredThe capability an agent must hold. Note the field name — it is
required_capability, notcapability.endpoint_urlstringoptionalRequired for
HTTP. Must be HTTPS unless the host is loopback.http_methodenumoptionalGETPOSTPUTPATCHDELETE. Defaults toPOST.destination_allowliststring[]optionalHosts this tool may contact. Checked again at the egress boundary.
credential_idstringoptionalThe credential the broker leases for this tool. Omit and the tool runs unauthenticated downstream.
risk_levelenumoptionalLOWMEDIUMHIGHCRITICAL. A policy field:tool.risk_level.dangerousbooleanoptionalMarks the tool destructive or high impact. A policy field:
tool.dangerous. Templates use it to require approval, anddoctorflags a dangerous tool with no rule covering it.sensitive_parametersstring[]optionalDotted paths redacted from logs, audit and the stored result summary.
parameter_schemaobjectoptionalJSON Schema. Validated before execution, so a malformed call fails at the gateway rather than downstream.
#HTTP or CLIENT
Both kinds go through the same pipeline: authentication, capability, policy, risk, approval and audit. They differ in who performs the call once it is permitted.
HTTP |
CLIENT |
|
|---|---|---|
| Performs the call | The gateway | Your code, inside guard() |
| Holds the credential | The broker; the agent never sees it | Your code |
endpoint_url, credential_id |
Required for real egress | Not allowed |
| Bypassable by skipping the SDK | No, there is nothing to call with | Yes |
Registered by veefore init |
No | Yes |
CLIENT is how an existing agent gets decisions, approvals and an audit trail
on day one without moving any secrets. HTTP is where you end up for tools that
matter: broker the credential, re-register the
tool as HTTP with its endpoint, and guard() starts returning the gateway's
result instead of running your function. Nothing executes twice, and no code
changes.
bashcurl -X POST https://veefore.example.com/v1/tools \
-H "Authorization: Bearer $VEEFORE_API_KEY" \
-H 'content-type: application/json' \
-d '{
"workspace_id": "wsp_01JAV…",
"key": "lookup_order",
"name": "Look up an order",
"kind": "CLIENT",
"required_capability": "tool.lookup_order",
"destination_allowlist": ["api.shop.example.com"],
"risk_level": "LOW"
}'
#Registering grants nothing
bashcurl -X POST https://veefore.example.com/v1/tools/tool_01ABC/agents \
-H "Authorization: Bearer $VEEFORE_API_KEY" \
-H 'content-type: application/json' \
-d '{
"agent_id": "agt_01JAV…",
"parameter_restrictions": { "orderId": { "pattern": "^ord_" } }
}'
Two steps, deliberately
If holding refund.create automatically granted access to every tool declaring
it, then registering a new refund tool would silently widen what every existing
finance agent could reach. Tool registration would be a privilege escalation.
With the steps separated, registration grants nobody anything. The two refusals
are distinct so you can tell which half is missing:
CAPABILITY_NOT_GRANTED versus TOOL_NOT_AUTHORIZED.
parameter_restrictions narrows further, per agent. The same tool can be
restricted differently for different agents without a policy rule.
Revoke with DELETE /v1/tools/{toolId}/agents/{agentId}.
#Egress guards
When GATEWAY_ALLOW_REAL_EGRESS=true and the tool kind is HTTP, the gateway
makes a real outbound call. Six guards apply at the egress boundary — after
policy, after approval, after the credential is leased, immediately before bytes
leave the process:
| Guard | Refusal |
|---|---|
Tool kind must be HTTP |
Otherwise the simulated runtime runs |
endpoint_url must be set |
UNPROCESSABLE |
Host must be in the tool's own destination_allowlist |
DESTINATION_BLOCKED |
| HTTPS required unless loopback | DESTINATION_BLOCKED |
redirect: 'error' — redirects are never followed |
Execution fails |
Timeout at GATEWAY_EGRESS_TIMEOUT_MS (15s) |
EXECUTION_TIMEOUT |
Plus a circuit breaker per tool: five consecutive failures opens it for 20 seconds, so one broken downstream cannot consume the gateway's capacity.
Redirects are refused, not followed
A 302 from your downstream fails the execution. This is deliberate: following
a redirect is how an allowlisted host becomes a request to a host that was never
allowlisted, which is server-side request forgery with extra steps.
If your downstream redirects, point endpoint_url at the final location.
Headers the gateway always sends:
httpcontent-type: application/json
accept: application/json
user-agent: Veefore-Gateway/1.0
x-veefore-request-id: req_01JAV…
x-veefore-agent-id: agt_01JAV…
idempotency-key: … # when the agent supplied one
Plus trace propagation headers, plus whatever the leased credential contributes.
x-veefore-request-id is worth logging on your side — it ties your downstream
log line to the Veefore action record.
#Without real egress
With GATEWAY_ALLOW_REAL_EGRESS=false — the default — or for kind
SIMULATED, the simulated runtime handles execution. It produces deterministic
responses so the full pipeline can be exercised end to end, and every
response carries simulated: true. Nothing is ever presented as output from a
real system when it is not.
That flag defaults to off so a local run cannot reach a real external system by accident. Turn it on deliberately. See Local development.
#Disable one
bashcurl -X POST https://veefore.example.com/v1/tools/tool_01ABC/disable \
-H "Authorization: Bearer $VEEFORE_API_KEY" \
-H 'content-type: application/json' \
-d '{ "reason": "Payments service maintenance window" }'
Immediate, for every agent. Refused with TOOL_DISABLED — which is distinct
from a policy BLOCK, so the agent's logs say what actually happened.
Statuses: ACTIVE, RESTRICTED, DISABLED.
#What policy can see
| Field | Type |
|---|---|
tool.id |
string |
tool.key |
string |
tool.kind |
HTTP CLIENT MCP A2A DATABASE WEBHOOK SIMULATED |
tool.protocol |
HTTP MCP A2A |
tool.risk_level |
LOW MEDIUM HIGH CRITICAL |
tool.dangerous |
boolean |
destination.host |
string |
destination.risk_level |
LOW MEDIUM HIGH CRITICAL |
tool.dangerous is the one worth using. Mark destructive tools and let the
starter policy require approval for all of them at once, rather than enumerating
tool keys in a rule that goes stale.
#Practical advice
Set dangerous: true generously. The cost is an approval step. The cost of
not setting it is an agent doing something irreversible with nobody notified.
Keep destination_allowlist as narrow as the tool needs. One host is
typical. It is checked twice — in the pipeline and at egress — and the second
check is what protects you if the endpoint is later edited.
Fill in sensitive_parameters. Redaction is applied on the way in, so a
listed path never reaches logs or audit at all. There is no code path where it
is written and later filtered.
Use parameter_schema. A malformed call failing at the gateway with
VALIDATION_FAILED is much easier to debug than a 500 from your downstream.