---
title: "Manage Person"
method: POST
path: "/api/facade/manage_person"
tags: ["facade"]
---

# Manage Person

`POST /api/facade/manage_person`

## Request

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

Create and manage people in your pipeline — the consolidated person writer.

Actions (id = person UUID unless noted):
- create: add a person manually (no id; `payload` = {first_name,
  last_name, company_name, email and/or linkedin_url (at least one),
  title?, company_website?, phone?, location?, objective_id?,
  skip_research?}); bulk via `items` (one payload per person).
- update_contact: override contact fields (`payload` = contact overrides).
- set_position: make one of the person's current positions their primary
  position for this workspace (`payload` = {company_id}).
- add_position: record a current or former role (`payload` =
  {company_name, title?, company_id?, relationship_type? (employment|
  founder|advisor|board|contractor|other), is_current? (default true),
  started_at_raw?, ended_at_raw?, make_primary? (current roles only)}).
  Positions are shared facts about the person; make_primary also makes it
  the role this workspace shows. The response's `change` names what the
  add created and the primary position it replaced.
- task: create a follow-up task on the person (`payload` = {title,
  description?, due_at? (ISO), priority? (low|medium|high|urgent),
  assigned_to?}). The response's task id and updated_at identify it.
- note: add a timeline note (`payload` = {body}).
- log_interaction: log a manual touch that happened outside an automated
  sequence — a call (with disposition + what was said), or an
  email/LinkedIn/other touch made elsewhere. `payload` =
  {interaction_kind: call|email|linkedin|meeting|other, direction?:
  outbound|inbound, summary?, disposition? (calls only), occurred_at?
  (ISO, backdatable), deal_id?}. Lands on the person's timeline attributed
  to you (performed_by_user_id).
- archive: soft-delete one or many people — stops outreach plans and
  rejects pending drafts (id or id array; no payload). DESTRUCTIVE.
- unarchive: restore archived people (id or id array; no payload).
- meeting_booked: mark a meeting booked with this person (`payload` =
  {idempotency_key: UUID}; reuse it when retrying the same booking).
- save_discovered: THE creator — save a researched prospect into the
  pipeline, atomically (no id; bulk via `items`). Two shapes:
  NEW prospect: `payload` = {person: {first_name, last_name,
  linkedin_url|email, company_id (from research action=save_company) or
  company_name plus company_domain/company_website/company_linkedin_url,
  ...research fields}, assessment: {match_score
  0-100, match_summary, alignment_points?, concerns?, why_now?,
  recommended_approach?, overall_confidence?, scored_by?}, objective_id or
  assessment_objective_id}. Person + research + pipeline membership land
  in ONE transaction — a rejected or failed save persists nothing.
  EXISTING person: `payload` = {person_id, company_id, objective_id?,
  assessment_objective_id?, assessment?}; if company_id is unavailable,
  pass company_name plus at least one top-level company_domain,
  company_website, or company_linkedin_url. Never send a bare person_id:
  the binding atomically adds/promotes the current position and pins the
  pipeline membership while the assessment is applied update-in-place
  (THE path to score an existing stub). Pass exactly one of `person` /
  `person_id`. Use assessment_objective_id to preserve
  objective-scoring provenance without assigning the person. Your
  assessment is AUTHORITATIVE: the backend records it with provenance and
  never re-scores it. Backend floor stays mechanical: dedupe first
  (anchors resolving to an existing person continue as a duplicate
  update); new rows require a LinkedIn URL or email and stay within the
  per-tenant discovery-volume cap; the `person` shape requires
  `assessment` + an objective ref (did no fit analysis? use action=create
  instead). Duplicates do not consume or re-check the new-row cap. The
  response includes `company_id` and `company_bound`; require
  `company_bound=true` before treating the save as company-complete or
  calling manage_outreach action=start. If false, do not enroll—repair the
  company binding and retry save_discovered.
- set_persona: set a contact's buying-center role (Revenue Architecture
  Table 8.2) — initiator / user / champion / decider / gatekeeper /
  influencer / executive_buyer / approver / purchaser / unknown
  (`payload` = {persona, reasoning?}).
- set_meeting_purpose: say what a meeting was for (`id` = the meeting id,
  the `source_id` of a relationship-history event whose `source_type` is
  `meetings`, or `li:<interaction id>` for a logged meeting; `payload` = {purpose}
  — discovery / demo / proposal / negotiation / commit / kickoff /
  impact_review / renewal / expansion / winback / internal / other, or
  null to clear). The seller's word outlives the calendar title; a held
  meeting's timeline event moves to the new purpose's practice. The harness classifies buying role at touch time and
  writes it here; there is no backend classifier.
- set_meeting_no_show: mark a meeting as a no-show, or undo the mark
  (`id` = the meeting id: the `source_id` of a `fetch
  type=relationship_history_detail` event whose `source_type` is
  `meetings`; `payload` = {no_show: true|false}). A meeting
  with an external person counts toward a meeting objective once it
  starts unless it was cancelled or marked; only a started meeting can be
  a no-show (409 otherwise). Undo restores its earlier outcome.
- adopt_directory: add an existing shared canonical person to this tenant's
  CRM (id = directory person UUID; no payload). This copies no tenant-private
  notes, activity, objective, score, or outreach state. It is free for the
  tenant.

Bulk: `id` arrays apply the same action to each person (archive/unarchive
route to the native bulk endpoints; others loop with per-item status —
partial failure is reported per item, never silent). `items` is for
create-like actions, max 100 per call. Payloads are validated by the
underlying route's own schema, so a bad field returns that route's
precise 422.

## 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)
