curl --request PATCH \
--url https://app.famulor.io/api/v1/tools/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"description": "Look up an order by its number.",
"is_active": false,
"expected_revision": 4
}
'import requests
url = "https://app.famulor.io/api/v1/tools/{id}"
payload = {
"description": "Look up an order by its number.",
"is_active": False,
"expected_revision": 4
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
description: 'Look up an order by its number.',
is_active: false,
expected_revision: 4
})
};
fetch('https://app.famulor.io/api/v1/tools/{id}', 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/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'description' => 'Look up an order by its number.',
'is_active' => false,
'expected_revision' => 4
]),
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/{id}"
payload := strings.NewReader("{\n \"description\": \"Look up an order by its number.\",\n \"is_active\": false,\n \"expected_revision\": 4\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://app.famulor.io/api/v1/tools/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"description\": \"Look up an order by its number.\",\n \"is_active\": false,\n \"expected_revision\": 4\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.famulor.io/api/v1/tools/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"description\": \"Look up an order by its number.\",\n \"is_active\": false,\n \"expected_revision\": 4\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"
}
}Update a tool
Revision-protected partial update. type is immutable. Sending ••• for a secret keeps the stored value. A provided config replaces the stored configuration: send back every header from the read response (masked values as •••); omitted headers are removed. A stale revision returns 409 revision_conflict. Required scope: assistants:write.
curl --request PATCH \
--url https://app.famulor.io/api/v1/tools/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"description": "Look up an order by its number.",
"is_active": false,
"expected_revision": 4
}
'import requests
url = "https://app.famulor.io/api/v1/tools/{id}"
payload = {
"description": "Look up an order by its number.",
"is_active": False,
"expected_revision": 4
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
description: 'Look up an order by its number.',
is_active: false,
expected_revision: 4
})
};
fetch('https://app.famulor.io/api/v1/tools/{id}', 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/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'description' => 'Look up an order by its number.',
'is_active' => false,
'expected_revision' => 4
]),
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/{id}"
payload := strings.NewReader("{\n \"description\": \"Look up an order by its number.\",\n \"is_active\": false,\n \"expected_revision\": 4\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://app.famulor.io/api/v1/tools/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"description\": \"Look up an order by its number.\",\n \"is_active\": false,\n \"expected_revision\": 4\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.famulor.io/api/v1/tools/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"description\": \"Look up an order by its number.\",\n \"is_active\": false,\n \"expected_revision\": 4\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"
}
}Authorizations
API key (fam_..., created under Settings → API Keys) or an OAuth 2.0 access token (fam_at_...). REST operations also require API Access for the credential's workspace. 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, billing:write, 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 within the workspace's available capabilities.
Path Parameters
Tool ID.
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.
Required for PATCH to prevent lost concurrent updates.
x >= 1Tool name — becomes the LLM function name.
^[a-zA-Z][a-zA-Z0-9_-]{0,63}$What the tool does — shown to the LLM as the function description.
1000api = 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. For warm_call_transfer, optional post_answer_dtmf accepts up to 32 characters (0-9*#A-D and w, case-insensitive); each w is a fixed half-second pause after answer and before the briefing. Omit or clear it for a direct destination. See BuiltinTool for field constraints. Saved caller-facing announcements, filler phrases and end-call farewells remain editable. With secondary languages configured on the assistant, they guide generated wording in the current supported conversation language without fixed saved audio. Otherwise their existing delivery behavior is preserved. An empty End call farewell adds no speech.
Whether the tool is active (default true).
Response
The updated 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