curl --request DELETE \
--url https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId} \
--header 'Authorization: Bearer YOUR_API_KEY'import requests
url = "https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}"
headers = {"Authorization": "Bearer <token>"}
response = requests.delete(url, headers=headers)
print(response.text)const options = {method: 'DELETE', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}', 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.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.delete("https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"success": true,
"workflow": {
"locationId": "loc_abc123",
"campaignId": "cmp_123",
"campaignConfig": {
"callbackMode": "ai"
},
"nodes": {
"tag_booked": {
"id": "tag_booked",
"type": "add-tag",
"config": {
"tags": [
"booked_via_ai"
]
}
},
"sms_reminder": {
"id": "sms_reminder",
"type": "send-sms",
"config": {
"message": "Thanks for booking! See you soon."
}
}
},
"edges": [
{
"id": "e_booked",
"source": "tag_booked",
"target": "sms_reminder"
}
],
"dispositionEntries": {
"SUCCESS": "tag_booked"
},
"version": 3,
"createdAt": "2026-07-01T09:15:22.000Z",
"updatedAt": "2026-07-14T16:04:11.000Z"
}
}{
"success": false,
"error": "VALIDATION",
"message": "Workflow graph must have a nodes object."
}{
"success": false,
"error": "Unauthorized",
"message": "Authentication required."
}{
"success": false,
"error": "FORBIDDEN",
"message": "You do not have access to this project."
}{
"success": false,
"error": "NOT_FOUND",
"message": "Campaign not found on this project."
}{
"success": false,
"error": "CONFLICT",
"message": "The workflow changed since it was loaded. Re-read and retry."
}{
"success": false,
"error": "RateLimited",
"message": "Rate limit exceeded. Retry after a short delay."
}Remove a workflow node
The normal way to delete a step. Remove one action node from its outcome chain and re-link around it — the node before it is joined to the node after it, so the chain survives. That re-linking is why deleting a node by omitting it from a PUT is not the same operation. Branch (if) and fan-out nodes must be removed in the canvas.
Required API key scope: campaign_workflows:write.
curl --request DELETE \
--url https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId} \
--header 'Authorization: Bearer YOUR_API_KEY'import requests
url = "https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}"
headers = {"Authorization": "Bearer <token>"}
response = requests.delete(url, headers=headers)
print(response.text)const options = {method: 'DELETE', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}', 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.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.delete("https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.teamfollowup.ai/api/agent-builder/agents/{id}/campaign/workflow/nodes/{nodeId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"success": true,
"workflow": {
"locationId": "loc_abc123",
"campaignId": "cmp_123",
"campaignConfig": {
"callbackMode": "ai"
},
"nodes": {
"tag_booked": {
"id": "tag_booked",
"type": "add-tag",
"config": {
"tags": [
"booked_via_ai"
]
}
},
"sms_reminder": {
"id": "sms_reminder",
"type": "send-sms",
"config": {
"message": "Thanks for booking! See you soon."
}
}
},
"edges": [
{
"id": "e_booked",
"source": "tag_booked",
"target": "sms_reminder"
}
],
"dispositionEntries": {
"SUCCESS": "tag_booked"
},
"version": 3,
"createdAt": "2026-07-01T09:15:22.000Z",
"updatedAt": "2026-07-14T16:04:11.000Z"
}
}{
"success": false,
"error": "VALIDATION",
"message": "Workflow graph must have a nodes object."
}{
"success": false,
"error": "Unauthorized",
"message": "Authentication required."
}{
"success": false,
"error": "FORBIDDEN",
"message": "You do not have access to this project."
}{
"success": false,
"error": "NOT_FOUND",
"message": "Campaign not found on this project."
}{
"success": false,
"error": "CONFLICT",
"message": "The workflow changed since it was loaded. Re-read and retry."
}{
"success": false,
"error": "RateLimited",
"message": "Rate limit exceeded. Retry after a short delay."
}Authorizations
Send your API key as Authorization: Bearer YOUR_API_KEY.
Path Parameters
Agent id.
1Id of the node, as it appears in the nodes map of a workflow read.
1Query Parameters
Pair with campaignId; omit both for primary campaign.
1Pair with locationId; omit both for primary campaign.
1Response
The updated workflow.
true
Show child attributes
Show child attributes
Everything about the graph that is worth knowing and was not worth refusing. Empty on a clean graph. Read this after a write: a 200 means the graph was stored, not that every node will do something.
Show child attributes
Show child attributes
