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

# Find failing tests

> Find tests that need attention, then drill into the specific entities (users, buckets, repos, etc.) causing them to fail, 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 AskPrompt = ({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, #3b82f6 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: "#3b82f6",
  background: "color-mix(in srgb, #3b82f6 12%, transparent)",
  border: "1px solid color-mix(in srgb, #3b82f6 30%, transparent)",
  whiteSpace: "nowrap",
  flexShrink: 0,
  minWidth: "9rem"
}}>
        Ask Vanta Agent
      </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
}}>
          Use the Vanta MCP server to run this workflow on your behalf.
        </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: "#3b82f6",
  transform: defaultOpen ? "rotate(180deg)" : "rotate(0deg)",
  transition: "transform 200ms ease"
}}>
        <Icon icon="chevron-down" iconType="regular" size={14} color="#3b82f6" />
      </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"
}}>
        {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 FAILING_TESTS_ASK = "Using the Vanta MCP server, give me a remediation snapshot of every NEEDS_ATTENTION test, drilled all the way down to the failing entities behind each one.\n\nREPLACE WITH FILTERS that apply to my org if I want a narrower slice — e.g. categoryFilter (one of the categories the API exposes, like INFRASTRUCTURE), frameworkFilter (a framework key like soc2 or iso27001), or integrationFilter (an integration key like aws or okta). If I leave them blank, give me everything.\n\nFor each test, include the test id, name, category, integrations, owner displayName, lastTestRunDate, remediationStatusInfo (status, soonestRemediateByDate, itemCount), and the failing entities — entity id, displayName, responseType, lastUpdatedDate. Sort tests by remediationStatusInfo.soonestRemediateByDate ascending (most urgent first), then group entities under each test.";

export const FAILING_TESTS_BUILD = "You are writing a production-quality Node.js 18+ script that runs on a recurring schedule (cron / GitHub Actions / Cloud Scheduler) to produce a structured remediation report from Vanta — failing tests with the specific entities behind each one. The report is consumed by downstream ticketing or dashboards. Use the Manage Vanta API (base URL https://api.vanta.com).\n\nSteps:\n\n1. Read VANTA_CLIENT_ID and VANTA_CLIENT_SECRET from env. Also accept optional CATEGORY_FILTER, FRAMEWORK_FILTER, INTEGRATION_FILTER (each maps to the matching API query param), OUTPUT_PATH (default ./vanta-failing-tests.ndjson), STATE_PATH (default ./.vanta-failing-tests-state.json), and DRY_RUN (default \"false\"). Fail fast on stderr if VANTA_CLIENT_ID or VANTA_CLIENT_SECRET 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\". 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. Page through GET https://api.vanta.com/v1/tests with statusFilter=NEEDS_ATTENTION, pageSize=100, and any of categoryFilter / frameworkFilter / integrationFilter that were supplied. Use the pageCursor request param, reading results.pageInfo.endCursor and hasNextPage until hasNextPage is false. Collect every test.\n4. For each test, page through GET https://api.vanta.com/v1/tests/{testId}/entities?entityStatus=FAILING&pageSize=100 with pageCursor pagination. Collect every failing entity. Issue tests sequentially (no parallelism) to keep ordering deterministic and stay under rate limits.\n5. Build the report: one record per test, sorted by remediationStatusInfo.soonestRemediateByDate ascending (nulls last). Each record contains test fields plus a nested \"failingEntities\" array.\n6. Load STATE_PATH if present — { \"previous\": { \"<testId>\": { \"entityIds\": [...], \"lastTestRunDate\": \"...\" } } } — and compute, for each test, \"newlyFailingEntities\" (entity ids in this run not in the previous run) and \"fixedEntities\" (previous ids no longer failing). Mark tests with status changes via \"isNewFailure\" (test wasn't NEEDS_ATTENTION last run) or \"hasNewEntities\" (any newlyFailingEntities).\n7. Write OUTPUT_PATH as newline-delimited JSON (NDJSON), one test per line. Atomically replace the file (write to OUTPUT_PATH + \".tmp\", fsync, then rename). After successful write, persist current state to STATE_PATH atomically.\n8. Print a human-readable summary to stdout, one line per test: testId | name | category | integrations[] | soonestRemediateByDate | itemCount | newlyFailingEntities count | fixedEntities count.\n9. End with a single JSON summary line on stdout: {\"totalTests\": T, \"totalFailingEntities\": E, \"newFailures\": NF, \"newEntities\": NE, \"fixed\": F, \"byCategory\": {...}}.\n10. If DRY_RUN === \"true\", skip the OUTPUT_PATH and STATE_PATH writes but still print the summary.\n\nSample API response item from GET /v1/tests (use these field names exactly):\n{ \"id\": \"inventory-list-owners\", \"name\": \"Inventory items have active owners\", \"lastTestRunDate\": \"2024-07-26T23:20:42.307Z\", \"latestFlipDate\": null, \"description\": \"...\", \"failureDescription\": \"...\", \"remediationDescription\": \"...\", \"version\": { \"major\": 0, \"minor\": 0 }, \"category\": \"Infrastructure\", \"integrations\": [\"aws\"], \"status\": \"NEEDS_ATTENTION\", \"deactivatedStatusInfo\": { \"isDeactivated\": false, \"deactivatedReason\": null, \"lastUpdatedDate\": null }, \"remediationStatusInfo\": { \"status\": \"OVERDUE\", \"soonestRemediateByDate\": \"2023-09-20T15:12:00.092Z\", \"itemCount\": 8 }, \"owner\": { \"id\": \"64992d9c01bf8bd6638dfe40\", \"displayName\": \"Alex González\" } }\n\nSample API response item from GET /v1/tests/{testId}/entities:\n{ \"id\": \"65fc81a3359c8508c9af880f\", \"entityStatus\": \"FAILING\", \"displayName\": \"account-123456789012\", \"responseType\": \"AWS account\", \"deactivatedReason\": null, \"lastUpdatedDate\": \"2024-06-18T20:17:38.463Z\", \"createdDate\": \"2024-06-18T20:17:38.463Z\" }\n\nMissing-field handling: if owner is null treat as \"(unowned)\"; if remediationStatusInfo.soonestRemediateByDate is null sort to the end; if integrations is missing or empty print \"—\". Never throw on a missing optional field.\n\nError handling:\n- 401: re-mint once and retry the request.\n- 404 on /tests/{testId}/entities: log the testId and continue (test was deleted between calls).\n- 429: respect Retry-After header 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 any test id, framework, integration, or category — every customer's test set is different. Discover everything via /v1/tests with the optional env-driven filters.\n- Send queries with status filters other than NEEDS_ATTENTION on /v1/tests, or entityStatus filters other than FAILING on entities.\n- Cache the access token across runs.\n- Silently swallow errors.\n\nDone when: OUTPUT_PATH contains valid NDJSON with one record per failing test, STATE_PATH is updated, the summary line is printed. Exit 0 if errors === 0; otherwise exit 1.\n\nScope required: vanta-api.all:read.";

This guide builds a remediation report from Vanta — every `NEEDS_ATTENTION` test, drilled down to the specific failing entities (users, buckets, repos, devices, vulnerabilities) behind each one. It pairs `GET /v1/tests?statusFilter=NEEDS_ATTENTION` with `GET /v1/tests/{testId}/entities?entityStatus=FAILING` on the Manage Vanta API. Use the ask prompt for an MCP-driven snapshot, or the build prompt to emit NDJSON suitable for downstream ticketing or dashboards, complete with `newlyFailingEntities` and `fixedEntities` diffs across runs.

<VibePrompts>
  <AskPrompt prompt={FAILING_TESTS_ASK} />

  <BuildPrompt prompt={FAILING_TESTS_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 scope `vanta-api.all:read`.

Tests in Vanta are automated configuration checks — either pre-built when you connect an integration, or [Custom Tests](https://help.vanta.com/hc/en-us/articles/28012720726036-Creating-Custom-Tests) you author. Each failing test has one or more **entities** behind it: the specific S3 bucket, IAM user, repo, or person that's out of compliance. This guide finds failing tests, then resolves them down to those entities.

<Info>
  Building a remediation dashboard? Cache the test list (refreshed every 6–12 hours) and only re-fetch entities for tests whose `status` is `NEEDS_ATTENTION` and whose `lastTestRunDate` has changed.
</Info>

<Steps>
  <Step title="List failing tests">
    **Your terminal** — call [`GET /v1/tests`](/reference/manage-vanta/overview) with `statusFilter=NEEDS_ATTENTION`. Add `categoryFilter` or `frameworkFilter` to narrow further.

    ```bash Terminal theme={"system"}
    curl 'https://api.vanta.com/v1/tests?pageSize=10&statusFilter=NEEDS_ATTENTION&categoryFilter=INFRASTRUCTURE' \
      -H 'Accept: application/json' \
      -H 'Authorization: Bearer YOUR_TOKEN'
    ```

    Response (truncated)

    ```json theme={"system"}
    {
      "results": {
        "data": [
          {
            "id": "inventory-list-owners",
            "name": "Inventory items have active owners",
            "lastTestRunDate": "2024-07-26T23:20:42.307Z",
            "status": "NEEDS_ATTENTION",
            "category": "Infrastructure",
            "remediationStatusInfo": {
              "status": "OVERDUE",
              "soonestRemediateByDate": "2023-09-20T15:12:00.092Z",
              "itemCount": 8
            },
            "owner": {
              "id": "64992d9c01bf8bd6638dfe40",
              "displayName": "Alex González"
            }
          }
        ],
        "pageInfo": { "hasNextPage": false, "endCursor": "..." }
      }
    }
    ```

    Copy each `id` you want to inspect — you'll send it as the path parameter in Step 2. The test ID is human-readable (e.g. `inventory-list-owners`), and you can also copy it from the URL on the [Tests page](https://app.vanta.com/tests).

    <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="What status filters are available?">
        `OK`, `NEEDS_ATTENTION` (failing), `IN_PROGRESS`, `DEACTIVATED`, `INVALID`, `NOT_APPLICABLE`. Most remediation tooling only cares about `NEEDS_ATTENTION`.
      </Accordion>

      <Accordion title="Filter by framework or integration?">
        Add `frameworkFilter=soc2` (or `iso27001`, `hipaa`, etc.) and/or `integrationFilter=aws`. Combine with `categoryFilter` to narrow to e.g. failing AWS infrastructure tests for SOC 2.
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Get the failing entities for a test">
    **Your terminal** — call [`GET /v1/tests/{testId}/entities`](/reference/manage-vanta/overview) with `entityStatus=FAILING` (the default). Each entity is the specific resource — an AWS account, S3 bucket, IAM user, etc. — that's causing the test to fail.

    <CodeGroup>
      ```javascript Node.js theme={"system"}
      const TEST_ID = "inventory-list-owners";

      const res = await fetch(
        `https://api.vanta.com/v1/tests/${TEST_ID}/entities?entityStatus=FAILING&pageSize=50`,
        {
          headers: { Authorization: "Bearer YOUR_TOKEN" },
        },
      );
      const body = await res.json();
      for (const entity of body.results.data) {
        console.log(entity.responseType, entity.displayName, entity.id);
      }
      ```

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

      TEST_ID = "inventory-list-owners"

      r = requests.get(
          f"https://api.vanta.com/v1/tests/{TEST_ID}/entities",
          headers={"Authorization": "Bearer YOUR_TOKEN"},
          params={"entityStatus": "FAILING", "pageSize": 50},
      )
      r.raise_for_status()
      for entity in r.json()["results"]["data"]:
          print(entity["responseType"], entity["displayName"], entity["id"])
      ```

      ```bash Terminal theme={"system"}
      TEST_ID="inventory-list-owners"

      curl "https://api.vanta.com/v1/tests/$TEST_ID/entities?entityStatus=FAILING&pageSize=50" \
        -H 'Accept: application/json' \
        -H 'Authorization: Bearer YOUR_TOKEN'
      ```
    </CodeGroup>

    Expected response (`200`):

    ```json theme={"system"}
    {
      "results": {
        "data": [
          {
            "id": "65fc81a3359c8508c9af880f",
            "entityStatus": "FAILING",
            "displayName": "account-123456789012",
            "responseType": "AWS account",
            "lastUpdatedDate": "2024-06-18T20:17:38.463Z",
            "createdDate": "2024-06-18T20:17:38.463Z"
          }
        ],
        "pageInfo": { "hasNextPage": false, "endCursor": "..." }
      }
    }
    ```

    `displayName` is what you'll surface in your UI ("S3 bucket `my-data-bucket` is failing"). `id` is the entity reference if you want to deactivate it later.

    <AccordionGroup>
      <Accordion title="Got a 404?">
        The `testId` is wrong. They're case-sensitive and human-readable (e.g. `inventory-list-owners`, not `Inventory list owners`). Re-run Step 1 and copy the `id` exactly.
      </Accordion>

      <Accordion title="No failing entities returned but the test is `NEEDS_ATTENTION`?">
        Pagination — `pageSize` defaults to 10. Pass `pageSize=50` (max 100) and paginate with `pageCursor` until `hasNextPage` is `false`. Some tests have hundreds of failing entities.
      </Accordion>

      <Accordion title="Want deactivated entities too?">
        Pass `entityStatus=DEACTIVATED` to see entities you've explicitly excluded from the test. These don't count toward failure but are still tracked.
      </Accordion>

      <Accordion title="Record a justification for a failing entity">
        To mark a failing entity as a known false positive or accepted risk, deactivate it with a written justification. The reason is preserved on the entity for the audit trail.

        ```bash Terminal theme={"system"}
        curl -X POST "https://api.vanta.com/v1/tests/$TEST_ID/entities/$ENTITY_ID/deactivate" \
          -H 'Content-Type: application/json' \
          -H 'Authorization: Bearer YOUR_TOKEN' \
          -d '{
            "deactivateReason": "Bastion host — exempted per ticket SEC-1234",
            "deactivateUntilDate": "2026-12-31T00:00:00Z"
          }'
        ```

        `deactivateReason` is required; `deactivateUntilDate` is optional (omit it for an indefinite deactivation). Reactivate later with [`POST /v1/tests/{testId}/entities/{entityId}/reactivate`](/reference/manage-vanta/overview).

        The endpoint deactivates one entity per call — there is no bulk-justification endpoint for test entities. To justify many entities, loop over the failing entities from Step 2 and call deactivate per entity, staying under the [50 requests/minute Manage Vanta rate limit](/reference/manage-vanta/overview#rate-limits). For vulnerabilities, use [`POST /v1/vulnerabilities/deactivate`](/reference/manage-vanta/overview), which accepts a batch.
      </Accordion>
    </AccordionGroup>
  </Step>
</Steps>

<Info>
  **Suppressing a known false positive?** Use [`POST /v1/tests/{testId}/entities/{entityId}/deactivate`](/reference/manage-vanta/overview) with a reason. Reactivate later with the matching `/reactivate` endpoint.
</Info>

## Congratulations

You've gone from "which tests are failing?" to "which specific resources are causing each failure?" — exactly the slice you need to drive remediation, file tickets, or ping the right owner.

## Next steps

<CardGroup cols={2}>
  <Card title="Add owners and descriptions" icon="user-pen" href="/docs/guides/add-owners-to-resources">
    Many "needs attention" infrastructure tests are because resources are missing owners or descriptions.
  </Card>

  <Card title="Scope resources in or out" icon="filter" href="/docs/guides/scope-resources-at-the-integration-level">
    Mark resources `inScope: false` to remove them from a test entirely.
  </Card>

  <Card title="Try it in Postman" icon="paper-plane" href="/docs/postman-setup">
    Import the collection and run the tests + entities calls against a sandbox in seconds.
  </Card>

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