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
- Step vocabulary. Only step-eligible verbs can appear in
plan.steps[].tool. Anything else fails withvalidation_failedand afield_errorsentry 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 ofargsfails preview withsender_required. - Scope shape.
objects(existing rows by sid),targets(per-item payload identities),generate(N slots, step 1 must create the object), ornone(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...) orsn_id(the Sales Navigator id,ACwAA...);profile_idtakes either.ln_member_idis the decoded member number (digits only) and cannot be dispatched by a send verb on its own; a vanity slug goes innickname. A slug inln_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. Omitscheduleonly for plans that can drain immediately.
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
sidis 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.
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.
The Mass Actions list: one row per run, with items done against total, the randomized pacing window, the derived state and who authored it.

A run: the read-only step plan on top, then per-item rows with status, error and attempt count.
GET /api/mass-actions/{sid}?include[]=metricsreturns the run with per-status item counts.POST /api/mass-action-items/searchwithfilter: { "mass_action_sid": { "eq": "..." } }lists the individual items and their step logs.- Or skip polling: subscribe a webhook to the
mass-actions.settledandmass-actions.pausedevents.
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
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.