curl --request POST \
--url https://api.waterr.ai/v1/skills \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "book_demo",
"trigger": "book_demo: when the participant asks to see the product live.",
"instructions": "Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.",
"gated_tool_names": [
"schedule_demo"
]
}
'import requests
url = "https://api.waterr.ai/v1/skills"
payload = {
"name": "book_demo",
"trigger": "book_demo: when the participant asks to see the product live.",
"instructions": "Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.",
"gated_tool_names": ["schedule_demo"]
}
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: 'book_demo',
trigger: 'book_demo: when the participant asks to see the product live.',
instructions: 'Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.',
gated_tool_names: ['schedule_demo']
})
};
fetch('https://api.waterr.ai/v1/skills', 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.waterr.ai/v1/skills",
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' => 'book_demo',
'trigger' => 'book_demo: when the participant asks to see the product live.',
'instructions' => 'Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.',
'gated_tool_names' => [
'schedule_demo'
]
]),
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.waterr.ai/v1/skills"
payload := strings.NewReader("{\n \"name\": \"book_demo\",\n \"trigger\": \"book_demo: when the participant asks to see the product live.\",\n \"instructions\": \"Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.\",\n \"gated_tool_names\": [\n \"schedule_demo\"\n ]\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.waterr.ai/v1/skills")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"book_demo\",\n \"trigger\": \"book_demo: when the participant asks to see the product live.\",\n \"instructions\": \"Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.\",\n \"gated_tool_names\": [\n \"schedule_demo\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.waterr.ai/v1/skills")
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\": \"book_demo\",\n \"trigger\": \"book_demo: when the participant asks to see the product live.\",\n \"instructions\": \"Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.\",\n \"gated_tool_names\": [\n \"schedule_demo\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "9f0e1d2c-3b4a-5968-8776-655443322110",
"name": "book_demo",
"trigger": "book_demo: when the participant asks to see the product live.",
"instructions": "Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.",
"active": true,
"membership_id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"gated_tool_names": [
"schedule_demo"
],
"scenarios": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"active": true,
"position": 123
}
],
"created_at": "2026-09-10T09:00:00.000Z",
"updated_at": "2026-09-10T09:00:00.000Z"
}Create a skill
Register a skill on your account, then attach it to any number of scenarios with Attach a skill to a scenario. Names in gated_tool_names must match tools you created via Create a tool; a name that matches no tool simply gates nothing.
curl --request POST \
--url https://api.waterr.ai/v1/skills \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "book_demo",
"trigger": "book_demo: when the participant asks to see the product live.",
"instructions": "Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.",
"gated_tool_names": [
"schedule_demo"
]
}
'import requests
url = "https://api.waterr.ai/v1/skills"
payload = {
"name": "book_demo",
"trigger": "book_demo: when the participant asks to see the product live.",
"instructions": "Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.",
"gated_tool_names": ["schedule_demo"]
}
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: 'book_demo',
trigger: 'book_demo: when the participant asks to see the product live.',
instructions: 'Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.',
gated_tool_names: ['schedule_demo']
})
};
fetch('https://api.waterr.ai/v1/skills', 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.waterr.ai/v1/skills",
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' => 'book_demo',
'trigger' => 'book_demo: when the participant asks to see the product live.',
'instructions' => 'Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.',
'gated_tool_names' => [
'schedule_demo'
]
]),
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.waterr.ai/v1/skills"
payload := strings.NewReader("{\n \"name\": \"book_demo\",\n \"trigger\": \"book_demo: when the participant asks to see the product live.\",\n \"instructions\": \"Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.\",\n \"gated_tool_names\": [\n \"schedule_demo\"\n ]\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.waterr.ai/v1/skills")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"book_demo\",\n \"trigger\": \"book_demo: when the participant asks to see the product live.\",\n \"instructions\": \"Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.\",\n \"gated_tool_names\": [\n \"schedule_demo\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.waterr.ai/v1/skills")
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\": \"book_demo\",\n \"trigger\": \"book_demo: when the participant asks to see the product live.\",\n \"instructions\": \"Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.\",\n \"gated_tool_names\": [\n \"schedule_demo\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "9f0e1d2c-3b4a-5968-8776-655443322110",
"name": "book_demo",
"trigger": "book_demo: when the participant asks to see the product live.",
"instructions": "Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once.",
"active": true,
"membership_id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"gated_tool_names": [
"schedule_demo"
],
"scenarios": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"active": true,
"position": 123
}
],
"created_at": "2026-09-10T09:00:00.000Z",
"updated_at": "2026-09-10T09:00:00.000Z"
}Authorizations
API key from waterr.ai/settings?tab=api-keys. Send as Authorization: Bearer wai_<your_key>.
Body
Response
Skill created.
An invokable instruction pack. Only the one-line trigger sits in the agent's prompt each turn; the full instructions load when the agent invokes the skill mid-meeting and are dropped when the flow completes.
"9f0e1d2c-3b4a-5968-8776-655443322110"
Identifier the agent passes to invoke_skill. Unique per account.
64^[a-zA-Z0-9_-]{1,64}$"book_demo"
One line describing WHEN to invoke this skill. Lives in the base prompt on every turn — keep it short and specific.
500"book_demo: when the participant asks to see the product live."
The instruction pack, delivered to the agent only after it invokes the skill.
8000"Confirm intent first. Then collect their preferred time and timezone, one question per turn, and call schedule_demo once."
Account-level kill-switch for the definition.
true
"a1b2c3d4-e5f6-7890-abcd-ef0123456789"
Custom-function names that refuse to execute until this skill is invoked. [] means the skill is purely behavioural.
20^[a-zA-Z0-9_-]{1,64}$["schedule_demo"]
Scenarios this skill is attached to. Returned only on the single-skill read.
Show child attributes
Show child attributes
"2026-09-10T09:00:00.000Z"
"2026-09-10T09:00:00.000Z"

