curl --request POST \
--url https://api.pyai.com/v1/tools \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"description": "<string>",
"input_schema": {},
"config_schema": {
"fields": [
{
"key": "<string>",
"label": "<string>",
"required": false,
"secret": false,
"help": "<string>",
"placeholder": "<string>",
"options": [
"<string>"
]
}
]
},
"webhook_url": "<string>",
"auth_header": "<string>",
"auth_secret": "<string>",
"timeout_ms": 5000
}
'import requests
url = "https://api.pyai.com/v1/tools"
payload = {
"name": "<string>",
"description": "<string>",
"input_schema": {},
"config_schema": { "fields": [
{
"key": "<string>",
"label": "<string>",
"required": False,
"secret": False,
"help": "<string>",
"placeholder": "<string>",
"options": ["<string>"]
}
] },
"webhook_url": "<string>",
"auth_header": "<string>",
"auth_secret": "<string>",
"timeout_ms": 5000
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
description: '<string>',
input_schema: {},
config_schema: {
fields: [
{
key: '<string>',
label: '<string>',
required: false,
secret: false,
help: '<string>',
placeholder: '<string>',
options: ['<string>']
}
]
},
webhook_url: '<string>',
auth_header: '<string>',
auth_secret: '<string>',
timeout_ms: 5000
})
};
fetch('https://api.pyai.com/v1/tools', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pyai.com/v1/tools",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'description' => '<string>',
'input_schema' => [
],
'config_schema' => [
'fields' => [
[
'key' => '<string>',
'label' => '<string>',
'required' => false,
'secret' => false,
'help' => '<string>',
'placeholder' => '<string>',
'options' => [
'<string>'
]
]
]
],
'webhook_url' => '<string>',
'auth_header' => '<string>',
'auth_secret' => '<string>',
'timeout_ms' => 5000
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.pyai.com/v1/tools"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"input_schema\": {},\n \"config_schema\": {\n \"fields\": [\n {\n \"key\": \"<string>\",\n \"label\": \"<string>\",\n \"required\": false,\n \"secret\": false,\n \"help\": \"<string>\",\n \"placeholder\": \"<string>\",\n \"options\": [\n \"<string>\"\n ]\n }\n ]\n },\n \"webhook_url\": \"<string>\",\n \"auth_header\": \"<string>\",\n \"auth_secret\": \"<string>\",\n \"timeout_ms\": 5000\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.pyai.com/v1/tools")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"input_schema\": {},\n \"config_schema\": {\n \"fields\": [\n {\n \"key\": \"<string>\",\n \"label\": \"<string>\",\n \"required\": false,\n \"secret\": false,\n \"help\": \"<string>\",\n \"placeholder\": \"<string>\",\n \"options\": [\n \"<string>\"\n ]\n }\n ]\n },\n \"webhook_url\": \"<string>\",\n \"auth_header\": \"<string>\",\n \"auth_secret\": \"<string>\",\n \"timeout_ms\": 5000\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pyai.com/v1/tools")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"input_schema\": {},\n \"config_schema\": {\n \"fields\": [\n {\n \"key\": \"<string>\",\n \"label\": \"<string>\",\n \"required\": false,\n \"secret\": false,\n \"help\": \"<string>\",\n \"placeholder\": \"<string>\",\n \"options\": [\n \"<string>\"\n ]\n }\n ]\n },\n \"webhook_url\": \"<string>\",\n \"auth_header\": \"<string>\",\n \"auth_secret\": \"<string>\",\n \"timeout_ms\": 5000\n}"
response = http.request(request)
puts response.read_body{
"object": "tool",
"id": "<string>",
"org_id": "<string>",
"name": "<string>",
"kind": "prebuilt",
"description": "<string>",
"input_schema": {},
"config_schema": {
"fields": [
{
"key": "<string>",
"label": "<string>",
"type": "string",
"required": false,
"secret": false,
"help": "<string>",
"placeholder": "<string>",
"options": [
"<string>"
]
}
]
},
"webhook_url": "<string>",
"execution": "hosted",
"auth_header": "<string>",
"has_auth": true,
"side_effect": "read",
"timeout_ms": 123,
"status": "active",
"created_at": 123
}{
"object": "tool",
"id": "<string>",
"org_id": "<string>",
"name": "<string>",
"kind": "prebuilt",
"description": "<string>",
"input_schema": {},
"config_schema": {
"fields": [
{
"key": "<string>",
"label": "<string>",
"type": "string",
"required": false,
"secret": false,
"help": "<string>",
"placeholder": "<string>",
"options": [
"<string>"
]
}
]
},
"webhook_url": "<string>",
"execution": "hosted",
"auth_header": "<string>",
"has_auth": true,
"side_effect": "read",
"timeout_ms": 123,
"status": "active",
"created_at": 123,
"hmac_secret": "<string>"
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}Create or update a custom tool
Register a custom tool. Idempotent upsert on (org, name): if your org already has a tool with this name, it is updated in place (HTTP 200, no duplicate, hmac_secret preserved), so re-syncing the same tool from a multi-tenant deploy is safe; otherwise a new tool is created (HTTP 201, hmac_secret returned once). A prebuilt/global tool of the same name never blocks creating your org’s tool. In server mode PyAI calls your webhook_url (signed, network-isolated), works on phone calls; client mode runs on the WebSocket. Requires the omni:session scope.
curl --request POST \
--url https://api.pyai.com/v1/tools \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"description": "<string>",
"input_schema": {},
"config_schema": {
"fields": [
{
"key": "<string>",
"label": "<string>",
"required": false,
"secret": false,
"help": "<string>",
"placeholder": "<string>",
"options": [
"<string>"
]
}
]
},
"webhook_url": "<string>",
"auth_header": "<string>",
"auth_secret": "<string>",
"timeout_ms": 5000
}
'import requests
url = "https://api.pyai.com/v1/tools"
payload = {
"name": "<string>",
"description": "<string>",
"input_schema": {},
"config_schema": { "fields": [
{
"key": "<string>",
"label": "<string>",
"required": False,
"secret": False,
"help": "<string>",
"placeholder": "<string>",
"options": ["<string>"]
}
] },
"webhook_url": "<string>",
"auth_header": "<string>",
"auth_secret": "<string>",
"timeout_ms": 5000
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
description: '<string>',
input_schema: {},
config_schema: {
fields: [
{
key: '<string>',
label: '<string>',
required: false,
secret: false,
help: '<string>',
placeholder: '<string>',
options: ['<string>']
}
]
},
webhook_url: '<string>',
auth_header: '<string>',
auth_secret: '<string>',
timeout_ms: 5000
})
};
fetch('https://api.pyai.com/v1/tools', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pyai.com/v1/tools",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'description' => '<string>',
'input_schema' => [
],
'config_schema' => [
'fields' => [
[
'key' => '<string>',
'label' => '<string>',
'required' => false,
'secret' => false,
'help' => '<string>',
'placeholder' => '<string>',
'options' => [
'<string>'
]
]
]
],
'webhook_url' => '<string>',
'auth_header' => '<string>',
'auth_secret' => '<string>',
'timeout_ms' => 5000
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.pyai.com/v1/tools"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"input_schema\": {},\n \"config_schema\": {\n \"fields\": [\n {\n \"key\": \"<string>\",\n \"label\": \"<string>\",\n \"required\": false,\n \"secret\": false,\n \"help\": \"<string>\",\n \"placeholder\": \"<string>\",\n \"options\": [\n \"<string>\"\n ]\n }\n ]\n },\n \"webhook_url\": \"<string>\",\n \"auth_header\": \"<string>\",\n \"auth_secret\": \"<string>\",\n \"timeout_ms\": 5000\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.pyai.com/v1/tools")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"input_schema\": {},\n \"config_schema\": {\n \"fields\": [\n {\n \"key\": \"<string>\",\n \"label\": \"<string>\",\n \"required\": false,\n \"secret\": false,\n \"help\": \"<string>\",\n \"placeholder\": \"<string>\",\n \"options\": [\n \"<string>\"\n ]\n }\n ]\n },\n \"webhook_url\": \"<string>\",\n \"auth_header\": \"<string>\",\n \"auth_secret\": \"<string>\",\n \"timeout_ms\": 5000\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pyai.com/v1/tools")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"input_schema\": {},\n \"config_schema\": {\n \"fields\": [\n {\n \"key\": \"<string>\",\n \"label\": \"<string>\",\n \"required\": false,\n \"secret\": false,\n \"help\": \"<string>\",\n \"placeholder\": \"<string>\",\n \"options\": [\n \"<string>\"\n ]\n }\n ]\n },\n \"webhook_url\": \"<string>\",\n \"auth_header\": \"<string>\",\n \"auth_secret\": \"<string>\",\n \"timeout_ms\": 5000\n}"
response = http.request(request)
puts response.read_body{
"object": "tool",
"id": "<string>",
"org_id": "<string>",
"name": "<string>",
"kind": "prebuilt",
"description": "<string>",
"input_schema": {},
"config_schema": {
"fields": [
{
"key": "<string>",
"label": "<string>",
"type": "string",
"required": false,
"secret": false,
"help": "<string>",
"placeholder": "<string>",
"options": [
"<string>"
]
}
]
},
"webhook_url": "<string>",
"execution": "hosted",
"auth_header": "<string>",
"has_auth": true,
"side_effect": "read",
"timeout_ms": 123,
"status": "active",
"created_at": 123
}{
"object": "tool",
"id": "<string>",
"org_id": "<string>",
"name": "<string>",
"kind": "prebuilt",
"description": "<string>",
"input_schema": {},
"config_schema": {
"fields": [
{
"key": "<string>",
"label": "<string>",
"type": "string",
"required": false,
"secret": false,
"help": "<string>",
"placeholder": "<string>",
"options": [
"<string>"
]
}
]
},
"webhook_url": "<string>",
"execution": "hosted",
"auth_header": "<string>",
"has_auth": true,
"side_effect": "read",
"timeout_ms": 123,
"status": "active",
"created_at": 123,
"hmac_secret": "<string>"
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "invalid_request_error",
"param": "<string>"
}
}Authorizations
Use Authorization: Bearer pyai_live_... (or pyai_test_...).
Body
Optional. Declare customer settings this tool needs (captured per agent on the binding config).
Show child attributes
Show child attributes
HTTPS URL PyAI calls when this server tool runs. Public HTTPS only; private, loopback, and metadata addresses are rejected at registration.
How the tool runs. server = PyAI calls webhook_url (default when webhook_url is set; works on phone calls). client = your connected app executes it over the Omni socket. Register server tools here; do not put webhook_url on the Omni configure frame.
server, client Header name the executor injects the webhook auth value into (e.g. Authorization). Pair with auth_secret.
Auth value for your webhook (e.g. 'Bearer ...'). Stored encrypted at rest, injected by the executor, never echoed back.
read, action Per-call budget (ms). Note the executor also enforces a platform hard ceiling (5000 ms by default); the effective budget is min(timeout_ms, ceiling). On timeout the model receives a synthetic soft { "error": "tool timed out" } and the turn continues.
100 <= x <= 15000Response
Updated existing tool (idempotent upsert by name)
"tool"
prebuilt, custom Customer settings this tool needs to run. Render as a form in your builder; save answers on the agent binding config.
Show child attributes
Show child attributes
How the tool runs. hosted = PyAI runs it (prebuilt read catalog). server = the tool-executor calls your webhook. engine = the Omni call engine runs it natively (call control: transfer_to_human, send_dtmf, play_hold, collect, end_call), not routed to the executor. client = your connected app.
hosted, server, engine, client Whether a webhook auth secret is configured (the secret itself is never returned).
read, action active, disabled