Skip to main content
A mass action is a bulk dispatch on the Orchestration service: up to 100 items, each run through a plan of 1 to 3 steps, paced by a randomized schedule. The API forces a two-phase flow so nothing bulk ever fires from a single call: first a preview that validates everything and mints a consent token, then a commit that consumes it.
Give this to your AI agent and keep the go-ahead for yourself:“On gtm-api (MCP connector at https://mcp.gtm-api.com/mcp, or REST at app.gtm-api.com with my key): build a mass-action plan for the targets I give you, run the preview, and show me the findings and the consent summary. Wait for my explicit approval before committing. After the commit, monitor the run and report per-item outcomes when it settles.”

1. Preview the plan

POST /api/mass-actions/preview validates the whole plan without persisting, charging or sending anything, and reports every finding at once.
POST /orchestration/v4/api/mass-actions/preview
What the validator enforces:
  • Step vocabulary. Only step-eligible verbs can appear in plan.steps[].tool. Anything else fails with validation_failed and a field_errors entry naming the authorable set, so the plan is repairable in one pass.
  • One sender per run. A send-class step names the account it sends from in args.linkedin_account_sid. A target’s payload carries the target (ln_id, the profile URN) and its per-send extras (note, client_reference), never the sender: a plan that leaves the sender out of args fails preview with sender_required.
  • Scope shape. objects (existing rows by sid), targets (per-item payload identities), generate (N slots, step 1 must create the object), or none (a standing run an auto-scrape feeds later). All bounded at 100 items.
  • A target is one person. Name them by ln_id (the profile URN, ACoAA...) or sn_id (the Sales Navigator id, ACwAA...); profile_id takes either. ln_member_id is the decoded member number (digits only) and cannot be dispatched by a send verb on its own; a vanity slug goes in nickname. A slug in ln_member_id, an id that does not decode, or a Recruiter id where a member verb wants a profile URN fails the preview on that target (scope.targets.3.ln_member_id, ln_member_id_not_numeric), and the same person named twice (their URN and their Sales Navigator id) counts once.
  • Schedule mandate. A plan with a send-class step must carry a schedule; the per-gap interval is randomized between your min and max, because a fixed cadence is itself a detectable pattern. Omit schedule only for plans that can drain immediately.
On success the action envelope returns a preview block (items_count, dangerous_steps, eta, warnings) plus commit_token and expires_at.

2. Commit it

POST /api/mass-actions takes the exact same inputs plus the commit_token. The token is an HMAC over those inputs and the caller: edit anything, and the commit fails with validation_failed; wait past 15 minutes, and it expires. Re-preview in either case. Two behaviors to design around:
  • The run is always asynchronous, even for one item. The returned sid is your monitoring handle.
  • A still-valid token can be replayed, and a replay creates a second identical run. Discard the token the moment the commit succeeds.
With canary_mode: "first_item", only item 1 dispatches until it succeeds. A canary failure pauses the whole run with paused_reason: canary_failed, so a broken template costs you one send, not a hundred.

3. Monitor to settlement

Runs are visible in the app whoever authored them: yourself, an API key, an agent over MCP, or the platform. Mass Actions lists them with their pacing, live counts and state.
Mass Actions list showing runs with item counts, pacing intervals, states and the actor that created each

The Mass Actions list: one row per run, with items done against total, the randomized pacing window, the derived state and who authored it.

Opening a run shows the plan it plays and every item’s outcome, which is the fastest way to tell a broken template from a transient failure.
Mass action run detail showing a two-step plan and a per-item table with succeeded and failed rows, error text and try counts

A run: the read-only step plan on top, then per-item rows with status, error and attempt count.

Over the API:
  • GET /api/mass-actions/{sid}?include[]=metrics returns the run with per-status item counts.
  • POST /api/mass-action-items/search with filter: { "mass_action_sid": { "eq": "..." } } lists the individual items and their step logs.
  • Or skip polling: subscribe a webhook to the mass-actions.settled and mass-actions.paused events.
Control verbs while it runs: POST /api/mass-actions/{sid}/pause and /resume, and /release for a standing run.

4. Retry failures

POST /api/mass-action-items/retry re-enters failed items at their current step. Completed steps are never re-executed, so there are no duplicate creates and no double sends. Target exactly one of a single item sid or a filter (pass mass_action_sid in the filter unless you mean every run). Only status: failed rows match, and a retry matching nothing returns retried_count: 0, not an error. Two guards hold whatever you pass. An item is retried at most 10 times, and an item of a deleted run, or a cancelled one, is never retried. By sid these answer 422 retry_limit_exceeded and 409 cancelled_cannot_retry. A filter leaves such items alone and reports the first kind in left_at_retry_limit_count. A send is not sent again on a guess. Each send of a run (a message, a connection request, a comment) goes out under a key of its own (Sending once explains the keys), and a send whose answer was lost waits with wait_reason: send_outcome_unknown while the run asks the owning service what came of it. It goes again only when the answer is not_sent. If nobody can tell within 24 hours, the item fails with send_unconfirmed: in its error_message, and a plain retry asks again for another day. An item_timeout: failure on a send step is asked about the same way. To send it anyway, look on LinkedIn first: the conversation, the person’s invitations, the post’s comments. If it is not there, retry that one item with your word:
POST /orchestration/v4/api/mass-action-items/retry
The step goes again naming the attempt in doubt, and the owning service still holds it while that attempt could land. resend_unverified vouches for one send, so it takes a sid and never a filter (422 resend_unverified_needs_sid). A comment carries a key since 2026-10-05 like every other send. A send step that began before its key existed, such as a comment of a run that was already going, cannot be asked about: a plain retry leaves it failed with a note and counts it in left_unconfirmed_count, and only resend_unverified sends it again. On a step that is not a send, an item_timeout: failure still means the outbound call may have landed: check the target before retrying a step that is not safe to repeat.