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

# Create vendors with evidence

> Add a vendor to your Vanta vendor inventory and attach a documentation file (SOC 2, DPA, security questionnaire, etc.), 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 VENDORS_BUILD = "Using the Manage Vanta API (base URL https://api.vanta.com), write a Node.js script that creates a vendor and attaches an evidence file. The script should run on a recurring schedule (cron / GitHub Actions / Cloud Scheduler) so new vendors and refreshed evidence flow into Vanta automatically.\n\n1. Reads VANTA_CLIENT_ID, VANTA_CLIENT_SECRET, and VENDOR_INPUT_PATH from env (VENDOR_INPUT_PATH points to a JSON file with the vendor + evidence rows to apply). Fail fast if any are missing.\n2. Mints a Vanta API token using the client-credentials flow at POST https://api.vanta.com/oauth/token (Content-Type: application/json, body includes client_id, client_secret, grant_type=\"client_credentials\", scope=\"vanta-api.all:read vanta-api.all:write\"). Re-mint at the top of every run — tokens expire after one hour.\n3. For each row in the input file: a) check whether a vendor with that websiteUrl already exists by paging GET /v1/vendors with pageCursor pagination — if so, capture its id and skip creation. Otherwise create one with POST /v1/vendors. Body: { name, websiteUrl, category, servicesProvided, inherentRiskLevel }. Log the returned vendor id.\n4. Uploads an evidence file with POST /v1/vendors/{vendorId}/documents as multipart/form-data. Fields: file (binary), type (e.g. \"SOC2_REPORT\"), title, description. Do NOT set Content-Type manually — let the HTTP client inject the multipart boundary.\n5. Logs the returned document id and title.\n6. Persists a state file mapping websiteUrl → vendorId so re-runs are idempotent.\n7. Prints a JSON summary line: { \"created\": N, \"skipped\": S, \"uploaded\": U, \"errors\": E }.\n\nError handling:\n- 401: re-mint the token (expires after one hour) and retry once.\n- 415: warn that Content-Type must not be set manually for multipart uploads.\n- 400 on type field: log supported values — SOC2_REPORT, ISO_27001_REPORT, PEN_TEST, DPA, PRIVACY_POLICY, OTHER.\n- 5xx: retry with 2s exponential back-off, max 3 attempts.\n\nDo not hard-code any vendor name, websiteUrl, category, or file path — every customer's vendor list is different. Always read VENDOR_INPUT_PATH and discover existing vendors via /v1/vendors.\n\nAccepted file types: .pdf, .docx, .jpg, .png, .xlsx.\nScopes required: vanta-api.all:read, vanta-api.all:write.";

This guide creates a vendor in your Vanta vendor inventory with `POST /v1/vendors` and attaches an evidence file (SOC 2 report, DPA, pen test, security questionnaire) with `POST /v1/vendors/{vendorId}/documents` on the Manage Vanta API. The build prompt below generates a scheduled job that reads a vendor list from disk, deduplicates by `websiteUrl`, and uploads fresh evidence files idempotently.

<VibePrompts>
  <BuildPrompt prompt={VENDORS_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 evidence file you want to attach (PDF, DOCX, JPG, PNG, or XLSX) saved locally.

<Info>
  Bulk-importing vendors from a procurement system? Run Step 1 in a loop, capture the returned `id`, then run Step 2 once per supporting document.
</Info>

<Steps>
  <Step title="Create the vendor">
    **Your terminal** — `POST /v1/vendors` with the vendor metadata. Only `name` is required; everything else can be filled in later via `PATCH /v1/vendors/{vendorId}`.

    <CodeGroup>
      ```javascript Node.js theme={"system"}
      const res = await fetch("https://api.vanta.com/v1/vendors", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer YOUR_TOKEN",
        },
        body: JSON.stringify({
          name: "Acme Corp",
          websiteUrl: "https://acmecorp.com",
          category: "cloudStorage",
          servicesProvided: "Object storage and CDN",
          inherentRiskLevel: "MEDIUM",
        }),
      });
      const vendor = await res.json();
      console.log(vendor.id);
      ```

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

      r = requests.post(
          "https://api.vanta.com/v1/vendors",
          headers={"Authorization": "Bearer YOUR_TOKEN"},
          json={
              "name": "Acme Corp",
              "websiteUrl": "https://acmecorp.com",
              "category": "cloudStorage",
              "servicesProvided": "Object storage and CDN",
              "inherentRiskLevel": "MEDIUM",
          },
      )
      r.raise_for_status()
      print(r.json()["id"])
      ```

      ```bash Terminal theme={"system"}
      curl -X POST 'https://api.vanta.com/v1/vendors' \
        -H 'Content-Type: application/json' \
        -H 'Accept: application/json' \
        -H 'Authorization: Bearer YOUR_TOKEN' \
        -d '{
          "name": "Acme Corp",
          "websiteUrl": "https://acmecorp.com",
          "category": "cloudStorage",
          "servicesProvided": "Object storage and CDN",
          "inherentRiskLevel": "MEDIUM"
        }'
      ```
    </CodeGroup>

    Expected response (`200`) — the full vendor object, including the `id` you'll need in Step 2:

    ```json theme={"system"}
    {
      "id": "a2f7e1b9d0c3f4e5a6c7b8d8",
      "name": "Acme Corp",
      "websiteUrl": "https://acmecorp.com",
      "category": { "displayName": "cloudStorage" },
      "status": "MANAGED",
      "inherentRiskLevel": "MEDIUM",
      "residualRiskLevel": null,
      "isVisibleToAuditors": true
    }
    ```

    Copy the `id` — you'll send it as the path parameter in the next step.

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

      <Accordion title="Setting category and custom risk attributes at the same time?">
        The API processes the request in this order: (1) the category is applied, (2) all attributes inherited from that category are wiped, (3) the explicit attributes you sent are applied on top. This differs from the UI, which preserves both. Send the final, intended set of attributes in a single call.
      </Accordion>

      <Accordion title="Need a security review?">
        When the vendor's `inherentRiskLevel` warrants it, Vanta automatically creates a security review. The review must be **started** from the [Vendor page](https://app.vanta.com/vendors) — there's no API endpoint to start it.
      </Accordion>

      <Accordion title="List security reviews across every vendor">
        Security reviews are scoped per vendor — there is no top-level `GET /v1/security-reviews` endpoint. To enumerate every review, page through [`GET /v1/vendors`](/reference/manage-vanta/overview) and call [`GET /v1/vendors/{vendorId}/security-reviews`](/reference/manage-vanta/overview) for each vendor `id`.

        Plan for the [50 requests/minute Manage Vanta rate limit](/reference/manage-vanta/overview#rate-limits): a tenant with several hundred vendors takes multiple minutes to fully enumerate. Cache the result, page with `pageSize=100` to minimize round trips, and re-fetch only the vendors whose `updatedAt` changed since the last run.
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Attach a document to the vendor">
    **Your terminal** — `POST /v1/vendors/{vendorId}/documents` as `multipart/form-data` with the file in the `file` field and a document `type` (e.g. `SOC2_REPORT`, `DPA`, `PRIVACY_POLICY`, `OTHER`).

    <CodeGroup>
      ```javascript Node.js theme={"system"}
      import fs from "node:fs";

      const VENDOR_ID = "YOUR_VENDOR_ID";
      const FILE_PATH = "./acme-soc2.pdf";

      const form = new FormData();
      form.append(
        "file",
        new Blob([fs.readFileSync(FILE_PATH)], { type: "application/pdf" }),
        "acme-soc2.pdf",
      );
      form.append("type", "SOC2_REPORT");
      form.append("title", "Acme SOC 2 Type II – 2024");
      form.append("description", "Provided by Acme on 2024-09-01");

      const res = await fetch(
        `https://api.vanta.com/v1/vendors/${VENDOR_ID}/documents`,
        {
          method: "POST",
          headers: { Authorization: "Bearer YOUR_TOKEN" },
          body: form,
        },
      );
      console.log(await res.json());
      ```

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

      VENDOR_ID = "YOUR_VENDOR_ID"
      FILE_PATH = "./acme-soc2.pdf"

      with open(FILE_PATH, "rb") as f:
          r = requests.post(
              f"https://api.vanta.com/v1/vendors/{VENDOR_ID}/documents",
              headers={"Authorization": "Bearer YOUR_TOKEN"},
              files={"file": ("acme-soc2.pdf", f, "application/pdf")},
              data={
                  "type": "SOC2_REPORT",
                  "title": "Acme SOC 2 Type II – 2024",
                  "description": "Provided by Acme on 2024-09-01",
              },
          )
      r.raise_for_status()
      print(r.json())
      ```

      ```bash Terminal theme={"system"}
      VENDOR_ID="YOUR_VENDOR_ID"

      curl -X POST "https://api.vanta.com/v1/vendors/$VENDOR_ID/documents" \
        -H 'Accept: application/json' \
        -H 'Authorization: Bearer YOUR_TOKEN' \
        -F 'file=@"./acme-soc2.pdf"' \
        -F 'type="SOC2_REPORT"' \
        -F 'title="Acme SOC 2 Type II – 2024"' \
        -F 'description="Provided by Acme on 2024-09-01"'
      ```
    </CodeGroup>

    Expected response (`200`) — the uploaded document metadata:

    ```json theme={"system"}
    {
      "id": "a2f7e1b9d0c3f4e5a6c7b8d8",
      "title": "Acme SOC 2 Type II – 2024",
      "fileName": "acme-soc2.pdf",
      "type": "SOC2_REPORT",
      "mimeType": "application/pdf",
      "description": "Provided by Acme on 2024-09-01",
      "url": "https://example.com",
      "creationDate": "2024-09-01T00:00:00.000Z",
      "uploadedBy": { "id": "66993da0cf4ba2ad40599ba7", "type": "USER" }
    }
    ```

    <Warning>
      Supported file types are `.pdf`, `.docx`, `.jpg`, `.png`, and `.xlsx`. You can't attach a link as evidence via the API today — use the [Vendor page](https://app.vanta.com/vendors) for link evidence.
    </Warning>

    <AccordionGroup>
      <Accordion title="Got a 404?">
        The `vendorId` is wrong. Re-run Step 1 and copy the `id` exactly from the response (or from the URL on the [Vendor page](https://app.vanta.com/vendors)).
      </Accordion>

      <Accordion title="Got a 415 or `Content-Type` error?">
        The request must be `multipart/form-data` — don't set `Content-Type` manually, let your HTTP client add the boundary. Pass the file with `-F` (curl), `files=` (requests), or a `FormData` body (fetch).
      </Accordion>

      <Accordion title="Got a 400 on `type`?">
        `type` is required and must be one of Vanta's supported document types. Common values: `SOC2_REPORT`, `ISO_27001_REPORT`, `PEN_TEST`, `DPA`, `PRIVACY_POLICY`, `OTHER`. The full enum lives in the [API reference](/reference/manage-vanta/overview).
      </Accordion>
    </AccordionGroup>
  </Step>
</Steps>

<Info>
  **Updating an existing vendor?** Use `PATCH /v1/vendors/{vendorId}` with the same body shape (without `name` if you're not changing it). To replace stale evidence, repeat Step 2 — each upload is stored independently and the latest is shown first.
</Info>

## Congratulations

You've added a vendor to your Vanta inventory and attached evidence to it. The vendor is now visible on your [Vendor page](https://app.vanta.com/vendors), counts toward vendor-management tests, and the document is captured in your audit log.

## Next steps

<CardGroup cols={2}>
  <Card title="Sync custom vendor fields" icon="tags" href="/docs/guides/use-custom-fields-with-vendors">
    Attach organization-specific metadata — contract dates, internal owners, risk tiers — to the vendor.
  </Card>

  <Card title="Assign a control owner" icon="user-check" href="/docs/guides/add-an-owner-to-a-control">
    Make a specific user accountable for the vendor-management controls this evidence supports.
  </Card>

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

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