> ## Documentation Index
> Fetch the complete documentation index at: https://developer.vanta.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mark accounts as not a person

> Exclude shared mailboxes, service accounts, and test accounts from Vanta's onboarding checklists and personnel tests, using the Manage Vanta API.

export function vibePromptBuildText(prompt, pageTitle) {
  const url = typeof window !== "undefined" && window.location ? window.location.href : "";
  const ref = url ? "\n\nReference: " + (pageTitle || "Vanta docs") + " — " + url : "";
  return prompt + ref;
}

export function vibePromptOpen(e) {
  e.preventDefault();
  e.stopPropagation();
  const link = e.currentTarget;
  const target = link.getAttribute("data-target");
  const prompt = link.getAttribute("data-prompt") || "";
  const pageTitle = link.getAttribute("data-page-title") || "";
  const URL_LIMIT = 8000;
  const text = vibePromptBuildText(prompt, pageTitle);
  let enc = encodeURIComponent(text);
  if (enc.length > URL_LIMIT) enc = encodeURIComponent(text.slice(0, 2400));
  let dest = "";
  if (target === "cursor") dest = "cursor://anysphere.cursor-deeplink/prompt?text=" + enc; else if (target === "claude") dest = "https://claude.ai/new?q=" + enc; else if (target === "chatgpt") dest = "https://chatgpt.com/?q=" + enc;
  if (!dest) return;
  if (target === "cursor") {
    window.location.href = dest;
  } else {
    window.open(dest, "_blank", "noopener,noreferrer");
  }
}

export function vibePromptCopy(e) {
  e.preventDefault();
  e.stopPropagation();
  const btn = e.currentTarget;
  const prompt = btn.getAttribute("data-prompt") || "";
  const pageTitle = btn.getAttribute("data-page-title") || "";
  const original = btn.getAttribute("data-label") || btn.innerText;
  btn.setAttribute("data-label", original);
  const text = vibePromptBuildText(prompt, pageTitle);
  navigator.clipboard.writeText(text).then(function () {
    btn.innerText = "Copied";
    setTimeout(function () {
      btn.innerText = original;
    }, 1500);
  }, function () {
    btn.innerText = "Copy failed";
    setTimeout(function () {
      btn.innerText = original;
    }, 1500);
  });
}

export function vibePromptToggle(e) {
  e.preventDefault();
  const summary = e.currentTarget;
  const panel = summary.parentElement;
  if (!panel) return;
  const body = panel.querySelector(".vibe-prompt__body");
  const chevron = summary.querySelector(".vibe-prompt__chevron");
  const isOpen = panel.getAttribute("data-open") === "true";
  const next = isOpen ? "false" : "true";
  panel.setAttribute("data-open", next);
  summary.setAttribute("aria-expanded", next);
  if (body) body.style.display = isOpen ? "none" : "block";
  if (chevron) chevron.style.transform = isOpen ? "rotate(0deg)" : "rotate(180deg)";
}

export const BuildPrompt = ({prompt, pageTitle, defaultOpen}) => <div className="vibe-prompt__panel" data-open={defaultOpen ? "true" : "false"} style={{
  marginTop: "0.85rem",
  borderRadius: "12px",
  border: "1px solid var(--vanta-border, rgba(120, 120, 130, 0.18))",
  background: "color-mix(in srgb, #5E05C4 4%, transparent)",
  overflow: "hidden"
}}>
    <button type="button" onClick={vibePromptToggle} aria-expanded={defaultOpen ? "true" : "false"} style={{
  display: "flex",
  alignItems: "center",
  gap: "0.75rem",
  width: "100%",
  padding: "0.9rem 1.1rem",
  background: "transparent",
  border: "none",
  textAlign: "left",
  cursor: "pointer",
  font: "inherit",
  color: "inherit"
}}>
      <span style={{
  display: "inline-flex",
  alignItems: "center",
  justifyContent: "center",
  gap: "0.35rem",
  padding: "0.2rem 0.55rem",
  borderRadius: "9999px",
  fontSize: "0.7rem",
  fontWeight: 700,
  letterSpacing: "0.06em",
  textTransform: "uppercase",
  color: "#5E05C4",
  background: "color-mix(in srgb, #5E05C4 12%, transparent)",
  border: "1px solid color-mix(in srgb, #5E05C4 30%, transparent)",
  whiteSpace: "nowrap",
  flexShrink: 0,
  minWidth: "9rem"
}}>
        Code this
      </span>
      <span style={{
  flex: 1,
  minWidth: 0
}}>
        <span className="text-gray-600 dark:text-gray-300" style={{
  display: "block",
  fontSize: "0.85rem",
  lineHeight: 1.45
}}>
          Generate a script or app that performs this function using the Vanta API.
        </span>
      </span>
      <span className="vibe-prompt__chevron" aria-hidden="true" style={{
  flexShrink: 0,
  display: "inline-flex",
  alignItems: "center",
  justifyContent: "center",
  width: "24px",
  height: "24px",
  color: "#5E05C4",
  transform: defaultOpen ? "rotate(180deg)" : "rotate(0deg)",
  transition: "transform 200ms ease"
}}>
        <Icon icon="chevron-down" iconType="regular" size={14} color="#5E05C4" />
      </span>
    </button>
    <div className="vibe-prompt__body" style={{
  display: defaultOpen ? "block" : "none",
  padding: "0 1.1rem 1.1rem"
}}>
      <div style={{
  margin: 0,
  padding: "0.85rem 1rem",
  borderRadius: "8px",
  background: "rgba(15, 17, 21, 0.92)",
  color: "#f4f4f5",
  fontSize: "0.78rem",
  lineHeight: 1.55,
  fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
  whiteSpace: "pre-wrap",
  wordBreak: "break-word",
  overflowWrap: "anywhere",
  overflowX: "hidden",
  overflowY: "auto",
  maxHeight: "22rem"
}}>
        {prompt}
      </div>
      <div className="not-prose" style={{
  display: "flex",
  flexWrap: "wrap",
  gap: "0.5rem",
  marginTop: "0.75rem"
}}>
        <button type="button" onClick={vibePromptCopy} data-prompt={prompt} data-page-title={pageTitle} style={{
  display: "inline-flex",
  alignItems: "center",
  gap: "0.4rem",
  padding: "0.4rem 0.85rem",
  borderRadius: "8px",
  border: "none",
  cursor: "pointer",
  fontSize: "0.8rem",
  fontWeight: 600,
  color: "#ffffff",
  background: "#5E05C4"
}}>
          <Icon icon="copy" iconType="regular" size={13} color="#ffffff" />
          Copy prompt
        </button>
        <a href="#" onClick={vibePromptOpen} data-target="cursor" data-prompt={prompt} data-page-title={pageTitle} className="vibe-prompt__link" style={{
  display: "inline-flex",
  alignItems: "center",
  gap: "0.4rem",
  padding: "0.4rem 0.75rem",
  borderRadius: "8px",
  border: "1px solid var(--vanta-border, rgba(120, 120, 130, 0.25))",
  fontSize: "0.8rem",
  fontWeight: 500,
  textDecoration: "none",
  color: "inherit",
  background: "transparent"
}}>
          <Icon icon="arrow-up-right-from-square" iconType="regular" size={11} color="#5E05C4" /><span>Cursor</span>
        </a>
        <a href="#" onClick={vibePromptOpen} data-target="claude" data-prompt={prompt} data-page-title={pageTitle} className="vibe-prompt__link" style={{
  display: "inline-flex",
  alignItems: "center",
  gap: "0.4rem",
  padding: "0.4rem 0.75rem",
  borderRadius: "8px",
  border: "1px solid var(--vanta-border, rgba(120, 120, 130, 0.25))",
  fontSize: "0.8rem",
  fontWeight: 500,
  textDecoration: "none",
  color: "inherit",
  background: "transparent"
}}>
          <Icon icon="arrow-up-right-from-square" iconType="regular" size={11} color="#5E05C4" /><span>Claude</span>
        </a>
        <a href="#" onClick={vibePromptOpen} data-target="chatgpt" data-prompt={prompt} data-page-title={pageTitle} className="vibe-prompt__link" style={{
  display: "inline-flex",
  alignItems: "center",
  gap: "0.4rem",
  padding: "0.4rem 0.75rem",
  borderRadius: "8px",
  border: "1px solid var(--vanta-border, rgba(120, 120, 130, 0.25))",
  fontSize: "0.8rem",
  fontWeight: 500,
  textDecoration: "none",
  color: "inherit",
  background: "transparent"
}}>
          <Icon icon="arrow-up-right-from-square" iconType="regular" size={11} color="#5E05C4" /><span>ChatGPT</span>
        </a>
      </div>
    </div>
  </div>;

export const VibePrompts = ({children}) => <section className="vibe-prompts" style={{
  marginTop: "2.5rem",
  marginBottom: "2rem",
  padding: "1.5rem 1.5rem 1.25rem",
  borderRadius: "16px",
  border: "1px solid color-mix(in srgb, #5E05C4 22%, transparent)",
  background: "linear-gradient(180deg, color-mix(in srgb, #5E05C4 6%, transparent) 0%, transparent 100%)"
}}>
    <div style={{
  display: "flex",
  alignItems: "center",
  gap: "0.65rem",
  marginBottom: "0.25rem"
}}>
      <div style={{
  display: "inline-flex",
  alignItems: "center",
  justifyContent: "center",
  width: "32px",
  height: "32px",
  borderRadius: "9px",
  background: "color-mix(in srgb, #5E05C4 14%, transparent)"
}}>
        <Icon icon="wand-magic-sparkles" iconType="regular" size={16} color="#5E05C4" />
      </div>
      <div>
        <div className="text-gray-900 dark:text-white" style={{
  fontFamily: "Reckless, Georgia, serif",
  fontSize: "1.25rem",
  fontWeight: 500,
  lineHeight: 1.15
}}>
          Let AI do this for you
        </div>
        <div className="text-gray-600 dark:text-gray-300" style={{
  fontSize: "0.85rem",
  lineHeight: 1.45,
  marginTop: "0.15rem"
}}>
          Copy the prompt to run this live via the <a href="/docs/vanta-mcp" style={{
  color: "#5E05C4"
}}> MCP server</a> or have AI generate a runnable script.
        </div>
      </div>
    </div>
    {children}
  </section>;

export const NOT_PEOPLE_BUILD = "You are writing a production-quality Node.js 18+ script that runs on a daily schedule (cron / GitHub Actions / Cloud Scheduler) and keeps Vanta's personnel list clean by tagging non-human accounts as not-a-person. Use the Manage Vanta API (base URL https://api.vanta.com).\n\nSteps:\n\n1. Read VANTA_CLIENT_ID, VANTA_CLIENT_SECRET, and PATTERNS_PATH from env. PATTERNS_PATH points to a JSON file shaped like { \"rules\": [{ \"match\": \"email\" | \"displayName\", \"regex\": \"...\", \"reason\": \"Shared inbox\" }, ...] }. Also accept STATE_PATH (default ./.vanta-not-people-state.json) and DRY_RUN (default \"false\"). Fail fast with a clear stderr message if VANTA_CLIENT_ID, VANTA_CLIENT_SECRET, or PATTERNS_PATH is missing.\n2. Mint a Vanta API token via POST https://api.vanta.com/oauth/token with Content-Type: application/json and a JSON body containing client_id, client_secret, grant_type=\"client_credentials\", and scope=\"vanta-api.all:read vanta-api.all:write\". The scope field is required — omitting it returns invalid_scope. Read access_token from the response. Re-mint at the start of each run (tokens expire after one hour).\n3. Load STATE_PATH if present. It contains { \"alreadyMarked\": [personId, ...] }. Treat anything in alreadyMarked as already handled — never re-call the endpoint for them.\n4. Page through GET https://api.vanta.com/v1/people?pageSize=100 using the pageCursor request param, reading results.pageInfo.endCursor and hasNextPage until hasNextPage is false. Collect every person whose employment.status is CURRENT or FORMER (skip records the API has already filtered with isActive=false).\n5. For each person, evaluate the rules in order. Match using the regex against the configured field (emailAddress or name.display). The first matching rule wins; record { id, emailAddress, displayName, employmentStatus, reason: rule.reason }. Skip people whose id is already in alreadyMarked.\n6. If DRY_RUN is \"true\": print the matches as one JSON line per row, print a summary, and exit 0 without writing.\n7. Otherwise, batch the matches into chunks of 100 and POST each chunk to https://api.vanta.com/v1/people/mark-as-not-people with body { \"updates\": [{ \"id\": \"<personId>\", \"reason\": \"<reason>\" }, ...] }. Issue chunks sequentially.\n8. Parse the per-entry results. Append every id where status === \"SUCCESS\" to alreadyMarked and write STATE_PATH atomically (write to a temp file, then rename). For each \"ERROR\" entry, log id, displayName, status, and message — but do not throw.\n9. Print one line per processed person: emailAddress | displayName | reason | status (SUCCESS / ERROR / SKIPPED).\n10. End with a single JSON summary line on stdout: {\"total\": N, \"marked\": M, \"skipped\": S, \"errors\": E, \"byReason\": {...}}.\n\nSample API response item from GET /v1/people (use these field names exactly):\n{ \"id\": \"640b888ba7e831e41f0d6cb6\", \"emailAddress\": \"support@example.com\", \"name\": { \"display\": \"Support Inbox\", \"first\": null, \"last\": null }, \"employment\": { \"status\": \"CURRENT\", \"jobTitle\": null, \"startDate\": \"2024-03-01T00:00:00.000Z\", \"endDate\": null }, \"tasksSummary\": null }\n\nSample API response item from POST /v1/people/mark-as-not-people:\n{ \"results\": [{ \"id\": \"640b888ba7e831e41f0d6cb6\", \"status\": \"SUCCESS\" }, { \"id\": \"65a0caf8b4c7501f891e2183\", \"status\": \"ERROR\", \"message\": \"Person not found\" }] }\n\nMissing-field handling: if name.display, employment.status, or any optional field is null, fall back to the email local-part for the display name and \"UNKNOWN\" for the status. Never throw on a missing optional field.\n\nError handling:\n- 401: re-mint once and retry the request.\n- 400: log the offending request body and exit non-zero.\n- 429: respect Retry-After if present, otherwise sleep 5s; retry up to 3 times.\n- 5xx: retry with 2s exponential back-off, max 3 attempts; then log and exit non-zero.\n\nDo not:\n- Add external dependencies — use built-in fetch, fs, and path only.\n- Hard-code patterns in the script — always read PATTERNS_PATH so security can iterate without redeploying.\n- Apply changes when DRY_RUN is \"true\".\n- Cache the access token across runs.\n- Silently swallow errors — every caught error must log a clear message.\n- Invent query param names — use only pageSize and pageCursor.\n\nDone when: the script exits 0 with a non-empty summary on stdout, STATE_PATH is updated, and re-running the script reports 0 new matches (idempotent). Exit non-zero only on auth failures or unrecoverable HTTP errors.\n\nScope required: vanta-api.all:read vanta-api.all:write.";

This guide flags shared mailboxes, service accounts, and test accounts in your Vanta personnel list as "not a person" using `POST /v1/people/mark-as-not-people` on the Manage Vanta API. Marked accounts are excluded from onboarding checklists, training assignments, and personnel-based tests. The build prompt below generates a daily job that applies your configured regex rules and persists state so re-runs are idempotent.

<VibePrompts>
  <BuildPrompt prompt={NOT_PEOPLE_BUILD} />
</VibePrompts>

## Before you begin

This guide is for Vanta admins managing data inside their own Vanta account.

You'll need:

* A Manage Vanta [API token](/docs/quickstart/manage-vanta).
* The token must have scopes `vanta-api.all:read` and `vanta-api.all:write`.
* The accounts you want to exclude already exist in Vanta (most often pulled in from your IDP).

People in Vanta are user accounts in scope for an audit. Some accounts pulled in via IDP sync — shared mailing lists, service accounts, CI bots, test accounts — aren't real people. Marking them "not a person" excludes them from onboarding checklists, training assignments, policy acceptance, and any other personnel-related test.

<Info>
  Made a mistake and need to undo? Use [`POST /v1/people/mark-as-people`](/reference/manage-vanta/overview) with the same `updates` shape (no `reason` required) to restore them.
</Info>

<Steps>
  <Step title="Find the account IDs">
    **Your terminal** — call [`GET /v1/people`](/reference/manage-vanta/overview) and locate the accounts you want to exclude. Filtering by `q` (partial match on email or name) is usually fastest.

    ```bash Terminal theme={"system"}
    curl 'https://api.vanta.com/v1/people?pageSize=50&q=support' \
      -H 'Accept: application/json' \
      -H 'Authorization: Bearer YOUR_TOKEN'
    ```

    Response

    ```json theme={"system"}
    {
      "results": {
        "data": [
          {
            "id": "640b888ba7e831e41f0d6cb6",
            "emailAddress": "support@example.com",
            "name": { "display": "Support Inbox" },
            "employment": { "status": "CURRENT", "jobTitle": null }
          }
        ]
      }
    }
    ```

    Copy each `id` you want to exclude — you'll send them in Step 2. You can also copy a person's ID directly from the URL on the [People page](https://app.vanta.com/people/people).

    <AccordionGroup>
      <Accordion title="Got a 401?">
        Token is expired (one-hour lifetime), missing, or lacks `vanta-api.all:read`. Mint a fresh one — see [Authentication → Token expiration](/docs/concepts/authentication#token-expiration).
      </Accordion>

      <Accordion title="Can't find the account?">
        Filter client-side by `emailAddress` or paginate with `pageCursor` if `hasNextPage` is `true`. If the account is missing entirely, your IDP hasn't synced it yet — wait one sync cycle or trigger a refresh from the [Integrations page](https://app.vanta.com/integrations).
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Mark the accounts as not a person">
    **Your terminal** — `POST /v1/people/mark-as-not-people` with an `updates` array. Each entry needs the person `id` and a short `reason` that's stored alongside the change in your audit log.

    <CodeGroup>
      ```javascript Node.js theme={"system"}
      const res = await fetch(
        "https://api.vanta.com/v1/people/mark-as-not-people",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: "Bearer YOUR_TOKEN",
          },
          body: JSON.stringify({
            updates: [
              { id: "640b888ba7e831e41f0d6cb6", reason: "Shared mailing list" },
              { id: "65a0caf8b4c7501f891e2183", reason: "CI service account" },
            ],
          }),
        },
      );
      console.log(await res.json());
      ```

      ```python Python theme={"system"}
      import requests

      r = requests.post(
          "https://api.vanta.com/v1/people/mark-as-not-people",
          headers={"Authorization": "Bearer YOUR_TOKEN"},
          json={
              "updates": [
                  {"id": "640b888ba7e831e41f0d6cb6", "reason": "Shared mailing list"},
                  {"id": "65a0caf8b4c7501f891e2183", "reason": "CI service account"},
              ]
          },
      )
      r.raise_for_status()
      print(r.json())
      ```

      ```bash Terminal theme={"system"}
      curl -X POST 'https://api.vanta.com/v1/people/mark-as-not-people' \
        -H 'Content-Type: application/json' \
        -H 'Accept: application/json' \
        -H 'Authorization: Bearer YOUR_TOKEN' \
        -d '{
          "updates": [
            { "id": "640b888ba7e831e41f0d6cb6", "reason": "Shared mailing list" },
            { "id": "65a0caf8b4c7501f891e2183", "reason": "CI service account" }
          ]
        }'
      ```
    </CodeGroup>

    Expected response (`200`) — a per-account result, so you can tell exactly which entries failed:

    ```json theme={"system"}
    {
      "results": [
        { "id": "640b888ba7e831e41f0d6cb6", "status": "SUCCESS" },
        { "id": "65a0caf8b4c7501f891e2183", "status": "SUCCESS" }
      ]
    }
    ```

    <Warning>
      Up to **100** accounts per call. Batch larger lists across multiple requests.
    </Warning>

    <AccordionGroup>
      <Accordion title="Got an `ERROR` in `results`?">
        The `message` field tells you why a specific entry failed — typically because the `id` doesn't exist, or the account is already marked as not-a-person. Successful entries are still applied; only fix and retry the failures.
      </Accordion>

      <Accordion title="Got a 403?">
        Your token has `vanta-api.all:read` but not `vanta-api.all:write`. Mint a token with both scopes.
      </Accordion>

      <Accordion title="Need to undo?">
        Call [`POST /v1/people/mark-as-people`](/reference/manage-vanta/overview) with `{ "updates": [{ "id": "..." }] }` — no `reason` is required to restore.
      </Accordion>
    </AccordionGroup>
  </Step>
</Steps>

<Info>
  **Tip.** You can verify a change took effect by re-calling `GET /v1/people/{personId}` — the account will be excluded from the default `data` array on `GET /v1/people` going forward.
</Info>

## Congratulations

You've excluded shared mailboxes, service accounts, or other non-human accounts from Vanta's personnel tracking. Those accounts won't get assigned trainings, policy acceptances, or background checks, and they won't show up as failures on personnel-related tests.

## Next steps

<CardGroup cols={2}>
  <Card title="List overdue security tasks" icon="list-check" href="/docs/guides/list-users-with-overdue-security-tasks">
    Now that the noise is filtered out, see who actually has overdue work.
  </Card>

  <Card title="Offboard people" icon="user-minus" href="/docs/guides/offboard-people">
    For real ex-employees, complete the offboarding programmatically.
  </Card>

  <Card title="Try it in Postman" icon="paper-plane" href="/docs/postman-setup">
    Import the collection and run `mark-as-not-people` against a sandbox in seconds.
  </Card>

  <Card title="Manage Vanta API reference" icon="book" href="/reference/manage-vanta/overview">
    Browse every Manage Vanta endpoint — controls, tests, documents, people.
  </Card>
</CardGroup>
