curl --request POST \
--url https://app.gtm-api.com/orchestration/v4/api/mass-actions/preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Team-SID: <api-key>' \
--data '
{
"target_entity": "<string>",
"scope": {
"kind": "objects",
"object_sids": [
"<string>"
]
},
"plan": {
"steps": [
{
"args": {}
}
]
},
"title": "<string>",
"schedule": {
"interval_seconds_min": 43215,
"interval_seconds_max": 43215
}
}
'import requests
url = "https://app.gtm-api.com/orchestration/v4/api/mass-actions/preview"
payload = {
"target_entity": "<string>",
"scope": {
"kind": "objects",
"object_sids": ["<string>"]
},
"plan": { "steps": [{ "args": {} }] },
"title": "<string>",
"schedule": {
"interval_seconds_min": 43215,
"interval_seconds_max": 43215
}
}
headers = {
"Authorization": "Bearer <token>",
"Team-SID": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
Authorization: 'Bearer <token>',
'Team-SID': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
target_entity: '<string>',
scope: {kind: 'objects', object_sids: ['<string>']},
plan: {steps: [{args: {}}]},
title: '<string>',
schedule: {interval_seconds_min: 43215, interval_seconds_max: 43215}
})
};
fetch('https://app.gtm-api.com/orchestration/v4/api/mass-actions/preview', 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.gtm-api.com/orchestration/v4/api/mass-actions/preview",
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([
'target_entity' => '<string>',
'scope' => [
'kind' => 'objects',
'object_sids' => [
'<string>'
]
],
'plan' => [
'steps' => [
[
'args' => [
]
]
]
],
'title' => '<string>',
'schedule' => [
'interval_seconds_min' => 43215,
'interval_seconds_max' => 43215
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Team-SID: <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://app.gtm-api.com/orchestration/v4/api/mass-actions/preview"
payload := strings.NewReader("{\n \"target_entity\": \"<string>\",\n \"scope\": {\n \"kind\": \"objects\",\n \"object_sids\": [\n \"<string>\"\n ]\n },\n \"plan\": {\n \"steps\": [\n {\n \"args\": {}\n }\n ]\n },\n \"title\": \"<string>\",\n \"schedule\": {\n \"interval_seconds_min\": 43215,\n \"interval_seconds_max\": 43215\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Team-SID", "<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://app.gtm-api.com/orchestration/v4/api/mass-actions/preview")
.header("Authorization", "Bearer <token>")
.header("Team-SID", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"target_entity\": \"<string>\",\n \"scope\": {\n \"kind\": \"objects\",\n \"object_sids\": [\n \"<string>\"\n ]\n },\n \"plan\": {\n \"steps\": [\n {\n \"args\": {}\n }\n ]\n },\n \"title\": \"<string>\",\n \"schedule\": {\n \"interval_seconds_min\": 43215,\n \"interval_seconds_max\": 43215\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.gtm-api.com/orchestration/v4/api/mass-actions/preview")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Team-SID"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"target_entity\": \"<string>\",\n \"scope\": {\n \"kind\": \"objects\",\n \"object_sids\": [\n \"<string>\"\n ]\n },\n \"plan\": {\n \"steps\": [\n {\n \"args\": {}\n }\n ]\n },\n \"title\": \"<string>\",\n \"schedule\": {\n \"interval_seconds_min\": 43215,\n \"interval_seconds_max\": 43215\n }\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"operation": "action",
"action": "<string>",
"item": "<unknown>",
"result": {
"preview": {
"items_count": 123,
"steps_per_item": 123,
"dangerous_steps": [
{
"step_id": 123,
"tool": "<string>"
}
],
"eta": {
"starts": "<string>",
"estimated_completion_at": "<string>"
},
"warnings": [
"<string>"
]
},
"commit_token": "<string>",
"expires_at": "<string>"
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"debug_url": "<string>"
}
}{
"success": false,
"error": {
"code": "validation_failed",
"message": "<string>",
"recoverable": true,
"suggestion": "<string>",
"field_errors": {},
"blockers": [
{
"type": "<string>",
"severity": "hard",
"description": "<string>",
"entity_sid": "<string>",
"resolution": "<string>",
"resolution_hint": "<string>",
"count": 123
}
],
"context": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"debug_url": "<string>"
}
}{
"success": false,
"error": {
"code": "validation_failed",
"message": "<string>",
"recoverable": true,
"suggestion": "<string>",
"field_errors": {},
"blockers": [
{
"type": "<string>",
"severity": "hard",
"description": "<string>",
"entity_sid": "<string>",
"resolution": "<string>",
"resolution_hint": "<string>",
"count": 123
}
],
"context": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"debug_url": "<string>"
}
}Preview mass-action plan
Validate a whole bulk plan without running anything and mint the consent token create_mass_action consumes. ALWAYS the first call of a bulk dispatch.
Validates in one pass, reporting all findings at once: plan shape (1..3 steps), step-eligibility of each tool, scope shape and size (1..100), the generate-scope rule (step 1 must mint an object) and the send-class schedule mandate. A tool outside the step vocabulary comes back 422 validation_failed with error.field_errors[“plan.steps..tool”] = [“not_step_eligible: …”], naming the authorable set so the plan is repairable in one turn. Nothing is persisted, charged or created.
On success the result carries preview (items_count, steps_per_item, dangerous_steps, eta, warnings), commit_token and expires_at. Show the preview to the user, then pass the token to create_mass_action UNCHANGED with the exact same inputs: it is an HMAC over them plus the caller, so any edit invalidates it (422) and needs a fresh preview. Tokens live 15 minutes.
Contract:
- MCP tool
preview_mass_action, registry packagemcp.orchestration/mass_actions, mountorchestration.mass_actions. - Operation
action, response envelopeaction. - Flags: read only.
curl --request POST \
--url https://app.gtm-api.com/orchestration/v4/api/mass-actions/preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Team-SID: <api-key>' \
--data '
{
"target_entity": "<string>",
"scope": {
"kind": "objects",
"object_sids": [
"<string>"
]
},
"plan": {
"steps": [
{
"args": {}
}
]
},
"title": "<string>",
"schedule": {
"interval_seconds_min": 43215,
"interval_seconds_max": 43215
}
}
'import requests
url = "https://app.gtm-api.com/orchestration/v4/api/mass-actions/preview"
payload = {
"target_entity": "<string>",
"scope": {
"kind": "objects",
"object_sids": ["<string>"]
},
"plan": { "steps": [{ "args": {} }] },
"title": "<string>",
"schedule": {
"interval_seconds_min": 43215,
"interval_seconds_max": 43215
}
}
headers = {
"Authorization": "Bearer <token>",
"Team-SID": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
Authorization: 'Bearer <token>',
'Team-SID': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
target_entity: '<string>',
scope: {kind: 'objects', object_sids: ['<string>']},
plan: {steps: [{args: {}}]},
title: '<string>',
schedule: {interval_seconds_min: 43215, interval_seconds_max: 43215}
})
};
fetch('https://app.gtm-api.com/orchestration/v4/api/mass-actions/preview', 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.gtm-api.com/orchestration/v4/api/mass-actions/preview",
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([
'target_entity' => '<string>',
'scope' => [
'kind' => 'objects',
'object_sids' => [
'<string>'
]
],
'plan' => [
'steps' => [
[
'args' => [
]
]
]
],
'title' => '<string>',
'schedule' => [
'interval_seconds_min' => 43215,
'interval_seconds_max' => 43215
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Team-SID: <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://app.gtm-api.com/orchestration/v4/api/mass-actions/preview"
payload := strings.NewReader("{\n \"target_entity\": \"<string>\",\n \"scope\": {\n \"kind\": \"objects\",\n \"object_sids\": [\n \"<string>\"\n ]\n },\n \"plan\": {\n \"steps\": [\n {\n \"args\": {}\n }\n ]\n },\n \"title\": \"<string>\",\n \"schedule\": {\n \"interval_seconds_min\": 43215,\n \"interval_seconds_max\": 43215\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Team-SID", "<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://app.gtm-api.com/orchestration/v4/api/mass-actions/preview")
.header("Authorization", "Bearer <token>")
.header("Team-SID", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"target_entity\": \"<string>\",\n \"scope\": {\n \"kind\": \"objects\",\n \"object_sids\": [\n \"<string>\"\n ]\n },\n \"plan\": {\n \"steps\": [\n {\n \"args\": {}\n }\n ]\n },\n \"title\": \"<string>\",\n \"schedule\": {\n \"interval_seconds_min\": 43215,\n \"interval_seconds_max\": 43215\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.gtm-api.com/orchestration/v4/api/mass-actions/preview")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Team-SID"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"target_entity\": \"<string>\",\n \"scope\": {\n \"kind\": \"objects\",\n \"object_sids\": [\n \"<string>\"\n ]\n },\n \"plan\": {\n \"steps\": [\n {\n \"args\": {}\n }\n ]\n },\n \"title\": \"<string>\",\n \"schedule\": {\n \"interval_seconds_min\": 43215,\n \"interval_seconds_max\": 43215\n }\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"operation": "action",
"action": "<string>",
"item": "<unknown>",
"result": {
"preview": {
"items_count": 123,
"steps_per_item": 123,
"dangerous_steps": [
{
"step_id": 123,
"tool": "<string>"
}
],
"eta": {
"starts": "<string>",
"estimated_completion_at": "<string>"
},
"warnings": [
"<string>"
]
},
"commit_token": "<string>",
"expires_at": "<string>"
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"debug_url": "<string>"
}
}{
"success": false,
"error": {
"code": "validation_failed",
"message": "<string>",
"recoverable": true,
"suggestion": "<string>",
"field_errors": {},
"blockers": [
{
"type": "<string>",
"severity": "hard",
"description": "<string>",
"entity_sid": "<string>",
"resolution": "<string>",
"resolution_hint": "<string>",
"count": 123
}
],
"context": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"debug_url": "<string>"
}
}{
"success": false,
"error": {
"code": "validation_failed",
"message": "<string>",
"recoverable": true,
"suggestion": "<string>",
"field_errors": {},
"blockers": [
{
"type": "<string>",
"severity": "hard",
"description": "<string>",
"entity_sid": "<string>",
"resolution": "<string>",
"resolution_hint": "<string>",
"count": 123
}
],
"context": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"debug_url": "<string>"
}
}Authorizations
Access token issued by gtm.service.id. Its access_identity claim carries team_sid, actor_sid and actor_type, and that team scope is authoritative.
Team scope for tokens that do not carry one. Ignored when the token already names a team.
Body
Request body of preview_mass_action.
Kebab-plural entity family the plan's steps operate on, e.g. 'linkedin-connection-requests', 'linkedin-posting' or 'email-messages'. The compatibility anchor a linked auto-scrape checks against.
128What the run enrols: existing rows (objects), payload identities (targets), generated slots (generate), or nothing yet (none, a standing run an auto-scrape feeds).
- Option 1
- Option 2
- Option 3
- Option 4
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Human label for the run, shown in the list view and on the consent surface.
255Omit for an ASAP drain. Required when the plan carries a send-class step, else 422 schedule_required.
Show child attributes
Show child attributes
Default 'first_item': only item 1 dispatches until it succeeds, so a wrong plan burns one target instead of all of them.
none, first_item Response
action success envelope.