---
title: "Manage Outreach"
method: POST
path: "/api/facade/manage_outreach"
tags: ["facade"]
---

# Manage Outreach

`POST /api/facade/manage_outreach`

## Request

- Base URL: `https://api.vruum.ai`
- URL: `https://api.vruum.ai/api/facade/manage_outreach`
- Auth: `HTTPBearer` (a scheme the document does not define)

Start and steer outreach plans and objectives — the consolidated outreach writer.

Objective reads (setup, audience, forecast, sourcing plan and runs) are on
the read-only inspect_objective tool.

Actions:
- objective_pilot_refresh: refresh an expired pilot's undecided assessment.
  id = objective UUID; no payload. Does not restart work.
- objective_pilot_decide: record continue, change, wait, or stop. id = objective
  UUID; payload = {action_id, expected_version, expected_planning_version,
  decision, reason, next_check_at, idempotency_key}. Use null next_check_at
  only for stop; other choices require a future check within 90 days.
  Continue/change create owned preparation; wait schedules reassessment.
  No choice extends the pilot, applies setup, launches, or sends.
- objective_pilot_prepared: link a newly saved setup review to preparation.
  id = objective UUID; payload = {action_id, expected_version, setup_action_id,
  idempotency_key}. This completes preparation without applying setup.
- start: enroll a person in outreach. id = PERSON UUID (scalar starts
  one plan; an id ARRAY routes to the native bulk-start endpoint).
  `payload` optional: {max_touches?, allowed_channels?} — applied to
  every started plan. Enrollment always schedules the first action
  immediately; a `start_immediately` key is ignored (forced true).
  For researched/discovered prospects, call manage_person action=save_discovered
  first with a strong current-company binding and
  start only when its response has `company_bound=true` and the quality
  gates passed. Never use start to bypass an unresolved or name-only
  company save.
- plan_override: record a user override on a plan (id = plan UUID;
  `payload` = the override fields).
- channels: legacy plan-local policy action. It returns 409
  `objective_policy_owned`; update the objective policy or use
  `plan_override` for one touch.
- trigger_touch: queue the plan's next touch now (id = plan UUID; no
  payload). Refused with a structured 409 when the plan's pending
  approved email cannot send now: `email_daily_cap_reached` (the
  conversation's owning mailbox — or, for a first email, the sender
  pool — is at its daily cap; the error carries the capacity numbers,
  when capacity resets, and when the plan retries automatically) or
  `thread_mailbox_unavailable` (the owning mailbox is disconnected,
  resting, or excluded — reconnect it; another mailbox is never
  substituted into an existing conversation). A success here means the
  touch is genuinely queued, not that it may silently re-defer.
- pause / resume: pause or resume a plan (id = plan UUID). Resume
  re-arms the cold sequence: approved cold messages release and the
  next step queues. A plan that a human reply paused refuses with 409
  `plan_paused_by_reply`, carrying the latest reply, the pending cold
  messages, and the last step fired. Never resume to release a reply:
  replies send without the plan. Pass payload
  {continue_sequence: true} only when the operator decided to keep
  pitching this person; otherwise answer with a reply draft or stop the
  plan. A successful resume reports what it released.
- update: legacy plan-local policy action. It returns 409
  `objective_policy_owned`; update the objective policy instead.
- objective_create: create one draft objective (no id; payload carries
  name, goal_statement, target, cohort, and policy_envelope). It does not
  activate or send.
- objective_setup_propose: persist SetupProposalRequest for review; no configuration writes.
- objective_agent_control: start an objective Delegation (prepare_business_plan,
  pursue_meetings or pursue_content) or control one. Only an Owner of the
  objective starts, steers, resumes or takes over; any Owner or a workspace admin
  pauses or cancels. Read objective_agent_options first. Controls require only
  command, request_id, delegation_id and expected_revision (from objective_agent_runs);
  omit planning versions and guidance except on steer. A Delegation whose grantor
  lost access is paused: take it over (command takeover, optional sender_config_id)
  or cancel it. Kinds run in parallel; plan work waits for other work's paid
  steps. pursue_meetings (users call it Outreach) moves replies or meetings and
  needs sender_config_id on start: a mailbox the grantor sends from, a shared
  one, or one held by Sender delegation. It prepares replies and reviewed
  opportunity emails, follows exact reviews, delivery, replies and due work in
  the current period (fixed, or this week or month of an ongoing objective),
  and starts the next period's Job by itself. stop_period ends this period's
  Outreach work on an ongoing objective and keeps the grant.
  Content ownership requires a current fixed period and accepted human_handoff
  approach without dependencies. On start, content_brief names approach_key,
  author_user_id, follow_up_owner_user_id and publication_notes; no mailbox.
  It prepares one post, waits for exact publication approval, monitors captured
  responses, and prepares only human-assigned response work. A brief is immutable.
  Publication and engagement are not business outcomes. Steering permits another
  preparation attempt. Existing reviews remain reviewable. Reuse an exact
  request_id after uncertainty. Never applies a plan or sends contact.
- objective_setup_prepare: explicitly invoke the Architect and persist its setup review.
  mode=business_plan revises only business intent/plan. Active objectives accept
  plan and parent changes while preserving independent execution authority.
  A business plan may also propose up to five contributing_objectives (name,
  goal_statement, metric people_with_replies|people_with_meetings, goal,
  cadence weekly|monthly or end_at, reason, expected_contribution, timing,
  overlap_exclusions, suggested_kind outreach|content); approval creates them
  as drafts owned by this objective's Owners, linked as contributing. Nothing
  launches or starts agent work.
  objective_create accepts owner_user_ids (default: you) and business.original_wording,
  scope, constraints, measurement_prerequisite and an optional plan or parent contribution.
  An unresolved measure creates owned follow-up work and never fake progress.
- objective_business_complete: record an owned task completion with action_id,
  expected_version and completion_note. The task owner, any objective Owner or an admin
  can attest completion; this never asserts the business result.
- objective_opportunity_send: send one exact, human-approved email for a reviewed
  reactivation/deal-recovery work Action. Inspect objective_business first;
  payload carries action_id, expected_version, sender_config_id, to_email,
  subject and body. Repeat the identical payload only to recover or safely
  retry its canonical delivery. Never turn an unclear receipt into a new send.
- objective_setup_apply: apply the saved action_id and expected_version. Saves
  only configuration atomically, never sourcing, enrollment, launch, or sending.
- objective_setup_reject: reject the saved action_id and expected_version without applying it.
- objective_update: edit a native draft/paused objective (id = objective
  UUID; payload requires expected_planning_version and carries any of
  name, goal_statement, target, cohort, or policy_envelope). Required
  rates are the one active-objective edit:
  {<metric>: {min_rate: 0-1, min_rate_basis: '<arithmetic>'}}
  for contacted_to_reply | contacted_to_meeting | meeting_to_won, merged
  into target.required_rates (a metric set to null clears it). It is the
  viability loop's first seed basis (VRU-846); never an industry average.
  Without applicable observed conversion, a reviewed calibration pilot uses
  target.pilot = {max_contacts: 1-100, duration_days: 1-28,
  assumed_conversion_rate: >0 and <=1, assumption_basis, measurement_plan}.
  Preserve the saved target fields. A pilot also needs an executable
  sequence saved through objective_pin; target.pilot.claim_id is unsupported.
  With business_hours_only in policy_envelope.forbidden_moves, optional
  policy_envelope.sending_hours = {start_hour: 6, end_hour: 22} sets a
  same-day window in the workspace timezone, including daylight saving.
  Hours must be integers (start 0-23, end 1-24), with start < end. Opening
  is inclusive and closing exclusive; omitted hours keep 09:00-18:00.
- objective_research_brief: generate commercial research questions for review
  (id = objective UUID; payload = {expected_planning_version, guidance?}).
  Uses a budgeted model; never saves, researches prospects, assigns, or sends.
  Review/edit the proposal before objective_update saves cohort.research_brief;
  preserve other cohort fields and the proposal's expected_planning_version.
  Your own agent can instead draft the same typed brief using objective and
  company context, with brief_context_fingerprint from inspect_objective
  action=objective_audience_build.
- objective_account: select or deselect an existing CRM account (id = objective
  UUID; payload = {company_id, selected: bool}). Accounts may have zero contacts.
  Selection is planning context, not enrollment or a sourcing restriction.
  Selected accounts appear in inspect_objective action=objective_audience.
  Never enroll every employee automatically.
- objective_members: assign or unassign people (id = objective UUID;
  payload = {action: assign|unassign, person_ids: [...]}, max 100).
- objective_pin: set one claim-backed hard constraint (id = objective
  UUID; payload = {pin_key, pin_value, statement?}) on a draft/paused objective.
  Creates and links the backing Claim automatically. For an outreach sequence,
  use pin_key="sequence", pin_value=[{step: 1, delay_days: 0,
  channels: [{channel: "linkedin_connection"}], instructions: "..."}, ...],
  and statement=the sequence rationale. Steps need unique positive step
  numbers and non-empty channels; delay_days and instructions are optional.
  A standalone Claim, verdict, or tone/drafting brief does not save a sequence.
  For a bare connection request, separately pin connection_request_no_note=true.
  Repeating a live pin_key returns the existing pin; it does not replace it.
- objective_unpin: retire one pin and its Claim (id = objective UUID;
  payload = {pin_key, reason}).
- objective_retire: end an objective for good (id = objective UUID; no
  payload); its runnable plans stop under objective_retired. Works the
  same for every objective.
- objective_clone: a new draft with the objective's inputs and live pins
  (id = objective UUID; optional payload {name}); members are not
  copied. Works the same for every objective.
- objective_pause: pause an objective (id = objective UUID; no payload).
  Its runnable plans park under the objective's own stop reason;
  relaunch through forecast and approval restores exactly those. Works
  the same for every objective.
- objective_observe: propose a measurement review for an observe objective (id = objective UUID; payload = source_bindings and client_request_id). No sender, research, enrollment, or outreach.
- objective_observe_approve: approve only after the user reviews and confirms the observation definition and sources (payload = action_id and expected_version). This freezes measurement, not execution permissions.
- objective_observe_policy: set or revoke the caller's STANDING approval of observe-only runs (id = objective UUID; payload = expected_planning_version and definition {metric {id, definition_version}, source_bindings (sorted, distinct), cohort_rule {subject, admission, eligibility_rule, entry_event?}}, or definition null to revoke). Only after the user confirms it: the caller (an owner or operator) becomes the named approver, and the observation autopilot then activates matching observe-only runs as that person's approval with no further review. A run outside the definition, or an approver who loses the role, waits for a person. It never grants execution. An objective_update carries this section unchanged.
- objective_cohort_revise: record an explicit, evidenced admission or correction on an approved run (payload = run_action_id and revision). Corrections preserve the previous report and need the user's approval.
- objective_launch: propose the review-class launch Action over the exact
  current fingerprint (id = objective UUID; no payload). It does not
  activate or send. Requires a ready inspect_objective action=objective_forecast. For
  pilot_sequence_claim_required, use objective_pin with pin_key="sequence",
  then refresh the forecast. Separate claims/verdicts cannot clear this blocker;
  changing the target metric or pilot duration cannot save the missing sequence.
  A hand-off-only objective (every plan approach is a human hand-off, no
  outreach metric) has launch_mode "handoff": its launch names its
  approaches, needs no target or people, sends nothing, and enrolls no
  one. Launch it before delegating a content Job.
- objective_approve: accept that launch Action after a fresh fingerprint
  check (id = objective UUID; payload = {action_id}). A changed forecast
  abandons the action and returns 409. Approval activates the objective,
  enrolls eligible members, and schedules execution under its policy.
  and enqueues the executor, which enrolls the forecast's planned members
  through the research and supply gates; sends then follow the normal
  draft-and-approve path.
- objective_source: propose a review Action for that exact sourcing plan
  (id = objective UUID; no payload needed: the calibration pilot,
  target.pilot, sizes a pilot run, and {pilot_size} must equal its
  max_contacts if sent). Returns existing live sourcing work at once, or
  status=satisfied when the pilot audience is already full. Otherwise it
  returns status=preparing with job_id: a background job checks CRM
  imports (best company matches first) and attaches fits, then sizes
  additional sourcing. Poll inspect_objective objective_audience_build:
  preparing_job shows progress, and latest_run shows the proposal when it
  is saved. If existing members then cover the audience, the job completes
  with result.status=satisfied and no Action.
  No new discovery starts until approval. A pilot is one bounded sourcing run,
  not an outreach launch or a forecast of replies. Existing launch gates apply.
- objective_source_approve: approve the sourcing Action after a fresh
  fingerprint check (id = objective UUID; payload = {action_id}). Approval
  enqueues one bounded cohort build. Only people who pass the existing
  objective fit checks join the objective.
- objective_sourcing_stop: stop further work with action_id and expected_version; saved results remain.
- objective_sourcing_resume: explicitly approve recovery of saved work
  (id = objective UUID; payload = {job_id, reviewed_inputs}). reviewed_inputs
  come from inspect_objective action=objective_sourcing_recovery. Changed
  inputs renew fit on saved candidates without discovery or enrichment; the
  rubric model may run. Unknown provider outcomes require reconciliation
  and cannot be overridden with resume.
- objective_sourcing_reconcile: OPERATOR ONLY. Settle a saved run whose
  recovery read says reconcilable=true (id = objective UUID; payload =
  {job_id, note}; note = what you checked with the provider, 3-500
  characters). Unconfirmed outcomes settle at their reserved amount; the
  interrupted step is cut and re-runs only after objective_sourcing_resume.
  A run the user stopped never blocks a new objective_source proposal.
- objective_apply_template: render a touch's template over the
  objective's UNAUTHORED (needs_draft) rows for that touch —
  deterministic substitution of the operator's own template prose
  ({{first_name}}, {{last_name}}, {{company}}, {{title}}), never an LLM
  (id = objective UUID; payload = {step, cursor?}). BATCHED: processes up
  to 500 rows per call and returns {rendered, skipped (first 50),
  skipped_total, skipped_counts, total_unauthored, has_more,
  next_cursor}. While has_more is true you MUST call again with
  payload {step, cursor: <next_cursor>} — repeating without the cursor
  restarts from the beginning and skipped rows block the tail.
  Unrenderable rows are skipped with the reason and stay unauthored
  (missing variable value, channel outside the step's chain,
  email/InMail template without a subject). Set the template first via
  objective_update: a policy_envelope sequence step gains
  {"template": {"subject"?, "body"}}.
- objective_touch_asset: attach or remove one reusable asset for one
  touch channel (id = objective UUID; payload = {step, channel,
  asset_id} referencing a library entry uploaded through the objective
  assets route; or {step, channel, remove: true}). No inline upload or
  Drive-source fields are accepted. Assets are supported on email,
  linkedin_message, and linkedin_inmail only.
- objective_delegate: let one member manage every current and future
  objective for a sender (no id; payload = {sender_user_id,
  delegate_user_id}). The sender, owner, or operator can grant access.
- objective_revoke_delegation: revoke that grant immediately (id =
  delegation UUID). The sender, owner, or operator can revoke access.
- recommendation_decision: accept, reject, or defer one diagnosis or
  viability recommendation Action (id = the Action UUID from fetch
  type='actions_inbox'; payload = {decision: accept|reject|defer, reason,
  evidence_ids? (up to 20 evidence UUIDs from this workspace, such as the
  Action's justifications from fetch type='action'), expected_version?
  (the version you read), idempotency_key? (reuse it on a retry)}).
  accept records the user's approval (state approved) under the approval
  policy; reject ends it (rejected); defer keeps a proposed
  recommendation open and changes no state. Each decision appends one
  recommendation_decision event with the reason and evidence. Record
  only what the user decided: an agent never accepts on its own.
  A recommendation that names an objective needs write access to it; a
  workspace-wide one needs an owner or operator. Attention portfolio
  rows are refused here (decide them with attention_decision); a
  decided or expired recommendation returns 409 decision_refused.
- first_impact_setup_accept: accept a prepared First Impact measurement
  (id = the first_impact_setup Action UUID from fetch
  type='actions_inbox'; payload = {expected_version: the Action version
  you read}). Only after the user confirms it: one transaction creates the
  offer's observe objective with the prepared target and records the
  caller (an owner or operator) as the standing approver of its
  observe-only runs, so VM7, CR6 and the time to verified impact can be
  read. Nothing is sent and no objective is activated. A proposal whose
  cohort moved returns 409; reload it from the inbox.
- experiment_design: design a controlled experiment for one hypothesis
  (id = a live recommendation Action UUID whose intervention target names
  an objective, from fetch type='actions_inbox'; payload optional:
  {primary_metric?, guardrail_metrics?, minimum_detectable_effect?,
  mde_kind? (absolute | relative), alpha? (0.001-0.2), power?
  (0.5-0.99), horizon_days? (14-180), stop_rule? (fixed_horizon |
  fixed_horizon_harm_stop)}). What the user declares wins; the design
  agent drafts the rest; code computes the sample per arm from the
  objective's baseline and refuses an undeclared effect, alpha or power,
  a metric the registry does not know or cannot read per arm, and a
  design the objective's volume cannot power inside the horizon (the
  refusal names the shortfall and a learning task). It writes an
  experiment Claim, its Evidence and a review-class start Action; it
  starts nothing. One live experiment per objective.
- experiment_start: start a proposed experiment after the user reviewed
  and approved its design (id = the experiment Action UUID; payload =
  {expected_version}). The start approves it and assigns every live
  member of the objective to an arm (a fixed function of the member),
  in one transaction; it re-checks the volume there and refuses (409
  insufficient_volume, each arm's shortfall) when the members the
  objective still sends to can no longer reach the sample. Record only
  what the user decided: an agent never starts an experiment on its
  own.
- experiment_stop: record the user's decision to decline a proposed
  experiment or stop a started one (id = the experiment Action UUID;
  payload = {expected_version, reason?}). A proposal becomes rejected, a
  started experiment abandoned; its evaluation reads stopped, never a
  verdict. After its one analysis the same call closes the experiment
  instead (executed): the analysis stays its reading.
- attention_decision: record the user's decision on one attention
  portfolio row (id = the actions_inbox row id `attention:<uuid>` as it
  is, or a recommendation id from fetch type='attention_portfolio';
  payload = {decision: dismissed | not_yet | confirmed | undone | opened
  | prepared, idempotency_key (8-200 characters; reuse it on a retry),
  detail? {platform?: linkedin | x, reply_ref?, minutes?}}). dismissed
  ends the row (its Action is rejected); not_yet parks it; confirmed
  reports that the user did the work (a manual post or task; a review
  queue row completes in its own queue instead); undone reverses the
  last decision; opened and prepared record progress. Each decision is
  one append-only event; nothing sends or posts. Needs an interactive
  (OAuth) session: a personal access token gets 403. Record only what
  the user decided.

Bulk: `start` arrays hit the native bulk endpoint; pause/resume/
plan_override/channels/trigger_touch arrays loop with per-item status —
partial failure is reported per item, never silent (max 100). Payloads
are validated by the underlying route's own schema.

## Headers

- `Idempotency-Key` string, nullable: Optional. Send a unique key (a UUID) to make this call safe to retry: an exact retry of a SUCCESSFUL call replays the original response instead of re-running the action, so a dropped connection cannot double-send or double-spend. A deterministic 4xx refusal releases the key — the same key may retry once the refusing condition clears; only an ambiguous failure (>=500 or timeout) pins the key to a 409 conflict. Reusing a key with a DIFFERENT request body is rejected with 409. Keys are scoped to your company and expire after 24 hours.

## Request body

- WriterRequest: Uniform request body for every facade writer.
  - `action` string, required: The operation to perform — see the tool description for the action vocabulary and each action's id/payload semantics.
  - `id` union: Target entity id, or an ARRAY of ids to apply the same action+payload to each (max 100; see the tool description for which entity the id names per action).
    - string
    - string[]
  - `payload` object, nullable: Action-specific fields, validated by the underlying route's own schema (a bad field 422s with that route's precise errors). See the tool description per action.
  - `items` object[], nullable: For create-like actions only: an array of per-item payloads processed in order with per-item results (max 100). Mutually exclusive with `id` and `payload`.

## Response `200`

Successful Response

- unknown

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/vruum/apis/vruum-public-api.md) · [All operations](https://skmtc.dev/vruum/apis/vruum-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/vruum/apis/vruum-public-api/revisions/b8ada111c30a?raw)
