Create or Update API Tool
curl --request POST \
--url https://app.tuco.ai/api/campaign-tools \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"id": "<string>",
"name": "<string>",
"url": "<string>",
"responseMapping": "<string>",
"method": "<string>",
"description": "<string>",
"headers": {},
"body": "<string>"
}
'import requests
url = "https://app.tuco.ai/api/campaign-tools"
payload = {
"id": "<string>",
"name": "<string>",
"url": "<string>",
"responseMapping": "<string>",
"method": "<string>",
"description": "<string>",
"headers": {},
"body": "<string>"
}
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({
id: '<string>',
name: '<string>',
url: '<string>',
responseMapping: '<string>',
method: '<string>',
description: '<string>',
headers: {},
body: JSON.stringify('<string>')
})
};
fetch('https://app.tuco.ai/api/campaign-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.tuco.ai/api/campaign-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([
'id' => '<string>',
'name' => '<string>',
'url' => '<string>',
'responseMapping' => '<string>',
'method' => '<string>',
'description' => '<string>',
'headers' => [
],
'body' => '<string>'
]),
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.tuco.ai/api/campaign-tools"
payload := strings.NewReader("{\n \"id\": \"<string>\",\n \"name\": \"<string>\",\n \"url\": \"<string>\",\n \"responseMapping\": \"<string>\",\n \"method\": \"<string>\",\n \"description\": \"<string>\",\n \"headers\": {},\n \"body\": \"<string>\"\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.tuco.ai/api/campaign-tools")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"id\": \"<string>\",\n \"name\": \"<string>\",\n \"url\": \"<string>\",\n \"responseMapping\": \"<string>\",\n \"method\": \"<string>\",\n \"description\": \"<string>\",\n \"headers\": {},\n \"body\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/campaign-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 \"id\": \"<string>\",\n \"name\": \"<string>\",\n \"url\": \"<string>\",\n \"responseMapping\": \"<string>\",\n \"method\": \"<string>\",\n \"description\": \"<string>\",\n \"headers\": {},\n \"body\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyAPI Tools
Create or Update API Tool
Create or update a custom API tool in Tuco AI — define name, HTTP method, URL, headers, and JSON schema so goal bots and AI campaigns can call your endpoint.
POST
/
api
/
campaign-tools
Create or Update API Tool
curl --request POST \
--url https://app.tuco.ai/api/campaign-tools \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"id": "<string>",
"name": "<string>",
"url": "<string>",
"responseMapping": "<string>",
"method": "<string>",
"description": "<string>",
"headers": {},
"body": "<string>"
}
'import requests
url = "https://app.tuco.ai/api/campaign-tools"
payload = {
"id": "<string>",
"name": "<string>",
"url": "<string>",
"responseMapping": "<string>",
"method": "<string>",
"description": "<string>",
"headers": {},
"body": "<string>"
}
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({
id: '<string>',
name: '<string>',
url: '<string>',
responseMapping: '<string>',
method: '<string>',
description: '<string>',
headers: {},
body: JSON.stringify('<string>')
})
};
fetch('https://app.tuco.ai/api/campaign-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.tuco.ai/api/campaign-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([
'id' => '<string>',
'name' => '<string>',
'url' => '<string>',
'responseMapping' => '<string>',
'method' => '<string>',
'description' => '<string>',
'headers' => [
],
'body' => '<string>'
]),
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.tuco.ai/api/campaign-tools"
payload := strings.NewReader("{\n \"id\": \"<string>\",\n \"name\": \"<string>\",\n \"url\": \"<string>\",\n \"responseMapping\": \"<string>\",\n \"method\": \"<string>\",\n \"description\": \"<string>\",\n \"headers\": {},\n \"body\": \"<string>\"\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.tuco.ai/api/campaign-tools")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"id\": \"<string>\",\n \"name\": \"<string>\",\n \"url\": \"<string>\",\n \"responseMapping\": \"<string>\",\n \"method\": \"<string>\",\n \"description\": \"<string>\",\n \"headers\": {},\n \"body\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/campaign-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 \"id\": \"<string>\",\n \"name\": \"<string>\",\n \"url\": \"<string>\",\n \"responseMapping\": \"<string>\",\n \"method\": \"<string>\",\n \"description\": \"<string>\",\n \"headers\": {},\n \"body\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyAn API tool lets a goal bot call your endpoint mid-conversation — check stock, look up an
order, fetch a balance — and use the answer in its reply. Attach a tool to a bot by putting its
_id in that bot’s allowedToolIds.id to create; pass id to update that tool in place.
Authentication
Pass your workspace API key as a Bearer token, or use a Clerk session token.Authorization: Bearer tuco_xxxxxxxxxxxxx
Request body
string
Omit to create a new tool. Pass an existing tool
_id to update it — every other field is
overwritten with what you send, so send the complete tool.string
required
Tool name. Also derives
variableKey (the variable a bot reads the result from), so keep it
short and snake_case — check_stock becomes api_check_stock. A blank name returns
400 Tool name is required.string
required
The endpoint Tuco calls. Must be a reachable public URL — private, local and blocked hosts
return
400 Tool URL is invalid or blocked.string
required
Dot path into your response that becomes the tool’s value, e.g.
data.quantity. Blank returns
400 Response mapping is required.string
default:"GET"
One of
GET, POST, PUT, PATCH, DELETE. Any other value silently falls back to GET —
read method back from the response to confirm.string
What the tool does. The model reads this when deciding whether to call it, so write it for the
model, not for a human changelog.
object
Flat object of header name → value, e.g.
{ "Authorization": "Bearer abc123" }. Non-string
values and blank names are dropped.string
Request body template sent with
POST/PUT/PATCH. A string, not an object. Lead fields
can be interpolated into it.Example — create
curl -X POST "https://app.tuco.ai/api/campaign-tools" \
-H "Authorization: Bearer tuco_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "check_stock",
"method": "GET",
"url": "https://api.example.com/stock",
"responseMapping": "data.quantity",
"description": "Returns current stock for a SKU"
}'
Example — update
curl -X POST "https://app.tuco.ai/api/campaign-tools" \
-H "Authorization: Bearer tuco_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"id": "6ac3ed9271adb32c88d566e0",
"name": "check_stock",
"method": "GET",
"url": "https://api.example.com/v2/stock",
"responseMapping": "data.quantity"
}'
Success (200 OK)
{
"tool": {
"_id": "6ac3ed9271adb32c88d566e0",
"workspaceId": "org_3CzFir0Fps4hHL4nbU9WQ6FR4ur",
"name": "check_stock",
"description": "Returns current stock for a SKU",
"method": "GET",
"url": "https://api.example.com/stock",
"responseMapping": "data.quantity",
"variableKey": "api_check_stock",
"createdAt": "2026-10-05T18:33:54.781Z",
"updatedAt": "2026-10-05T18:33:54.781Z"
}
}
Run it once with
POST /api/campaign-tools/test before you
attach it to a bot — that returns the live response and the mapped value.Errors
| Status | When | Body |
|---|---|---|
400 | name blank | { "error": "Tool name is required" } |
400 | url blank | { "error": "Tool URL is required" } |
400 | url not reachable or on a blocked host | { "error": "Tool URL is invalid or blocked" } |
400 | responseMapping blank | { "error": "Response mapping is required" } |
400 | id supplied but malformed | { "error": "Invalid tool id" } |
401 | Missing or invalid API key | { "error": "Unauthorized" } |
404 | id supplied but no such tool in your workspace | { "error": "Tool not found" } |