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

# Scope resources in or out

> Control which resources synced from a connected integration are tested and used as compliance evidence, 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 SCOPE_RESOURCES_BUILD = "You are writing a production-quality Node.js 18+ script that runs on a recurring schedule (cron / GitHub Actions / Cloud Scheduler) to keep Vanta inventory scope aligned with a configurable rule set. Use the Manage Vanta API (base URL https://api.vanta.com).\n\nSteps:\n\n1. Read VANTA_CLIENT_ID, VANTA_CLIENT_SECRET, and RULES_PATH from env. RULES_PATH points to a JSON file shaped like:\n   { \"rules\": [{ \"integrationId\": \"<id>\", \"resourceKind\": \"<kind>\", \"match\": { \"field\": \"displayName\" | \"account\" | \"region\", \"regex\": \"<pattern>\", \"flags\": \"i\" }, \"targetInScope\": false }, ...] }\n   Also accept STATE_PATH (default ./.vanta-scope-state.json) and DRY_RUN (default \"false\"). Fail fast on stderr if any required var 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 RULES_PATH and validate every rule has integrationId, resourceKind, match.field, match.regex, and a boolean targetInScope. Compile each match.regex with the supplied flags.\n4. For each rule, page through GET https://api.vanta.com/v1/integrations/{rule.integrationId}/resource-kinds/{rule.resourceKind}/resources?pageSize=100 using the pageCursor request param (read results.pageInfo.endCursor and hasNextPage until hasNextPage is false). Collect every resource and apply the regex against the configured field.\n5. A resource matches a rule when the regex test returns true AND its current inScope !== rule.targetInScope. Build a candidates list of { integrationId, resourceKind, resourceId, displayName, connectionId, account, region, currentInScope, targetInScope, ruleIndex }.\n6. Load STATE_PATH if present — { \"appliedHash\": { \"<resourceId>\": \"<sha-of-rule>\" } } — and skip candidates that have already been written for that exact rule (idempotent).\n7. Print the dry-run plan: integrationId | resourceKind | resourceId | displayName | account | region | currentInScope | targetInScope | rule index.\n8. If DRY_RUN === \"true\", print a JSON summary line and exit 0 without writing.\n9. Group candidates by (integrationId, resourceKind) and chunk each group into batches of 50 (the bulk endpoint's hard cap). For each batch, PATCH https://api.vanta.com/v1/integrations/{integrationId}/resource-kinds/{resourceKind}/resources with body { \"updates\": [{ \"id\": \"<resourceId>\", \"inScope\": <targetInScope> }, ...] }. Issue requests sequentially.\n10. Parse the per-resource results. For every entry where status === \"SUCCESS\" record state[resourceId] = sha of the rule; write STATE_PATH atomically after every batch. Log every \"ERROR\" entry's id, status, and message field.\n11. End with a single JSON summary line on stdout: {\"total\": N, \"updated\": U, \"skipped\": S, \"errors\": E, \"byIntegration\": {...}}.\n\nSample API response item from GET /v1/integrations/{integrationId}/resource-kinds/{resourceKind}/resources (use these field names exactly):\n{ \"responseType\": \"ComputeInstance\", \"resourceKind\": \"GCPComputeInstance\", \"resourceId\": \"663e78bb5360cc947addd5f3\", \"connectionId\": \"663e78b85360cc947add64bd\", \"displayName\": \"vm-backend-service-2\", \"owner\": \"61a66a98538a952a6533f540\", \"inScope\": true, \"description\": \"GCP Compute Instance\", \"account\": \"amalgamated-widgets-demo\", \"region\": \"us-central1-a\" }\n\nSample API response from PATCH /v1/integrations/{integrationId}/resource-kinds/{resourceKind}/resources:\n{ \"results\": [{ \"id\": \"663e78bb5360cc947addd5f3\", \"status\": \"SUCCESS\" }, { \"id\": \"663e78bb5360cc947addd611\", \"status\": \"ERROR\", \"message\": \"Resource not scopable\" }] }\n\nMissing-field handling: if account, region, or any optional field is null on a resource, the regex match against that field returns false (do not throw). Print \"—\" for missing fields in logs.\n\nError handling:\n- 401: re-mint once and retry the request.\n- 404: integrationId or resourceKind is wrong (case-sensitive). Log the rule, skip that rule, continue.\n- 4xx: log status + body, mark the batch as ERROR, continue.\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, path, and crypto only.\n- Hard-code any integrationId, resourceKind, connectionId, account, region, or pattern — every customer's inventory is different. Always read RULES_PATH and discover resources via the API.\n- Send more than 50 entries per PATCH call (hard cap).\n- Apply changes when DRY_RUN is \"true\".\n- Cache the access token across runs.\n- Silently swallow errors.\n\nDone when: a follow-up run reports 0 candidates (every targeted resource is at its target inScope). Exit 0 if errors === 0; otherwise exit 1.\n\nScope required: vanta-api.all:read vanta-api.all:write.";

This guide flips resources synced from a connected integration in or out of scope using `PATCH /v1/integrations/{integrationId}/resource-kinds/{resourceKind}/resources` on the Manage Vanta API. Out-of-scope resources stay visible in the inventory but stop counting as compliance evidence and stop running through tests. The build prompt below generates a scheduled job that applies regex-based scoping rules — like "exclude every `dev-*` GCP project" — in batches of 50.

<VibePrompts>
  <BuildPrompt prompt={SCOPE_RESOURCES_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`.
* At least one connected integration whose resources you want to scope.

Vanta-built integrations pull in resources — user accounts, cloud instances, code repositories, and more — that get tested for compliance. Marking a resource `inScope: false` excludes it from those tests entirely. Use it for sandbox accounts, test instances, or systems explicitly outside your audit boundary.

<Info>
  Want to know which resources are scopable before you write code? `GET /v1/integrations/{integrationId}/resource-kinds/{resourceKind}` returns `isScopable: true|false` along with `numResources` / `numInScope` counters.
</Info>

<Steps>
  <Step title="Find the resources you want to scope">
    **Your terminal** — pick the `integrationId` and `resourceKind` you're targeting, then list resources with the filters you care about. Common choices: `isInScope=true` to find things to scope **out**, or `isInScope=false` to bring something back **in**.

    ```bash Terminal theme={"system"}
    curl 'https://api.vanta.com/v1/integrations/gcp/resource-kinds/GCPComputeInstance/resources?connectionId=663e78b85360cc947add64bd&isInScope=true&pageSize=50' \
      -H 'Accept: application/json' \
      -H 'Authorization: Bearer YOUR_TOKEN'
    ```

    Response (truncated)

    ```json theme={"system"}
    {
      "results": {
        "data": [
          {
            "responseType": "ComputeInstance",
            "resourceKind": "GCPComputeInstance",
            "resourceId": "663e78bb5360cc947addd5f3",
            "connectionId": "663e78b85360cc947add64bd",
            "displayName": "vm-backend-service-2",
            "owner": "61a66a98538a952a6533f540",
            "inScope": true,
            "description": "GCP Compute Instance",
            "account": "amalgamated-widgets-demo",
            "region": "us-central1-a"
          }
        ],
        "pageInfo": { "hasNextPage": false, "endCursor": "..." }
      }
    }
    ```

    Copy each `resourceId` you want to update — you'll send them in Step 2.

    <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="Don't know the integrationId or resourceKind?">
        Call [`GET /v1/integrations`](/reference/manage-vanta/overview) to list connected integrations, their `connectionIds`, and the `resourceKinds` they expose (e.g. `aws`, `gcp`, `okta`, `snowflake`).
      </Accordion>

      <Accordion title="Multiple connections for the same integration?">
        Pass `connectionId=<id>` to filter to a single connection (e.g. one of two GCP projects). Without it, the response covers every connection for that integration.
      </Accordion>

      <Accordion title="Resource kind isn't scopable?">
        Some resource kinds (e.g. policy compliance results) can't be scoped out — `isScopable` will be `false` on the resource-kind details. Trying to update those will return an error.
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Update scope in bulk">
    **Your terminal** — `PATCH /v1/integrations/{integrationId}/resource-kinds/{resourceKind}/resources` with an `updates` array. Each entry is keyed by `id` (the `resourceId`) and may set `inScope`, `ownerId`, and/or `description`.

    <CodeGroup>
      ```javascript Node.js theme={"system"}
      const INTEGRATION_ID = "gcp";
      const RESOURCE_KIND = "GCPComputeInstance";

      const res = await fetch(
        `https://api.vanta.com/v1/integrations/${INTEGRATION_ID}/resource-kinds/${RESOURCE_KIND}/resources`,
        {
          method: "PATCH",
          headers: {
            "Content-Type": "application/json",
            Authorization: "Bearer YOUR_TOKEN",
          },
          body: JSON.stringify({
            updates: [
              { id: "663e78bb5360cc947addd5f3", inScope: false },
              { id: "663e78bb5360cc947addd611", inScope: false },
            ],
          }),
        },
      );
      console.log(await res.json());
      ```

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

      INTEGRATION_ID = "gcp"
      RESOURCE_KIND = "GCPComputeInstance"

      r = requests.patch(
          f"https://api.vanta.com/v1/integrations/{INTEGRATION_ID}/resource-kinds/{RESOURCE_KIND}/resources",
          headers={"Authorization": "Bearer YOUR_TOKEN"},
          json={
              "updates": [
                  {"id": "663e78bb5360cc947addd5f3", "inScope": False},
                  {"id": "663e78bb5360cc947addd611", "inScope": False},
              ]
          },
      )
      r.raise_for_status()
      print(r.json())
      ```

      ```bash Terminal theme={"system"}
      curl -X PATCH 'https://api.vanta.com/v1/integrations/gcp/resource-kinds/GCPComputeInstance/resources' \
        -H 'Content-Type: application/json' \
        -H 'Accept: application/json' \
        -H 'Authorization: Bearer YOUR_TOKEN' \
        -d '{
          "updates": [
            { "id": "663e78bb5360cc947addd5f3", "inScope": false },
            { "id": "663e78bb5360cc947addd611", "inScope": false }
          ]
        }'
      ```
    </CodeGroup>

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

    ```json theme={"system"}
    {
      "results": [
        { "id": "663e78bb5360cc947addd5f3", "status": "SUCCESS" },
        { "id": "663e78bb5360cc947addd611", "status": "SUCCESS" }
      ]
    }
    ```

    <Warning>
      The bulk endpoint accepts up to **50** resources per call. Page through Step 1 and call this endpoint in batches if you have more.
    </Warning>

    <AccordionGroup>
      <Accordion title="Got an `ERROR` in `results`?">
        The `message` field tells you which entry failed and why — typically because the `id` doesn't belong to that `(integrationId, resourceKind)` or the resource kind isn't scopable. Successful entries are still applied; only fix and retry the failures.
      </Accordion>

      <Accordion title="Got a 404 on the path?">
        The `integrationId` or `resourceKind` is wrong. They're case-sensitive — `gcp` and `GCPComputeInstance` are different fields. Re-run Step 1 to confirm the exact spellings.
      </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>
    </AccordionGroup>
  </Step>
</Steps>

<Info>
  **Updating one resource at a time?** Use `PATCH /v1/integrations/{integrationId}/resource-kinds/{resourceKind}/resources/{resourceId}` with `{ "inScope": false }`. It returns `204 No Content` on success and is the right call when you're reacting to a single event (e.g. a webhook from your tagging system).
</Info>

## Congratulations

You've taken control of which resources Vanta tests against. Out-of-scope resources are skipped by automated tests and won't show up as failing entities, while staying visible in your inventory for traceability. The change is captured in your audit log.

## Next steps

<CardGroup cols={2}>
  <Card title="Add owners and descriptions" icon="user-pen" href="/docs/guides/add-owners-to-resources">
    For everything that **stays** in scope, ensure each resource has an active owner and description.
  </Card>

  <Card title="Query failing tests" icon="circle-exclamation" href="/docs/guides/query-test-results-and-filter-for-failing-resources">
    Confirm a previously-failing test no longer flags the resource you scoped out.
  </Card>

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

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