tools[] array and the same soft result
contract (a tool failure never breaks the turn). Every tool has an execution
mode (returned on GET /v1/tools) that says who runs it:
Manage everything from the Tools screen in the console:
browse the hosted catalog, register custom tools, see a live call log.
Hosted catalog (zero setup)
Hosted tools need no webhook and no hosting, PyAI runs them for you. Enable one by adding its name totools[] (or toggling it in Agents → Tools), and the
agent’s brain calls it mid-conversation. GET /v1/tools returns the live catalog
(each row has an execution mode, an id, a side_effect, and, where
relevant, a config_schema). Read execution instead of inferring transport
from the tool name.
Catalog at a glance
🔒 = secret (encrypted at rest, returned masked). The nine hosted read tools run
today with zero setup. The five engine call-control tools are emitted by the
engine today, but a phone-call action only happens if your telephony transport
handles the frame, see Engine mode before enabling
them. The remaining action tools (
send_sms, send_email, calendar,
payment) are reserved catalog entries, the name, execution, side_effect,
input_schema, and config_schema are stable so you can build against them now,
but calling one returns a soft {"error":"hosted_tool_unavailable"} until it ships,
and (where it needs settings) until those settings are saved on the agent (see
Tool settings).
Live hosted tools, arguments & results
Arguments are what the brain fills in per call; results are returned to the brain (and never break the turn, a bad argument comes back as a softerror).
search_knowledge, KB-as-a-tool
search_knowledge, KB-as-a-tool
Searches the knowledge bases bound to the agent (falls back to the org’s
default KBs for zero-state sessions). Knowledge stays customer-hosted.
query(string, required), what to look up.top_k(integer, optional, default 5, 1-20), how many passages.
math, calculator
math, calculator
Safe arithmetic only:
+ - * / % ^, parentheses, unary ±, decimals and
exponent notation. No identifiers or function calls (nothing to inject).expression(string, required, ≤200 chars), e.g."2 + 3 * 4".
datetime, current time
datetime, current time
timezone(string, optional, defaultUTC), an IANA name likeAmerica/New_York.
unit_convert, units
unit_convert, units
Converts within a dimension: length (
mm cm m km in ft yd mi),
mass (mg g kg oz lb), or temperature (c f k).value(number, required),from(string, required),to(string, required).
currency, FX conversion
currency, FX conversion
Live mid-market rates.
from(string, required),to(string, required), 3-letter ISO codes.amount(number, optional, default 1).
weather, current + forecast
weather, current + forecast
Geocodes the place name, then returns current conditions and a 3-day forecast.
location(string, required), city/place name.units(string, optional,metric|imperial, defaultmetric).
geocode, place → coordinates
geocode, place → coordinates
address(string, required), place/address to resolve.limit(integer, optional, default 5, 1-10).
web_search, web lookup
web_search, web lookup
Backed by Wikipedia today (no key, no setup).
query(string, required).limit(integer, optional, default 5, 1-10).
news, recent headlines
news, recent headlines
topic(string, required).limit(integer, optional, default 5, 1-10).
Tool settings (per-agent config)
Some tools need a little setup before they can run, an action tool likesend_sms needs a from-number and a provider key; transfer_to_human needs a
destination. A tool declares what it needs in its config_schema (returned by
GET /v1/tools), and you supply the values per agent on the tool binding’s
config. This keeps the same tool reusable across agents with different settings.
"secret": true are encrypted at rest and returned masked
("********") on every read, re-send the mask (or leave the field blank) to keep
the stored value, or send a new value to rotate it. PyAI decrypts a secret only at
execution time. Custom tools can declare their own config_schema too, or omit it
and let your webhook manage its own configuration.
Server mode (PyAI calls your webhook)
Register a tool with awebhook_url. When the agent calls it, PyAI validates the
arguments, sends a signed request to the exact URL you saved, and feeds the
result back to the agent.
Example: Book a calendar event
This example registershttps://api.example.com/calendar/book as a server tool,
creates an agent, binds the saved tool, and connects through that agent profile.
1. Register the server tool
hmac_secret securely. It is shown once and is how your
webhook verifies that a request came from PyAI. Optionally set auth_header and
auth_secret when registering the tool if your API also requires its own
authorization header.
2. Create an agent and bind the returned tool ID
PUT /v1/agents/{id}/tools replaces the agent’s bindings, so include every tool
that should remain enabled.
3. Connect through the agent profile
Connect your normal Omni client to:
0x03 control
envelope:
session_label selects the stored agent profile, including the server-tool
binding. The realtime frame does not repeat the webhook URL.
Re-syncing is idempotent.
POST /v1/tools upserts on (org, name): posting a
tool whose name your org already has updates it in place (HTTP 200, no
duplicate, hmac_secret preserved) instead of creating a second copy, so a
multi-tenant deploy can re-push its tool catalog safely. A brand-new name creates
the tool (HTTP 201, hmac_secret returned once). You can also update explicitly
by id with POST /v1/tools/{id}.Rotating the signing secret with no dropped calls: call
POST /v1/tools/{id} with { "rotate_secret": true } to mint a new
hmac_secret (returned once). For a zero-drop rotation, deploy verification that
accepts both the old and new secret, rotate, confirm traffic verifies against
the new secret, then drop the old one.What PyAI sends your webhook
APOST with a JSON body:
The signature is
HMAC-SHA256(secret, "<t>.<raw_body>") in hex. Verify it on
every call and reject anything where the timestamp is stale (e.g. > 5 min) to
defeat replays.
What your webhook should return
For a declared write, return an explicit operation acknowledgement only after your system confirms the operation completed. HTTP 200 means the webhook transport completed; it does not prove the booking, write, or other action did. A supported booking response is:data:
success: true, ok: true, or executed: true, or a completed status of ok,
success, succeeded, completed, created, updated, deleted, booked,
scheduled, or sent. Any contradictory failure marker prevents a completion
claim. Callback submission and ticket creation retain their specific
acknowledgement contracts.
Do not place the only acknowledgement on a surrounding result wrapper.
For example, { "success": true, "result": { "id": "evt_7f3a" } } does not
satisfy this contract: the operation at the end of the result chain has no
positive acknowledgement. Prefer the flat acknowledgement with data above.
For client mode, put that operation object inside the required tool_result
frame’s result field; the frame itself is only a transport envelope.
Empty results, plain text, malformed or contradictory results, and pending or
unknown outcomes do not authorize the agent to say the action completed.
accepted, submitted, pending, or queued is not generic completion;
arbitrary statuses such as confirmed are not inferred to mean completion.
When completion is uncertain, reconcile the original operation in your system
and use its durable idempotency record. Do not blindly repeat a booking or
payment after a timeout or an uncertainty response.
Read-only tools can return ordinary JSON business data, including empty data.
Existing custom write webhooks that returned arbitrary shapes may need to
adopt this positive-acknowledgement contract before the agent can confirm
completion.
Reliability you get for free
- Soft-fail, a timeout, 5xx, or unreachable webhook becomes a structured error the agent can apologize for; it never crashes the call.
- Timeouts & size cap, the effective budget is
min(timeout_ms, platform ceiling); the ceiling is 5 s by default, so registeringtimeout_msabove it has no effect unless PyAI raises the ceiling for your org. Results larger than ~6 KB are truncated. - Idempotency, a retried tool invocation reuses the same
call_id; PyAI replays the prior successful result instead of calling your webhook again. Treatcall_idas the idempotency key on your side too. It is not the conversation/call record identifier, and it is not a callback-delivery ID. - Circuit breaker, if your webhook fails repeatedly, PyAI briefly stops calling it (and tells the agent the tool is unavailable) instead of hammering a broken endpoint and slowing every turn.
- Registered URL only, only the exact
webhook_urlyou registered is ever called, and only over public HTTPS (private, loopback, and metadata addresses are rejected at registration).
Engine mode (call control)
Call-control tools (transfer_to_human, send_dtmf, play_hold, collect,
end_call) are media/SIP actions on a phone call. The Omni engine decides
when to fire one and emits a control frame; the actual telephony action runs in
the transport that bridges Omni to the carrier, Twilio Media Streams,
FreeSWITCH, your SIP stack.
There are two ways to run that transport:
- PyAI managed Telephony (beta), buy a
number, bind it to an agent, and PyAI runs the bridge. Managed call control is
coming soon: PyAI’s bridge will perform these verbs for you (and fold the
agent’s
destinationsetting intotransfer_to_human), so they work end-to-end with no transport code. - Your own transport, you connect the WebSocket and translate each frame into a carrier operation (the Twilio and FreeSWITCH guides show working handlers).
0x03 control frame whose event is the tool name,
perform the carrier action with the arguments spread in the frame. The exact
shapes are in the wire protocol §4.1,
and the FreeSWITCH and
Twilio guides show working handlers for all five
verbs. Quick map:
Not running telephony (a browser or in-app agent)? These verbs are phone concepts
and are unavailable,
transfer_to_human and friends require a real call leg.
The Playground never offers or simulates transfer. On telephony,
transfer_to_human is available only when the tool is enabled and its
per-agent destination is configured; otherwise Omni says transfer is
unavailable. Use server tools for app actions instead.
Client mode (on the WebSocket)
For per-session or dynamic execution, define the tool in realtimeconfigure.tools[] with no URL:
tool_call in an 0x03 frame:
0x03 control frame:
Side effects & confirmation
Mark tools that change state with"side_effect": "action" (vs. "read"). The
agent requests caller confirmation for an action tool before firing it. That
is a runtime confirmation gate, not independent proof that a customer backend
may perform a write. Your backend must still authenticate its customer tenant,
authorize the requested calendar/payment/CRM action, bind the persisted action
to call_id, and return the stored result for duplicate delivery. If a
downstream write succeeds but its response times out, resolve the same call_id
against that durable result; never submit a second write.
Booking integration contract
For a customer-owned booking or payment, PyAI owns the voice session and your backend owns the customer tenant, calendar/provider authorization, and durable result. Keep these identifiers separate:
A safe appointment flow is:
- The agent states the appointment summary and receives caller confirmation.
- Omni sends
tool_callwith a newcall_id; your backend authenticates the mapped customer tenant and verifies it is allowed to make that booking. - Persist a pending row keyed by
call_id, including the immutable action summary or argument digest, before calling the calendar provider. - Commit the provider booking and persist its provider event ID and response.
- Send the stored result in
tool_result. On duplicate delivery, return that same stored result. On changed details, require a new confirmation and a new invocation; do not overwrite the earlier booking under itscall_id.
{ "type": "tool_result", "call_id": "…", "error": "not_authorized" }.
If the calendar provider times out after accepting a booking, query or reconcile
the pending row before replying; a timeout is not authorization to retry the
write. Prompt-level confirmation does not by itself prove who is authorized to
book or charge.
See also
Omni wire protocol
Full frame reference including
tool_call / tool_result.Browser voice agent
End-to-end website agent with grounding.
API reference
/v1/tools and /v1/agents/{id}/tools.Tools in the console
Catalog, custom-tool builder, and call log.