curl --request POST \
--url https://api.example.com/api/public/tools/add_dynamic_action \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"action": {
"type": "email",
"prompt": "<string>",
"assignedUserId": "<string>",
"subject": "<string>",
"isHumanApprovalRequired": true,
"recipientEmail": "<string>",
"delay": "<string>"
},
"contact": null,
"company": null,
"enrollmentId": null,
"ownerId": null,
"priority": null,
"sourceDescription": null,
"allowUnreadyLinkedInAccount": null
}
'import requests
url = "https://api.example.com/api/public/tools/add_dynamic_action"
payload = {
"action": {
"type": "email",
"prompt": "<string>",
"assignedUserId": "<string>",
"subject": "<string>",
"isHumanApprovalRequired": True,
"recipientEmail": "<string>",
"delay": "<string>"
},
"contact": None,
"company": None,
"enrollmentId": None,
"ownerId": None,
"priority": None,
"sourceDescription": None,
"allowUnreadyLinkedInAccount": None
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
action: {
type: 'email',
prompt: '<string>',
assignedUserId: '<string>',
subject: '<string>',
isHumanApprovalRequired: true,
recipientEmail: '<string>',
delay: '<string>'
},
contact: null,
company: null,
enrollmentId: null,
ownerId: null,
priority: null,
sourceDescription: null,
allowUnreadyLinkedInAccount: null
})
};
fetch('https://api.example.com/api/public/tools/add_dynamic_action', 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.example.com/api/public/tools/add_dynamic_action",
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([
'action' => [
'type' => 'email',
'prompt' => '<string>',
'assignedUserId' => '<string>',
'subject' => '<string>',
'isHumanApprovalRequired' => true,
'recipientEmail' => '<string>',
'delay' => '<string>'
],
'contact' => null,
'company' => null,
'enrollmentId' => null,
'ownerId' => null,
'priority' => null,
'sourceDescription' => null,
'allowUnreadyLinkedInAccount' => null
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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.example.com/api/public/tools/add_dynamic_action"
payload := strings.NewReader("{\n \"action\": {\n \"type\": \"email\",\n \"prompt\": \"<string>\",\n \"assignedUserId\": \"<string>\",\n \"subject\": \"<string>\",\n \"isHumanApprovalRequired\": true,\n \"recipientEmail\": \"<string>\",\n \"delay\": \"<string>\"\n },\n \"contact\": null,\n \"company\": null,\n \"enrollmentId\": null,\n \"ownerId\": null,\n \"priority\": null,\n \"sourceDescription\": null,\n \"allowUnreadyLinkedInAccount\": null\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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.example.com/api/public/tools/add_dynamic_action")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"action\": {\n \"type\": \"email\",\n \"prompt\": \"<string>\",\n \"assignedUserId\": \"<string>\",\n \"subject\": \"<string>\",\n \"isHumanApprovalRequired\": true,\n \"recipientEmail\": \"<string>\",\n \"delay\": \"<string>\"\n },\n \"contact\": null,\n \"company\": null,\n \"enrollmentId\": null,\n \"ownerId\": null,\n \"priority\": null,\n \"sourceDescription\": null,\n \"allowUnreadyLinkedInAccount\": null\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/public/tools/add_dynamic_action")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"action\": {\n \"type\": \"email\",\n \"prompt\": \"<string>\",\n \"assignedUserId\": \"<string>\",\n \"subject\": \"<string>\",\n \"isHumanApprovalRequired\": true,\n \"recipientEmail\": \"<string>\",\n \"delay\": \"<string>\"\n },\n \"contact\": null,\n \"company\": null,\n \"enrollmentId\": null,\n \"ownerId\": null,\n \"priority\": null,\n \"sourceDescription\": null,\n \"allowUnreadyLinkedInAccount\": null\n}"
response = http.request(request)
puts response.read_body{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}Add Dynamic Action
Create or extend one MCP Dynamic Actions enrollment for exactly one contact.
Required preflight
- Call get_guide with topic=dynamic_actions before this tool.
- Before calling this tool, call find_contact_data with every known identity unless verified enrichment was returned earlier in the conversation.
- Then call find_mcp_enrollment with the known identifiers; if candidates are returned, ask which enrollment to continue or whether to start a new sequence, and if none is returned, continue with the requested new action.
- Resolve action.assignedUserId with get_current_user and list_team_members before calling this tool.
- When content uses variables, use only fully qualified templates such as ; do not use aliases such as .
Workflow and write receipt
- This tool adds one action step per call and returns flowPlanId, enrollmentId, nodeId, assignedUserId, approval/task flags, taskIds when task rows are available, taskIdsPending/taskMaterializationStatus for async task materialization, uiLinks, and nextSteps.
- Treat enrollmentId + nodeId as the write receipt; if nodeAppended=true, do not retry add_dynamic_action for the same requested step just because taskIds is empty or get_flow_enrollment has not materialized actions/tasks yet.
Contact identity
- Every appended action requires contact.firstName and contact.lastName in its effective identity.
- For a new sequence, provide both names in contact plus contact.email, contact.linkedInUrl, contact.phone, or company.domain; contact.prospectId is also supported when it resolves to a full name.
- When continuing a selected enrollment that lacks either name, provide both names in contact to backfill it.
- Ask the customer for missing names and do not invoke an enrichment tool automatically.
- Use the optional contact and company objects for verified context returned by find_contact_data or explicit enrichment; their fields are all optional.
- Populate every other verified known field.
Content and email behavior
- This tool does not enrich request inputs or generate content; pass final email, LinkedIn send, call, or task text written by the MCP client.
- linkedin_profile_view has no content and only supports delay, appendTarget, and assignedUserId.
- MCP Dynamic Actions do not use AI to generate email subjects.
- For a new email enrollment, omit action.subjectType: it defaults to new_thread and action.subject is required.
- For an existing enrollment, action.subjectType is required: choose new_thread and provide action.subject for a separate conversation, or choose reply only when the selected append branch has a prior email;
reply inherits that subject and must omit action.subject. - Use find_mcp_enrollment candidate.root to trace the requested append branch.
- Use get_flow_enrollment after selecting an enrollment only when complete enrollment/action/task details are needed.
Existing enrollment and priority
- To continue an existing MCP Dynamic Actions enrollment, pass enrollmentId;
existing enrollment identity wins, and supplied contact.email, contact.linkedInUrl, contact.phone, or contact.prospectId can only backfill missing values, not retarget the enrollment. - Other contact and company context only fills missing enrollment properties and never overwrites an existing value.
- Set whole-enrollment scheduling priority only when the customer explicitly asks: use 1 for High, 0 or omit for normal, and -1 for Low.
- Priority on an existing enrollment updates it.
Sender eligibility
- Resolve and pass action.assignedUserId before every call: use the current MCP user when no sender is requested, a named member when requested, or one eligible team member when the customer asks for any capable sender.
- Email requires Email actions permission and a connected email account.
- LinkedIn connect/message/profile requires Social permission plus a live synced LinkedIn account; InMail additionally requires an eligible Premium or Sales Navigator plan.
- A linkedin_connect requires the selected sender not to be a saved 1st-degree connection of the contact; if already connected, use linkedin_message instead.
- A linkedin_message outside a connection_accepted branch requires the selected sender to be a saved 1st-degree connection; when another team member is connected instead, the validation error identifies that member for reassignment.
- When the selected sender is not connected, use linkedin_connect followed by a message on connection_accepted, or use linkedin_inmail if the sender has an eligible Premium or Sales Navigator plan.
- Call and manual tasks require their matching permissions.
- If the selected user lacks a required capability, this tool rejects the action instead of silently assigning another user.
Action requirements and scheduling
- Pass exactly one action with type email, linkedin_connect, linkedin_message, linkedin_inmail, linkedin_profile_view, manual_task, or call_task.
- Each action type exposes only its supported fields.
- Email requires action.prompt and an effective recipient email.
- LinkedIn messages require action.message and an effective linkedInUrl.
- linkedin_inmail requires non-empty action.prompt and action.subject, and does not accept action.message.
- linkedin_profile_view supports one action per default, connection accepted, or connection timeout branch and cannot have content or human approval.
- Manual tasks require action.name.
- Call tasks require action.message and an effective phone; action.name is not a call script.
- Email, LinkedIn send actions, InMail, and call actions default to no human approval; set action.isHumanApprovalRequired=true only when the customer explicitly asks for review/approval.
- Omit action.delay or use 00:00:00 for no delay/immediate execution.
- For a delay, use d.hh:mm:ss or hh:mm:ss, for example 5.00:00:00 for 5 days; do not use natural language such as ‘5 days’.
- Use action.appendTarget only for LinkedIn connection accepted/timeout branches.
- For linkedin_connect, action.connectionTimeout optionally sets the acceptance window using a positive d.hh:mm:ss or hh:mm:ss TimeSpan; omit it for the 14-day default.
Shared Flow Plan settings
- This tool has no per-action HubSpot enrichment or task-sync flags: those are shared MCP Flow Plan settings configured only with update_flow_plan as mcpDynamicActionsOptions.is_enrich_hubspot and mcpDynamicActionsOptions.is_push_to_hubspot_tasks.
- The task setting updates call, manual, and approval task sync together.
- The returned flowPlanId is read-only for its name, flow structure, and other normal mutations; do not use it with flow-root, publication, audience, or enrollment mutation tools.
curl --request POST \
--url https://api.example.com/api/public/tools/add_dynamic_action \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"action": {
"type": "email",
"prompt": "<string>",
"assignedUserId": "<string>",
"subject": "<string>",
"isHumanApprovalRequired": true,
"recipientEmail": "<string>",
"delay": "<string>"
},
"contact": null,
"company": null,
"enrollmentId": null,
"ownerId": null,
"priority": null,
"sourceDescription": null,
"allowUnreadyLinkedInAccount": null
}
'import requests
url = "https://api.example.com/api/public/tools/add_dynamic_action"
payload = {
"action": {
"type": "email",
"prompt": "<string>",
"assignedUserId": "<string>",
"subject": "<string>",
"isHumanApprovalRequired": True,
"recipientEmail": "<string>",
"delay": "<string>"
},
"contact": None,
"company": None,
"enrollmentId": None,
"ownerId": None,
"priority": None,
"sourceDescription": None,
"allowUnreadyLinkedInAccount": None
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
action: {
type: 'email',
prompt: '<string>',
assignedUserId: '<string>',
subject: '<string>',
isHumanApprovalRequired: true,
recipientEmail: '<string>',
delay: '<string>'
},
contact: null,
company: null,
enrollmentId: null,
ownerId: null,
priority: null,
sourceDescription: null,
allowUnreadyLinkedInAccount: null
})
};
fetch('https://api.example.com/api/public/tools/add_dynamic_action', 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.example.com/api/public/tools/add_dynamic_action",
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([
'action' => [
'type' => 'email',
'prompt' => '<string>',
'assignedUserId' => '<string>',
'subject' => '<string>',
'isHumanApprovalRequired' => true,
'recipientEmail' => '<string>',
'delay' => '<string>'
],
'contact' => null,
'company' => null,
'enrollmentId' => null,
'ownerId' => null,
'priority' => null,
'sourceDescription' => null,
'allowUnreadyLinkedInAccount' => null
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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.example.com/api/public/tools/add_dynamic_action"
payload := strings.NewReader("{\n \"action\": {\n \"type\": \"email\",\n \"prompt\": \"<string>\",\n \"assignedUserId\": \"<string>\",\n \"subject\": \"<string>\",\n \"isHumanApprovalRequired\": true,\n \"recipientEmail\": \"<string>\",\n \"delay\": \"<string>\"\n },\n \"contact\": null,\n \"company\": null,\n \"enrollmentId\": null,\n \"ownerId\": null,\n \"priority\": null,\n \"sourceDescription\": null,\n \"allowUnreadyLinkedInAccount\": null\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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.example.com/api/public/tools/add_dynamic_action")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"action\": {\n \"type\": \"email\",\n \"prompt\": \"<string>\",\n \"assignedUserId\": \"<string>\",\n \"subject\": \"<string>\",\n \"isHumanApprovalRequired\": true,\n \"recipientEmail\": \"<string>\",\n \"delay\": \"<string>\"\n },\n \"contact\": null,\n \"company\": null,\n \"enrollmentId\": null,\n \"ownerId\": null,\n \"priority\": null,\n \"sourceDescription\": null,\n \"allowUnreadyLinkedInAccount\": null\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/public/tools/add_dynamic_action")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"action\": {\n \"type\": \"email\",\n \"prompt\": \"<string>\",\n \"assignedUserId\": \"<string>\",\n \"subject\": \"<string>\",\n \"isHumanApprovalRequired\": true,\n \"recipientEmail\": \"<string>\",\n \"delay\": \"<string>\"\n },\n \"contact\": null,\n \"company\": null,\n \"enrollmentId\": null,\n \"ownerId\": null,\n \"priority\": null,\n \"sourceDescription\": null,\n \"allowUnreadyLinkedInAccount\": null\n}"
response = http.request(request)
puts response.read_body{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}{
"error": "<string>",
"message": "<string>",
"traceId": "<string>"
}Authorizations
Body
Action to add. Select exactly one typed variant. email requires prompt; linkedin_message and call_task require message; linkedin_inmail requires prompt and subject; manual_task requires name. Every variant requires assignedUserId.
- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
- Option 6
- Option 7
Show child attributes
Show child attributes
Optional verified contact context, normally forwarded from find_contact_data or a customer-requested enrichment result in this conversation.
- All fields are optional.
- On an existing enrollment, prospectId, email, linkedInUrl, and phone may only fill blanks and reject conflicts; firstName, lastName, role, and location only fill missing properties.
- Do not use this tool to enrich missing data.
Show child attributes
Show child attributes
Optional verified company context related to contact.
- All fields are optional.
- It is stored as enrollment context only and does not prove that the contact works for this company.
- On an existing enrollment, every company field only fills a missing property and never overwrites an existing value.
Show child attributes
Show child attributes
Optional existing MCP Dynamic Actions enrollment/activity id to extend.
- Use the exact enrollmentId returned by find_mcp_enrollment for the customer-selected candidate.
- If the selected enrollment lacks firstName or lastName, also pass both names in contact before appending an action.
- If find_mcp_enrollment returned any candidates, do not choose this value or omit it until the customer explicitly decides whether to continue one of those enrollments or start a new sequence.
- Omit when no MCP enrollment candidate was found to create a new enrollment; when the original request already specifies the action and all required identity data, do this without asking the customer to confirm the same action again.
Optional default owner user id for the dynamic enrollment.
- Use the exact current userId from get_current_user or a userId returned by list_team_members.
- Omit to keep the existing owner when extending an enrollment, or use the current MCP user for a new enrollment.
Optional scheduling priority for the whole dynamic enrollment.
- Use 1 for High, 0 or omit for normal, and -1 for Low.
- Higher values are preferred first.
- Omit for priority 0 on a new enrollment or to preserve priority when extending an existing enrollment.
- When supplied with enrollmentId, this updates the selected enrollment priority.
Optional UI context for the dynamic enrollment, limited to 500 characters.
- When useful context is known, write one to three concise sentences describing how this contact came to the sender and their relevant relationship; do not invent facts or merely repeat the action.
- Omit when no useful context is known, which preserves the existing description when extending an enrollment.
- Supplying a value for an existing enrollment replaces its prior sourceDescription.
Allow creating a LinkedIn action, or appending a follow-up to a LinkedIn connection outcome branch, even when the assigned team member's LinkedIn account is missing, disconnected, not alive, relations sync is incomplete, or the existing connection action is failed/canceled due account readiness.
- Use only after the customer explicitly asks to create the action despite LinkedIn account readiness problems.
- Social actions permission and contact.linkedInUrl are still required for LinkedIn actions.
- For InMail, an already connected account must still have an eligible Premium or Sales Navigator plan; this flag only permits authoring before account readiness is available.
Response
Tool result.

