curl --request POST \
--url https://app.famulor.io/api/v1/tools \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "check_order_status",
"description": "Look up the status of a customer order by order number.",
"type": "api",
"config": {
"method": "GET",
"url": "https://api.example.com/orders/status",
"headers": {
"Authorization": "Bearer sk_live_..."
},
"parameters": [
{
"name": "order_number",
"type": "string",
"description": "The order number",
"required": true,
"source": "llm",
"location": "query"
}
],
"timeout_ms": 10000,
"async": false,
"filler_phrase": "One moment, I am checking that for you."
}
}
'import requests
url = "https://app.famulor.io/api/v1/tools"
payload = {
"name": "check_order_status",
"description": "Look up the status of a customer order by order number.",
"type": "api",
"config": {
"method": "GET",
"url": "https://api.example.com/orders/status",
"headers": { "Authorization": "Bearer sk_live_..." },
"parameters": [
{
"name": "order_number",
"type": "string",
"description": "The order number",
"required": True,
"source": "llm",
"location": "query"
}
],
"timeout_ms": 10000,
"async": False,
"filler_phrase": "One moment, I am checking that for you."
}
}
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: 'check_order_status',
description: 'Look up the status of a customer order by order number.',
type: 'api',
config: {
method: 'GET',
url: 'https://api.example.com/orders/status',
headers: {Authorization: 'Bearer sk_live_...'},
parameters: [
{
name: 'order_number',
type: 'string',
description: 'The order number',
required: true,
source: 'llm',
location: 'query'
}
],
timeout_ms: 10000,
async: false,
filler_phrase: 'One moment, I am checking that for you.'
}
})
};
fetch('https://app.famulor.io/api/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://app.famulor.io/api/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' => 'check_order_status',
'description' => 'Look up the status of a customer order by order number.',
'type' => 'api',
'config' => [
'method' => 'GET',
'url' => 'https://api.example.com/orders/status',
'headers' => [
'Authorization' => 'Bearer sk_live_...'
],
'parameters' => [
[
'name' => 'order_number',
'type' => 'string',
'description' => 'The order number',
'required' => true,
'source' => 'llm',
'location' => 'query'
]
],
'timeout_ms' => 10000,
'async' => false,
'filler_phrase' => 'One moment, I am checking that for you.'
]
]),
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://app.famulor.io/api/v1/tools"
payload := strings.NewReader("{\n \"name\": \"check_order_status\",\n \"description\": \"Look up the status of a customer order by order number.\",\n \"type\": \"api\",\n \"config\": {\n \"method\": \"GET\",\n \"url\": \"https://api.example.com/orders/status\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_...\"\n },\n \"parameters\": [\n {\n \"name\": \"order_number\",\n \"type\": \"string\",\n \"description\": \"The order number\",\n \"required\": true,\n \"source\": \"llm\",\n \"location\": \"query\"\n }\n ],\n \"timeout_ms\": 10000,\n \"async\": false,\n \"filler_phrase\": \"One moment, I am checking that for you.\"\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://app.famulor.io/api/v1/tools")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"check_order_status\",\n \"description\": \"Look up the status of a customer order by order number.\",\n \"type\": \"api\",\n \"config\": {\n \"method\": \"GET\",\n \"url\": \"https://api.example.com/orders/status\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_...\"\n },\n \"parameters\": [\n {\n \"name\": \"order_number\",\n \"type\": \"string\",\n \"description\": \"The order number\",\n \"required\": true,\n \"source\": \"llm\",\n \"location\": \"query\"\n }\n ],\n \"timeout_ms\": 10000,\n \"async\": false,\n \"filler_phrase\": \"One moment, I am checking that for you.\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.famulor.io/api/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\": \"check_order_status\",\n \"description\": \"Look up the status of a customer order by order number.\",\n \"type\": \"api\",\n \"config\": {\n \"method\": \"GET\",\n \"url\": \"https://api.example.com/orders/status\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_...\"\n },\n \"parameters\": [\n {\n \"name\": \"order_number\",\n \"type\": \"string\",\n \"description\": \"The order number\",\n \"required\": true,\n \"source\": \"llm\",\n \"location\": \"query\"\n }\n ],\n \"timeout_ms\": 10000,\n \"async\": false,\n \"filler_phrase\": \"One moment, I am checking that for you.\"\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "f1b2c3d4-0000-4000-8000-000000000010",
"name": "check_order_status",
"description": "Look up the status of a customer order by order number.",
"type": "api",
"config": {
"method": "GET",
"url": "https://api.example.com/orders/status",
"headers": {
"Authorization": "•••"
},
"parameters": [
{
"name": "order_number",
"type": "string",
"description": "The order number",
"required": true,
"source": "llm",
"location": "query"
}
],
"static_values": {},
"response_mapping": {},
"timeout_ms": 10000,
"async": false,
"filler_phrase": "One moment, I am checking that for you."
},
"is_active": true,
"created_at": "2026-07-01T09:00:00Z",
"updated_at": "2026-07-01T09:00:00Z"
}
}{
"error": {
"code": "invalid_request",
"message": "\"to_number\" is required (E.164 format, e.g. +4930123456)."
}
}{
"error": {
"code": "unauthorized",
"message": "Invalid API key."
}
}{
"error": {
"code": "forbidden",
"message": "This API key is missing the required scope \"assistants:write\"."
}
}{
"error": {
"code": "conflict",
"message": "Campaign is not running."
}
}Create a tool
Create one centrally managed API, MCP, or built-in tool. Requires an admin-level credential because configs may contain secrets. Assign it via PUT /assistants/{id}/tools. Required scope: assistants:write.
curl --request POST \
--url https://app.famulor.io/api/v1/tools \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "check_order_status",
"description": "Look up the status of a customer order by order number.",
"type": "api",
"config": {
"method": "GET",
"url": "https://api.example.com/orders/status",
"headers": {
"Authorization": "Bearer sk_live_..."
},
"parameters": [
{
"name": "order_number",
"type": "string",
"description": "The order number",
"required": true,
"source": "llm",
"location": "query"
}
],
"timeout_ms": 10000,
"async": false,
"filler_phrase": "One moment, I am checking that for you."
}
}
'import requests
url = "https://app.famulor.io/api/v1/tools"
payload = {
"name": "check_order_status",
"description": "Look up the status of a customer order by order number.",
"type": "api",
"config": {
"method": "GET",
"url": "https://api.example.com/orders/status",
"headers": { "Authorization": "Bearer sk_live_..." },
"parameters": [
{
"name": "order_number",
"type": "string",
"description": "The order number",
"required": True,
"source": "llm",
"location": "query"
}
],
"timeout_ms": 10000,
"async": False,
"filler_phrase": "One moment, I am checking that for you."
}
}
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: 'check_order_status',
description: 'Look up the status of a customer order by order number.',
type: 'api',
config: {
method: 'GET',
url: 'https://api.example.com/orders/status',
headers: {Authorization: 'Bearer sk_live_...'},
parameters: [
{
name: 'order_number',
type: 'string',
description: 'The order number',
required: true,
source: 'llm',
location: 'query'
}
],
timeout_ms: 10000,
async: false,
filler_phrase: 'One moment, I am checking that for you.'
}
})
};
fetch('https://app.famulor.io/api/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://app.famulor.io/api/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' => 'check_order_status',
'description' => 'Look up the status of a customer order by order number.',
'type' => 'api',
'config' => [
'method' => 'GET',
'url' => 'https://api.example.com/orders/status',
'headers' => [
'Authorization' => 'Bearer sk_live_...'
],
'parameters' => [
[
'name' => 'order_number',
'type' => 'string',
'description' => 'The order number',
'required' => true,
'source' => 'llm',
'location' => 'query'
]
],
'timeout_ms' => 10000,
'async' => false,
'filler_phrase' => 'One moment, I am checking that for you.'
]
]),
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://app.famulor.io/api/v1/tools"
payload := strings.NewReader("{\n \"name\": \"check_order_status\",\n \"description\": \"Look up the status of a customer order by order number.\",\n \"type\": \"api\",\n \"config\": {\n \"method\": \"GET\",\n \"url\": \"https://api.example.com/orders/status\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_...\"\n },\n \"parameters\": [\n {\n \"name\": \"order_number\",\n \"type\": \"string\",\n \"description\": \"The order number\",\n \"required\": true,\n \"source\": \"llm\",\n \"location\": \"query\"\n }\n ],\n \"timeout_ms\": 10000,\n \"async\": false,\n \"filler_phrase\": \"One moment, I am checking that for you.\"\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://app.famulor.io/api/v1/tools")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"check_order_status\",\n \"description\": \"Look up the status of a customer order by order number.\",\n \"type\": \"api\",\n \"config\": {\n \"method\": \"GET\",\n \"url\": \"https://api.example.com/orders/status\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_...\"\n },\n \"parameters\": [\n {\n \"name\": \"order_number\",\n \"type\": \"string\",\n \"description\": \"The order number\",\n \"required\": true,\n \"source\": \"llm\",\n \"location\": \"query\"\n }\n ],\n \"timeout_ms\": 10000,\n \"async\": false,\n \"filler_phrase\": \"One moment, I am checking that for you.\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.famulor.io/api/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\": \"check_order_status\",\n \"description\": \"Look up the status of a customer order by order number.\",\n \"type\": \"api\",\n \"config\": {\n \"method\": \"GET\",\n \"url\": \"https://api.example.com/orders/status\",\n \"headers\": {\n \"Authorization\": \"Bearer sk_live_...\"\n },\n \"parameters\": [\n {\n \"name\": \"order_number\",\n \"type\": \"string\",\n \"description\": \"The order number\",\n \"required\": true,\n \"source\": \"llm\",\n \"location\": \"query\"\n }\n ],\n \"timeout_ms\": 10000,\n \"async\": false,\n \"filler_phrase\": \"One moment, I am checking that for you.\"\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "f1b2c3d4-0000-4000-8000-000000000010",
"name": "check_order_status",
"description": "Look up the status of a customer order by order number.",
"type": "api",
"config": {
"method": "GET",
"url": "https://api.example.com/orders/status",
"headers": {
"Authorization": "•••"
},
"parameters": [
{
"name": "order_number",
"type": "string",
"description": "The order number",
"required": true,
"source": "llm",
"location": "query"
}
],
"static_values": {},
"response_mapping": {},
"timeout_ms": 10000,
"async": false,
"filler_phrase": "One moment, I am checking that for you."
},
"is_active": true,
"created_at": "2026-07-01T09:00:00Z",
"updated_at": "2026-07-01T09:00:00Z"
}
}{
"error": {
"code": "invalid_request",
"message": "\"to_number\" is required (E.164 format, e.g. +4930123456)."
}
}{
"error": {
"code": "unauthorized",
"message": "Invalid API key."
}
}{
"error": {
"code": "forbidden",
"message": "This API key is missing the required scope \"assistants:write\"."
}
}{
"error": {
"code": "conflict",
"message": "Campaign is not running."
}
}Authorizations
API key (fam_..., created under Settings → API Keys) or an OAuth 2.0 access token (fam_at_...). Keys can be restricted to scopes such as assistants:read, calls:write, campaigns:write, automations:read, dashboards:read, dashboards:write, leads:write, segments:write, loop:read, loop:write, phone_numbers:write, sip_trunks:write, knowledge:write, voices:read, billing:read, settings:write, platform:read, platform:write; a *:write scope implies the matching *:read. Automation and dashboard endpoints also accept the legacy calls:* scope. Keys without scope restrictions have full access.
Body
Create/update payload for a reusable tool. On update, type is immutable and sending ••• for a secret config value keeps the stored value unchanged.
Tool name — becomes the LLM function name.
^[a-zA-Z][a-zA-Z0-9_-]{0,63}$api = HTTP API tool, mcp = external MCP server, builtin = built-in capability (call transfer & co.). Immutable after creation.
api, mcp, builtin Type-specific configuration. api: { method, url, headers, parameters[], static_values, response_mapping, timeout_ms, async, filler_phrase, speak_after }. speak_after (default true): when false, the tool completes silently without an LLM spoken reply. mcp: { url, auth_type, auth_header_name, auth_value_encrypted, allowed_tools[], tool_options: { [toolName]: { cancellable, on_duplicate, report_progress } }, timeout_ms }; on_duplicate is allow, reject, replace, or confirm, and replace requires cancellation. builtin: exactly one built-in tool item { type: call_transfer|warm_call_transfer|end_call|…|collect_payment_card|set_variable, description?, stripe_connection_id? for collect_payment_card, allowed_keys? for set_variable, ... } — same shape as one element of an assistant's builtin_tools array; secrets are masked as ••• in responses.
What the tool does — shown to the LLM as the function description.
1000Whether the tool is active (default true).
Required for PATCH to prevent lost concurrent updates.
x >= 1Response
The created tool (secret values masked).
A reusable tool of the account. Secret values inside config (auth values, header values) are always masked as •••.
Show child attributes
Show child attributes