Orchestrator tools¶
An Orchestrator reaches tools from three sources: sub-Orchestrators linked beneath it (see Orchestrator links), external MCP servers (see Orchestrator MCP servers), and a curated catalog of built-in tools. This page covers that last source: listing the catalog, and turning individual tools on, with configuration, per Orchestrator.
You can't attach an arbitrary custom Python function as an Orchestrator tool. If a built-in tool doesn't cover what you need, build or point at an MCP server instead; see Orchestrator MCP servers.
Gated behind a deployment setting, admin session token required
See the two notes at the top of Orchestrators: every endpoint here only exists when your deployment has Orchestrators turned on, and every endpoint here authenticates with an admin session token.
Two different field names for "which tool"¶
The catalog endpoint and the per-Orchestrator endpoints return slightly different objects. It's easy to mix them up, so here's the distinction:
GET /api/v1/orchestrator-tools(the catalog) returns objects whose identifying field is calledname.GET /api/v1/orchestrators/{orchestrator_id}/toolsandPUT /api/v1/orchestrators/{orchestrator_id}/tools/{tool_name}(the per-Orchestrator endpoints) return objects whose identifying field is calledtool_name.
Both refer to the same underlying tool identifier, send_email,
google_search, and so on; only the JSON key differs depending on which
endpoint returned it.
The catalog: every tool available to enable¶
Requires: Viewer and above.
Returns the full list of tools any Orchestrator in this deployment could enable, whether or not any Orchestrator currently has them on.
| Field | Type | Notes |
|---|---|---|
name |
string | The tool's identifier, used as the {tool_name} path segment when enabling it. |
description |
string | A human-readable summary of what the tool does. |
config_schema |
object or null |
A JSON-Schema-shaped object describing the keys this tool's config accepts, or null if it takes no configuration. Descriptive only, see Config isn't validated server-side below. |
display_name |
string or null |
A short label for admin UI display. |
category |
string | "built_in" (Gemini's own tools), "messaging" (AiFlow's own send_email, send_whatsapp, and send_telegram), or "knowledge" (search_skills). |
kind |
string | "tool" for a normal callable tool, or "code_executor" for code_execution, which plugs into the model's code-execution mechanism rather than being called like an ordinary tool. There is no behavioral difference from your side either way, both are enabled the same way, through the same PUT endpoint below. |
gemini_only |
boolean | true if this tool only works with a bare Gemini model. See The Gemini-only restriction below. |
supports_approval |
boolean | true if this tool can be switched to require human approval before it runs. See Human approval gate below. |
The built-in tool catalog¶
AiFlow's own tools¶
name |
display_name |
category |
What it does | config fields |
supports_approval |
|---|---|---|---|---|---|
send_email |
Send Email | messaging |
Sends an email through the same email provider integration a native Agent's send_email tool uses. |
from_address (string, optional): overrides the deployment's default sender, e.g. "Acme Support <support@acme.com>". |
true |
send_whatsapp |
Send WhatsApp | messaging |
Sends a WhatsApp message using a pre-approved Content Template. | content_sid (string, required): the Content Template's SID. from_number (string, required): the E.164 WhatsApp-enabled number to send from. An Orchestrator has no phone number of its own the way an Agent does, so the sending number has to be set here explicitly. |
true |
send_telegram |
Send Telegram | messaging |
Sends a plain-text Telegram message. There is no template to get approved, unlike WhatsApp. | bot_token (string, optional): from @BotFather, falling back to the deployment's TELEGRAM_BOT_TOKEN. chat_id (string, optional): the chat to send to when a call does not name one, falling back to TELEGRAM_CHAT_ID. |
true |
search_skills |
Search Skills | knowledge |
Looks up a step-by-step procedure for the current situation, this Orchestrator's own skills, matched by trigger description rather than a search query. | none | false |
remember_about_user |
Remember About User | knowledge |
Saves a short note onto the person's contact record, the same profile a native Agent reads, so it is there next time and every Agent sees it. Nothing is saved for a visitor who has not been identified. | none | false |
recall_about_user |
Recall About User | knowledge |
Reads the person's contact-record profile: everything known about them from every previous conversation, not just this one. | none | false |
follow_up |
Follow Up | automation |
Runs on its own schedule rather than being callable mid-run: periodically scans the whole shared Contacts list and emails anyone whose situation still matches the reason you set. See Tools: follow_up for the full explanation, shared with the native-Agent version of this tool. |
frequency_days (integer, required), reason (string, required). |
false |
send_email, send_whatsapp, and send_telegram log to the same
outbound-message log and audit trail as a native Agent's sends, with
triggered_by_orchestrator_id set on the message row instead of
agent_id. They're also, today, the only three tools with
supports_approval: true, see Human approval
gate below: the tools with a genuine external side
effect are exactly the ones worth gating; a read-only lookup like
search_skills has nothing to defer.
Gemini's built-in tools¶
These map to capabilities Gemini models expose natively. Every one of
them requires a bare Gemini model (gemini_only: true); see
The Gemini-only restriction.
name |
display_name |
What it does | config fields |
kind |
|---|---|---|---|---|
google_search |
Google Search | Searches Google for current information from the public web. | none | tool |
url_context |
URL Context | Retrieves and reads the content of URLs mentioned in the conversation. | none | tool |
vertex_ai_search |
Vertex AI Search | Searches your own private Vertex AI Search data store or search engine (internal documents, policies, knowledge bases). | data_store_id (string, optional) or search_engine_id (string, optional): set one or the other, not both. Both are the fully-qualified Vertex AI Search resource path, e.g. projects/{project}/locations/{location}/collections/{collection}/dataStores/{dataStore}. |
tool |
google_maps_grounding |
Google Maps Grounding | Grounds answers with Google Maps data: places, directions, hours. | none | tool |
enterprise_web_search |
Enterprise Web Search | Web search grounding suitable for enterprise compliance requirements. | none | tool |
code_execution |
Code Execution | Lets the model write and run Python code for calculations or data analysis. | none | code_executor |
google_maps_grounding only works when your deployment runs in Vertex AI
enterprise mode (GOOGLE_GENAI_USE_ENTERPRISE=true). On a deployment
using a plain Gemini API key instead, enabling it has no effect.
Gemini's own tool-calling layer normally won't let google_search or
vertex_ai_search combine with any other tool on the same Orchestrator,
a restriction unrelated to the Gemini-only rule above. AiFlow works
around this automatically, so enabling either one alongside an MCP
connection or a sub-Orchestrator link works exactly as you'd expect, with
no extra configuration required.
The Gemini-only restriction¶
The six tools in the table above require the Orchestrator's model
field to be a bare Gemini model name (see
Orchestrators: multi-model, not just Gemini),
not a provider/model LiteLLM string. This is a hard constraint. Enabling
one of these tools on an Orchestrator configured with, say,
anthropic/claude-sonnet-5 doesn't fail at enable time (the PUT below
still succeeds), but it fails at the Orchestrator's next delegated call,
with a clear AiFlow-authored error rather than an opaque one from deeper
in the model integration layer. It never fails silently, but since the
enable-time call succeeds regardless, check the model yourself before it
comes up at run time: there's no validation tying model and enabled
tools together at configuration time.
Not exposed¶
A few of Gemini's other built-in capabilities are not available as Orchestrator tools. Each is left out for a concrete reason:
- An interactive shell or bash tool. Every command would need a human to approve it, and a headless run has nowhere to ask.
- A computer-use tool. It needs a real screen to control.
- Memory tools. They need a memory backend AiFlow does not run.
- Human-in-the-loop input tools. There is no interface for someone to answer mid-run.
An Orchestrator's current tool configuration¶
Requires: Viewer and above.
Returns the same catalog as GET /api/v1/orchestrator-tools, merged with
this Orchestrator's current enabled and config state for each tool. A
tool this Orchestrator has never had a PUT call made for comes back
with enabled: false and config: null.
| Field | Type | Notes |
|---|---|---|
tool_name |
string | The tool's identifier. Note the field name: tool_name here, name on the catalog endpoint above. |
description |
string | Same as the catalog. |
enabled |
boolean | Whether this tool is currently on for this Orchestrator. |
config |
object or null |
This Orchestrator's stored configuration for the tool, or null if none was ever set. |
config_schema |
object or null |
Same as the catalog. |
display_name |
string or null |
Same as the catalog. |
category |
string | Same as the catalog. |
kind |
string | Same as the catalog. |
gemini_only |
boolean | Same as the catalog. |
supports_approval |
boolean | Same as the catalog: whether this tool can be gated, not whether it currently is. |
requires_approval |
boolean | Whether human approval is currently turned on for this tool on this Orchestrator. false for a tool never PUT for this Orchestrator, same as enabled. See Human approval gate. |
A nonexistent orchestrator_id gets a 404.
Enabling (or configuring) a tool¶
Requires: Admin and above.
A stricter tier than links and MCP connections
This endpoint requires Admin, not Editor, unlike
Orchestrator links and
Orchestrator MCP servers, which both
only require Editor. If a request here fails with 403 for an
Editor token that works fine on those other two endpoints, that's
why, not a bug.
| Field | Type | Required | Default |
|---|---|---|---|
enabled |
boolean | No | true |
config |
object or null |
No | null |
requires_approval |
boolean | No | false |
This is an upsert: the first PUT for a given orchestrator_id and
tool_name pair creates the row, and every subsequent one replaces it
entirely. There's no partial merge of any field: sending a new config
object replaces the old one in full rather than deep-merging with it, and
omitting requires_approval resets it to false, the same "full
replace, not merge" behavior config already has (see the warning at the
bottom of this page). {tool_name} must be one of the name values from
the catalog above; anything else gets a
404 with {"detail": "Unknown tool '<tool_name>'"}. A nonexistent
orchestrator_id also gets a 404. Setting requires_approval: true on
a tool whose catalog entry has supports_approval: false gets a 400
instead, see Human approval gate below.
Config isn't validated server-side¶
config_schema, shown on both the catalog and per-Orchestrator GET
endpoints above, exists to describe the shape of config so an admin UI
can build a form from it. The PUT endpoint doesn't validate config
against it. You can PUT a send_whatsapp config that's missing
content_sid and from_number, and it will be accepted and stored
as-is. The consequence shows up at run time instead: the tool call
returns an explicit error string to the model, for send_whatsapp
specifically,
"Error: this tool isn't fully configured (missing content_sid/from_number).",
rather than actually sending anything. Populate every field marked
required in the tables above if you want the tool to work; the API
itself won't stop you from skipping one.
Example: enabling send_email¶
const response = await fetch(
"https://api.your-domain.com/api/v1/orchestrators/1/tools/send_email",
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify({
enabled: true,
config: { from_address: "Acme Support <support@acme.com>" },
}),
},
);
Example: enabling send_whatsapp¶
content_sid and from_number are both required for this tool to
actually work; see Config isn't validated server-side
above.
import httpx
response = httpx.put(
"https://api.your-domain.com/api/v1/orchestrators/1/tools/send_whatsapp",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"enabled": True,
"config": {
"content_sid": "HXf1a2b3c4d5e6f7890abcdef123456789",
"from_number": "+15551234567",
},
},
)
response.raise_for_status()
const response = await fetch(
"https://api.your-domain.com/api/v1/orchestrators/1/tools/send_whatsapp",
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify({
enabled: true,
config: {
content_sid: "HXf1a2b3c4d5e6f7890abcdef123456789",
from_number: "+15551234567",
},
}),
},
);
Example: enabling vertex_ai_search¶
Set either data_store_id or search_engine_id, never both. This
Orchestrator's model must be a bare Gemini model name for this tool to
work; see The Gemini-only restriction
above.
curl -X PUT https://api.your-domain.com/api/v1/orchestrators/1/tools/vertex_ai_search \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{
"enabled": true,
"config": {
"data_store_id": "projects/acme-prod/locations/global/collections/default_collection/dataStores/support-kb"
}
}'
import httpx
response = httpx.put(
"https://api.your-domain.com/api/v1/orchestrators/1/tools/vertex_ai_search",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"enabled": True,
"config": {
"data_store_id": (
"projects/acme-prod/locations/global/collections/"
"default_collection/dataStores/support-kb"
)
},
},
)
response.raise_for_status()
const response = await fetch(
"https://api.your-domain.com/api/v1/orchestrators/1/tools/vertex_ai_search",
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify({
enabled: true,
config: {
data_store_id:
"projects/acme-prod/locations/global/collections/default_collection/dataStores/support-kb",
},
}),
},
);
Disabling a tool¶
Send {"enabled": false} to the same endpoint. The stored config is
left as-is, so re-enabling the tool later doesn't require re-submitting
its configuration:
Omitting config on this request clears it
config defaults to null on this schema. If you send
{"enabled": false} with no config key, that's equivalent to
sending {"enabled": false, "config": null}, which does clear
the stored configuration: this endpoint replaces the row's fields
with whatever the request body contains, it doesn't merge them. If
you want to disable a tool while keeping its configuration for
later, include the same config object you originally set, with
enabled changed to false.
Human approval gate¶
Set requires_approval: true on a tool whose catalog entry has
supports_approval: true (today send_email, send_whatsapp, and
send_telegram) and a call to it stops running on the spot. Instead, it creates a
PendingApproval row and the model gets back a string telling it the
action is queued, not done, so it doesn't tell the caller something
happened before it actually has.
curl -X PUT https://api.your-domain.com/api/v1/orchestrators/1/tools/send_email \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"enabled": true, "requires_approval": true}'
This is a deferred queue, not a paused run: nothing here suspends the
Orchestrator's own bounded step budget and wall-clock timeout waiting on a
human decision that might take hours. The run finishes normally with the
gated call resolved to "queued." See Approvals for the
full mechanics of what happens between queuing and a decision, and the
/api/v1/pending-approvals endpoints (list, approve, reject) that review
it, requiring Admin and above, the same tier as this endpoint.
That page also covers being told a request is waiting: a queued request can be emailed, sent over Telegram or WhatsApp, or phoned through, so it is not left until somebody opens the dashboard.
Setting requires_approval: true on a tool with supports_approval: false
(any of Gemini's own built-ins today) fails with a 400:
This only ever covers a tool from the catalog above, never a tool from a connected MCP server. A connection is handed to Google ADK as one whole toolset; ADK calls that server's individual tools directly, so there's no point where this gate could intercept one of those calls the way it does a registered built-in. If a sensitive action only exists as an MCP tool, the MCP server itself is the only place that can hold off performing it pending confirmation today; there's no way to defer or replay an MCP tool call from this side.