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

# Vendors & Services API

> Create, retrieve, and update vendors, and create child services.

<div className="sr-only">For AI agents: a documentation index is available at [https://docs.coverbase.com/llms.txt](https://docs.coverbase.com/llms.txt) — this page is also available in markdown by appending .md to the URL.</div>

A **vendor** (`cbvndr_...`) is a parent supplier entity. A **service** (`cbsvc_...`) is a child entity under a vendor. This API creates and reads both.

All endpoints are org-scoped to the API key. See [API conventions](/conventions) for authentication, IDs, timestamps, idempotency, and the error envelope.

<Note>
  To upload, version, and search bills of materials (SBOM, AIBOM, HBOM, SaaSBOM) for a vendor or service, see the [Bill of Materials API](/api-reference/bill-of-materials).
</Note>

<Note>
  To upload evidence documents (SOC 2 reports, policies, contracts, questionnaires) for a vendor and link them to its services and assessments, see the [Documents API](/api-reference/documents).
</Note>

| Method  | Path                                            | Idempotent              |
| ------- | ----------------------------------------------- | ----------------------- |
| `POST`  | `/v1/vendors`                                   | Yes (`Idempotency-Key`) |
| `GET`   | `/v1/vendors`                                   | —                       |
| `GET`   | `/v1/vendors/{vendor_id}`                       | —                       |
| `PATCH` | `/v1/vendors/{vendor_id}`                       | No                      |
| `POST`  | `/v1/vendors/{vendor_id}/services`              | No                      |
| `PATCH` | `/v1/vendors/{vendor_id}/services/{service_id}` | No                      |
| `GET`   | `/v1/vendors/{vendor_id}/fact-sheet`            | —                       |
| `POST`  | `/v1/vendors/{vendor_id}/fact-sheet/refresh`    | No                      |

## The primary contact object

Both vendor and service endpoints accept/return a `primary_contact`. On a vendor it is the address the inherent-risk-questionnaire portal invite (and other vendor emails) are sent to; on a service it is the service-level point-of-contact.

| Field          | Type           | Notes                                        |
| -------------- | -------------- | -------------------------------------------- |
| `email`        | string         | **Required**, validated as an email address. |
| `name`         | string \| null | Contact name.                                |
| `title`        | string \| null | Contact title.                               |
| `phone_number` | string \| null | Contact phone.                               |

On read, `primary_contact` is derived from the stored contact personnel (first entry with an email) — for vendors it falls back to the vendor's security contact email. It is `null` if no contact is set.

## Assigning people (analysts, owners, watchers)

Vendors and services accept optional personnel assignments as arrays of user IDs (`cbuser_...`). All are **optional**; on create, omit them to assign no one. On update (`PATCH`), supplying an array **replaces** the existing set — pass `[]` to clear it, or omit the field to leave it unchanged.

| Field                    | Vendor | Service | Meaning                                                              |
| ------------------------ | ------ | ------- | -------------------------------------------------------------------- |
| `risk_analyst_ids`       | ✓      | —       | Risk analysts. **Vendor-only** — services do not have risk analysts. |
| `relationship_owner_ids` | ✓      | ✓       | Relationship owners.                                                 |
| `watcher_ids`            | ✓      | ✓       | Watchers (users who follow the vendor/service for notifications).    |

<Tip>
  These fields take Coverbase user IDs, not emails. Syncing from an external system (e.g. ServiceNow) that keys on email? Use the [Users API](/api-reference/users#list-users-and-look-up-by-email) to resolve an email to its `cbuser_...` ID — `GET /v1/users?email=` looks one up, and `POST /v1/users` provisions the user if they don't exist yet (returning the existing user if they do).
</Tip>

## Create a vendor

<ParamField path="method" type="POST">
  `POST /v1/vendors`
</ParamField>

Creates a parent vendor. Returns `201 Created` with the full vendor object.

### Request body

<ParamField body="name" type="string" required>
  Vendor name.
</ParamField>

<ParamField body="website" type="string">
  Vendor website URL.
</ParamField>

<ParamField body="description" type="string">
  Vendor description.
</ParamField>

<ParamField body="hq_location" type="string">
  Headquarters location.
</ParamField>

<ParamField body="use_case" type="string">
  How the vendor is used within the organization.
</ParamField>

<ParamField body="external_id" type="string">
  Your system's identifier for this vendor (e.g. an Ariba request ID). Stored and echoed back; useful for correlation.
</ParamField>

<ParamField body="risk_analyst_ids" type="string[]">
  Risk analyst user IDs (`cbuser_...`) to assign. See [assigning people](#assigning-people-analysts-owners-watchers).
</ParamField>

<ParamField body="relationship_owner_ids" type="string[]">
  Relationship owner user IDs (`cbuser_...`) to assign.
</ParamField>

<ParamField body="watcher_ids" type="string[]">
  Watcher user IDs (`cbuser_...`) to assign.
</ParamField>

<ParamField body="primary_contact" type="object">
  The vendor point-of-contact (see [the primary contact object](#the-primary-contact-object)). If supplied, `email` is required and validated.
</ParamField>

### Headers

<ParamField header="Authorization" type="string" required>
  `Bearer ak_...`
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  Optional. Replaying the same key within 24 hours returns the original `201` body. See [Idempotency](/conventions#idempotency).
</ParamField>

### Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://sandbox.api.coverbase.app/v1/vendors" \
    -H "Authorization: Bearer ak_live_xxx" \
    -H "Idempotency-Key: acme-vendor-test1" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "ACME_Sup_Test1",
      "website": "https://supplier1.example.com",
      "use_case": "Demo supplier for Acme Inc sandbox",
      "external_id": "ariba-req-7821"
    }'
  ```

  ```python Python theme={null}
  import os
  import requests

  API_KEY = os.environ["COVERBASE_API_KEY"]
  BASE_URL = "https://sandbox.api.coverbase.app"

  resp = requests.post(
      f"{BASE_URL}/v1/vendors",
      headers={
          "Authorization": f"Bearer {API_KEY}",
          "Idempotency-Key": "acme-vendor-test1",
          "Content-Type": "application/json",
      },
      json={
          "name": "ACME_Sup_Test1",
          "website": "https://supplier1.example.com",
          "use_case": "Demo supplier for Acme Inc sandbox",
          "external_id": "ariba-req-7821",
      },
      timeout=30,
  )
  resp.raise_for_status()
  print(resp.json())
  ```
</CodeGroup>

### Example response

`201 Created` — the full vendor object (see [response fields](#vendor-object) below):

```json theme={null}
{
  "id": "cbvndr_e448ba62882143f3ba0c140bb2e30162",
  "external_id": "ariba-req-7821",
  "name": "ACME_Sup_Test1",
  "website": "https://supplier1.example.com",
  "description": null,
  "hq_location": null,
  "status": null,
  "status_id": null,
  "tags": [],
  "risk_analyst_ids": [],
  "relationship_owner_ids": [],
  "watcher_ids": [],
  "inherent_risk_level_id": null,
  "residual_risk_level_id": null,
  "engagement_type": null,
  "use_case": "Demo supplier for Acme Inc sandbox",
  "tier": null,
  "primary_contact": null,
  "services": [],
  "created_at": 1746576000,
  "updated_at": 1746576000
}
```

### Error responses

| Status | Body                                                       | When                                                                          |
| ------ | ---------------------------------------------------------- | ----------------------------------------------------------------------------- |
| 400    | `{"detail": {"code": "invalid_vendor", "message": "..."}}` | Creation rejected by a business rule.                                         |
| 422    | Standard validation error                                  | `name` missing, body malformed, or `primary_contact.email` not a valid email. |

## Create a child service

<ParamField path="method" type="POST">
  `POST /v1/vendors/{vendor_id}/services`
</ParamField>

Creates a service under an existing vendor. Returns `201 Created`.

<Note>
  This endpoint does **not** honor `Idempotency-Key` — sending the header is harmless but has no effect. Retrying after a successful create makes a second service.
</Note>

### Path parameters

<ParamField path="vendor_id" type="string" required>
  The parent vendor ID (`cbvndr_...`).
</ParamField>

### Request body

<ParamField body="name" type="string" required>
  Service name.
</ParamField>

<ParamField body="description" type="string">
  Service description.
</ParamField>

<ParamField body="external_id" type="string">
  Your system's identifier for this service.
</ParamField>

<ParamField body="tag_ids" type="string[]">
  Tag IDs (`cbtag_...`) to attach to the service.
</ParamField>

<ParamField body="relationship_owner_ids" type="string[]">
  Relationship owner user IDs (`cbuser_...`) to assign. See [assigning people](#assigning-people-analysts-owners-watchers).
</ParamField>

<ParamField body="watcher_ids" type="string[]">
  Watcher user IDs (`cbuser_...`) to assign.
</ParamField>

<ParamField body="primary_contact" type="object">
  The service point-of-contact (see [the primary contact object](#the-primary-contact-object)). If supplied, `email` is required and validated.
</ParamField>

<Note>
  Services do **not** have risk analysts — risk analysts are assigned on the parent vendor. Sending `risk_analyst_ids` to a service endpoint has no effect.
</Note>

### Example request

```bash cURL theme={null}
curl -X POST "https://sandbox.api.coverbase.app/v1/vendors/cbvndr_e448ba62882143f3ba0c140bb2e30162/services" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ACME_Sup_Test1 — Primary Service",
    "relationship_owner_ids": ["cbuser_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"],
    "watcher_ids": ["cbuser_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"],
    "primary_contact": { "email": "service-owner@supplier1.example.com" }
  }'
```

### Example response

`201 Created` (see [the service object](#service-object)):

```json theme={null}
{
  "id": "cbsvc_3c2b1a098f7e6d5c4b3a2918374655ab",
  "vendor_id": "cbvndr_e448ba62882143f3ba0c140bb2e30162",
  "name": "ACME_Sup_Test1 — Primary Service",
  "description": null,
  "external_id": null,
  "tags": [],
  "relationship_owner_ids": ["cbuser_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"],
  "watcher_ids": ["cbuser_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"],
  "primary_contact": { "email": "service-owner@supplier1.example.com", "name": null, "title": null, "phone_number": null },
  "created_at": 1746576000
}
```

### Service object

<ResponseField name="id" type="string">Service ID (`cbsvc_...`).</ResponseField>
<ResponseField name="vendor_id" type="string">The parent vendor ID.</ResponseField>
<ResponseField name="name" type="string">Service name.</ResponseField>
<ResponseField name="description" type="string | null">Service description.</ResponseField>
<ResponseField name="external_id" type="string | null">Your external identifier, if supplied.</ResponseField>
<ResponseField name="tags" type="string[]">Tag value strings (empty array if none).</ResponseField>
<ResponseField name="relationship_owner_ids" type="string[]">Relationship owner user IDs (`cbuser_...`). Empty array if none.</ResponseField>
<ResponseField name="watcher_ids" type="string[]">Watcher user IDs (`cbuser_...`). Empty array if none.</ResponseField>
<ResponseField name="primary_contact" type="object | null">The service point-of-contact (see [the primary contact object](#the-primary-contact-object)), or `null`.</ResponseField>
<ResponseField name="created_at" type="integer">Unix timestamp (seconds).</ResponseField>

### Error responses

| Status | Body                                                                       | When                                                       |
| ------ | -------------------------------------------------------------------------- | ---------------------------------------------------------- |
| 400    | `{"detail": {"code": "invalid_service", "message": "..."}}`                | Creation rejected by a business rule.                      |
| 404    | `{"detail": {"code": "vendor_not_found", "message": "Vendor not found."}}` | `vendor_id` does not exist or is not in the API key's org. |
| 422    | Standard validation error                                                  | `name` missing or body malformed.                          |

## Update a service

<ParamField path="method" type="PATCH">
  `PATCH /v1/vendors/{vendor_id}/services/{service_id}`
</ParamField>

Updates a service. Only included fields change. Returns the updated [service object](#service-object).

### Path parameters

<ParamField path="vendor_id" type="string" required>
  The parent vendor ID (`cbvndr_...`).
</ParamField>

<ParamField path="service_id" type="string" required>
  The service ID (`cbsvc_...`). Must belong to `vendor_id`.
</ParamField>

### Request body

<ParamField body="name" type="string">Service name.</ParamField>
<ParamField body="description" type="string">Service description.</ParamField>
<ParamField body="tag_ids" type="string[]">Replace the service's tag set (tag IDs `cbtag_...`).</ParamField>
<ParamField body="relationship_owner_ids" type="string[]">Replace the relationship owner set (user IDs `cbuser_...`). Pass `[]` to clear.</ParamField>
<ParamField body="watcher_ids" type="string[]">Replace the watcher set (user IDs `cbuser_...`). Pass `[]` to clear.</ParamField>
<ParamField body="primary_contact" type="object">Replace the service point-of-contact (see [the primary contact object](#the-primary-contact-object)). When supplied, `email` is required and validated.</ParamField>

<Note>
  `external_id` is set only at creation and is not updatable here. Services do not have risk analysts.
</Note>

### Example request

```bash cURL theme={null}
curl -X PATCH "https://sandbox.api.coverbase.app/v1/vendors/cbvndr_e448ba62882143f3ba0c140bb2e30162/services/cbsvc_3c2b1a098f7e6d5c4b3a2918374655ab" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "name": "ACME_Sup_Test1 — Renamed Service" }'
```

### Error responses

| Status | Body                                                                         | When                                                                                           |
| ------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| 400    | `{"detail": {"code": "invalid_service", "message": "..."}}`                  | Update rejected by a business rule.                                                            |
| 404    | `{"detail": {"code": "service_not_found", "message": "Service not found."}}` | The service does not exist, or does not belong to `vendor_id`, or is not in the API key's org. |
| 422    | Standard validation error                                                    | Body failed schema validation.                                                                 |

## List vendors

<ParamField path="method" type="GET">
  `GET /v1/vendors`
</ParamField>

Lists vendors in the API key's org, newest first. Without filters it returns
every vendor, paginated. Use it to enumerate vendors, resolve a vendor by name,
or map your own identifier to a Coverbase vendor ID.

<Note>
  List rows are a **lightweight summary** — they omit the nested `services` and
  the personnel-assignment arrays. Fetch [`GET /v1/vendors/{vendor_id}`](#retrieve-a-vendor)
  for the full object.
</Note>

### Query parameters

<ParamField query="name" type="string">
  Case-insensitive substring match on the vendor (or service) name.
</ParamField>

<ParamField query="external_id" type="string">
  Exact match on the vendor's `external_id` — handy for mapping your system's
  identifier back to a Coverbase vendor.
</ParamField>

<ParamField query="limit" type="integer" default="50">
  Page size, 1–200.
</ParamField>

<ParamField query="offset" type="integer" default="0">
  Number of rows to skip, for pagination.
</ParamField>

### Example request

```bash cURL theme={null}
curl -X GET "https://sandbox.api.coverbase.app/v1/vendors?name=acme&limit=50" \
  -H "Authorization: Bearer ak_live_xxx"
```

### Example response

`200 OK`:

```json theme={null}
{
  "items": [
    {
      "id": "cbvndr_e448ba62882143f3ba0c140bb2e30162",
      "external_id": "ariba-req-7821",
      "name": "ACME_Sup_Test1",
      "website": "https://supplier1.example.com",
      "description": null,
      "hq_location": null,
      "status": "Active",
      "status_id": "cbst_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
      "tags": ["saas"],
      "use_case": "Customer support ticketing",
      "tier": "tier_2",
      "primary_contact": null,
      "created_at": 1732292930,
      "updated_at": 1746575800
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}
```

<ResponseField name="items" type="object[]">
  The page of vendor summaries. Each carries `id`, `external_id`, `name`,
  `website`, `description`, `hq_location`, `status`, `status_id`, `tags`,
  `use_case`, `tier`, `primary_contact`, `created_at`, and `updated_at`. (No
  `services` or personnel arrays — see the note above.)
</ResponseField>

<ResponseField name="total" type="integer">Total vendors matching the filters (across all pages).</ResponseField>
<ResponseField name="limit" type="integer">The page size used.</ResponseField>
<ResponseField name="offset" type="integer">The offset used.</ResponseField>

## Retrieve a vendor

<ParamField path="method" type="GET">
  `GET /v1/vendors/{vendor_id}`
</ParamField>

### Path parameters

<ParamField path="vendor_id" type="string" required>
  The vendor ID (`cbvndr_...`).
</ParamField>

### Example request

```bash cURL theme={null}
curl -X GET "https://sandbox.api.coverbase.app/v1/vendors/cbvndr_e448ba62882143f3ba0c140bb2e30162" \
  -H "Authorization: Bearer ak_live_xxx"
```

### Example response

```json theme={null}
{
  "id": "cbvndr_e448ba62882143f3ba0c140bb2e30162",
  "external_id": "ariba-req-7821",
  "name": "ACME_Sup_Test1",
  "website": "https://supplier1.example.com",
  "description": "Enterprise customer support tooling provider",
  "hq_location": "San Francisco, CA",
  "status": "Active",
  "status_id": "cbst_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "tags": ["processes_pii", "saas"],
  "risk_analyst_ids": ["cbuser_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"],
  "relationship_owner_ids": ["cbuser_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"],
  "watcher_ids": ["cbuser_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"],
  "inherent_risk_level_id": "cbsclvl_7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "residual_risk_level_id": "cbsclvl_3f4e5d6c7b8a9b0c1d2e3f4a5b6c7d8e",
  "engagement_type": "saas_platform",
  "use_case": "Customer support ticketing",
  "tier": "tier_2",
  "primary_contact": {
    "email": "security@supplier1.example.com",
    "name": "Jordan Lee",
    "title": "CISO",
    "phone_number": "+1-555-0100"
  },
  "services": [
    {
      "id": "cbsvc_3c2b1a098f7e6d5c4b3a2918374655ab",
      "vendor_id": "cbvndr_e448ba62882143f3ba0c140bb2e30162",
      "name": "ACME_Sup_Test1 — Primary Service",
      "description": null,
      "external_id": null,
      "tags": [],
      "relationship_owner_ids": [],
      "watcher_ids": [],
      "primary_contact": null,
      "created_at": 1746576000
    }
  ],
  "created_at": 1732292930,
  "updated_at": 1746575800
}
```

### Vendor object

<ResponseField name="id" type="string">Vendor ID (`cbvndr_...`).</ResponseField>
<ResponseField name="external_id" type="string | null">Your external identifier, if set.</ResponseField>
<ResponseField name="name" type="string">Vendor name.</ResponseField>
<ResponseField name="website" type="string | null">Vendor website URL.</ResponseField>
<ResponseField name="description" type="string | null">Vendor description.</ResponseField>
<ResponseField name="hq_location" type="string | null">Headquarters location.</ResponseField>
<ResponseField name="status" type="string | null">Current status as a plain label string (e.g. `"Active"`). Org-customizable. `null` if unset.</ResponseField>
<ResponseField name="status_id" type="string | null">ID (`cbst_...`) of the current status record.</ResponseField>
<ResponseField name="tags" type="string[]">Tag value strings (empty array if none).</ResponseField>
<ResponseField name="risk_analyst_ids" type="string[]">Assigned risk analyst user IDs (`cbuser_...`).</ResponseField>
<ResponseField name="relationship_owner_ids" type="string[]">Relationship owner user IDs (`cbuser_...`).</ResponseField>
<ResponseField name="watcher_ids" type="string[]">Watcher user IDs (`cbuser_...`).</ResponseField>
<ResponseField name="inherent_risk_level_id" type="string | null">Scale level ID (`cbsclvl_...`) for inherent risk.</ResponseField>
<ResponseField name="residual_risk_level_id" type="string | null">Scale level ID (`cbsclvl_...`) for residual risk.</ResponseField>
<ResponseField name="engagement_type" type="string | null">Type of engagement with this vendor.</ResponseField>
<ResponseField name="use_case" type="string | null">How the vendor is used.</ResponseField>
<ResponseField name="tier" type="string | null">Derived vendor tier (e.g. `tier_1`, `tier_2`). Set by workflow tiering actions; `null` until derived.</ResponseField>
<ResponseField name="primary_contact" type="object | null">The vendor point-of-contact (see [the primary contact object](#the-primary-contact-object)), or `null`.</ResponseField>
<ResponseField name="services" type="object[]">The vendor's child services, each a [service object](#service-object). Empty array if none.</ResponseField>
<ResponseField name="created_at" type="integer">Unix timestamp (seconds) of creation.</ResponseField>
<ResponseField name="updated_at" type="integer">Unix timestamp (seconds) of last update.</ResponseField>

### Error responses

| Status | Body                                                                       | When                                                       |
| ------ | -------------------------------------------------------------------------- | ---------------------------------------------------------- |
| 404    | `{"detail": {"code": "vendor_not_found", "message": "Vendor not found."}}` | `vendor_id` does not exist or is not in the API key's org. |

## Update a vendor

<ParamField path="method" type="PATCH">
  `PATCH /v1/vendors/{vendor_id}`
</ParamField>

Updates one or more fields. Only included fields change; omitted fields are untouched. The full, refreshed vendor object is returned.

<Note>
  Every body field is optional. Sending `null` for a field is treated the same as omitting it (no change) — there is no clear-to-null behavior.
</Note>

### Request body

<ParamField body="name" type="string">Vendor name.</ParamField>
<ParamField body="website" type="string">Vendor website URL.</ParamField>
<ParamField body="description" type="string">Vendor description.</ParamField>
<ParamField body="hq_location" type="string">Headquarters location.</ParamField>
<ParamField body="status_id" type="string">ID (`cbst_...`) of a status record to transition to.</ParamField>
<ParamField body="tag_ids" type="string[]">Replace the entire tag set, using tag IDs (`cbtag_...`).</ParamField>
<ParamField body="risk_analyst_ids" type="string[]">Replace the risk analyst set (user IDs `cbuser_...`). Pass `[]` to clear.</ParamField>
<ParamField body="relationship_owner_ids" type="string[]">Replace the relationship owner set (user IDs `cbuser_...`). Pass `[]` to clear.</ParamField>
<ParamField body="watcher_ids" type="string[]">Replace the watcher set (user IDs `cbuser_...`). Pass `[]` to clear.</ParamField>
<ParamField body="inherent_risk_level_id" type="string">Scale level ID (`cbsclvl_...`) for inherent risk.</ParamField>
<ParamField body="residual_risk_level_id" type="string">Scale level ID (`cbsclvl_...`) for residual risk.</ParamField>
<ParamField body="use_case" type="string">How the vendor is used.</ParamField>
<ParamField body="engagement_type" type="string">Type of engagement.</ParamField>
<ParamField body="primary_contact" type="object">Replace the vendor point-of-contact (see [the primary contact object](#the-primary-contact-object)). When supplied, `email` is required and validated.</ParamField>

<Note>
  `external_id` and `tier` are **not** updatable through this endpoint (`external_id` is set only at creation; `tier` is derived by workflows).
</Note>

### Example request

```bash cURL theme={null}
curl -X PATCH "https://sandbox.api.coverbase.app/v1/vendors/cbvndr_e448ba62882143f3ba0c140bb2e30162" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "status_id": "cbst_offboarding7f8e9d0c1b2a3f4e5d6c7b8a9f0e1d2c",
    "use_case": "Legacy system — winding down"
  }'
```

The response is the full vendor object (same shape as [Retrieve a vendor](#vendor-object)).

### Error responses

| Status | Body                                                                       | When                                            |
| ------ | -------------------------------------------------------------------------- | ----------------------------------------------- |
| 400    | `{"detail": {"code": "invalid_update", "message": "..."}}`                 | Update rejected (e.g. an invalid reference ID). |
| 404    | `{"detail": {"code": "vendor_not_found", "message": "Vendor not found."}}` | `vendor_id` not found / not in org.             |
| 422    | Standard validation error                                                  | Body failed schema validation.                  |

## The vendor fact sheet

The **fact sheet** is AI-generated company intelligence about a vendor — company profile, products, public perception, financials, leadership, compliance/legal posture, and security posture. Coverbase assembles it from public sources; in the dashboard it powers the vendor "Fact Sheet" tab and its PDF export. These two endpoints let you read that data and trigger a regeneration programmatically.

Generation is **asynchronous**. Trigger it with the refresh endpoint, then poll the fact sheet endpoint until `status` is `"completed"`.

| `status`      | Meaning                                                                  |
| ------------- | ------------------------------------------------------------------------ |
| `null`        | Never generated. Call the refresh endpoint to populate it.               |
| `"pending"`   | Generation is in progress.                                               |
| `"completed"` | The most recent generation run finished successfully.                    |
| `"failed"`    | The most recent generation run failed. Trigger another refresh to retry. |

### Retrieve a vendor's fact sheet

<ParamField path="method" type="GET">
  `GET /v1/vendors/{vendor_id}/fact-sheet`
</ParamField>

Returns the current fact sheet for a vendor. A vendor that has never had a fact sheet generated returns `200` with `status: null` and every section `null` — it is **not** a `404`.

#### Path parameters

<ParamField path="vendor_id" type="string" required>
  The vendor ID (`cbvndr_...`).
</ParamField>

#### Example request

```bash cURL theme={null}
curl -X GET "https://sandbox.api.coverbase.app/v1/vendors/cbvndr_e448ba62882143f3ba0c140bb2e30162/fact-sheet" \
  -H "Authorization: Bearer ak_live_xxx"
```

#### Example response

`200 OK`:

```json theme={null}
{
  "vendor_id": "cbvndr_e448ba62882143f3ba0c140bb2e30162",
  "entity_id": "cbentity_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "status": "completed",
  "progress": ["company profile", "products", "security posture"],
  "generated_at": 1746575800,
  "company_profile": {
    "name": "ACME_Sup_Test1",
    "website": "https://supplier1.example.com",
    "industry": "Customer support software",
    "hq_location": "San Francisco, CA",
    "founded_year": 2014,
    "description": "Enterprise customer support tooling provider"
  },
  "products": { "products": [{ "name": "Helpdesk", "description": "Ticketing platform" }] },
  "public_perception": { "news_mentions": [], "competitors": [] },
  "financials": { "public_private": "private", "market_cap_usd": null },
  "people": { "leadership": [{ "name": "Jordan Lee", "title": "CEO" }] },
  "compliance_legal": { "legal_actions": [] },
  "security_posture": { "trust_center_url": "https://trust.supplier1.example.com", "security_incidents": [] }
}
```

#### Fact sheet object

<ResponseField name="vendor_id" type="string">The vendor ID (`cbvndr_...`).</ResponseField>
<ResponseField name="entity_id" type="string | null">ID of the underlying entity that holds the fact sheet data. `null` if the vendor is not yet linked to an entity.</ResponseField>
<ResponseField name="status" type="string | null">Generation status — `null`, `"pending"`, `"completed"`, or `"failed"` (see the [status table](#the-vendor-fact-sheet) above).</ResponseField>
<ResponseField name="progress" type="string[]">Section names completed so far in the in-progress / most recent run. Empty array if none.</ResponseField>
<ResponseField name="generated_at" type="integer | null">Unix timestamp (seconds) the fact sheet was last updated. `null` if never generated.</ResponseField>
<ResponseField name="company_profile" type="object | null">Company profile (name, website, industry, HQ, founding year, headcount, revenue, etc.). `null` until generated.</ResponseField>
<ResponseField name="products" type="object | null">Known products and offerings. `null` until generated.</ResponseField>
<ResponseField name="public_perception" type="object | null">News mentions, third-party reviews (BBB, Trustpilot, CFPB), and competitors. `null` until generated.</ResponseField>
<ResponseField name="financials" type="object | null">Public/private status, market cap, ticker/exchange, funding, and transactions. `null` until generated.</ResponseField>
<ResponseField name="people" type="object | null">Leadership and headcount estimate. `null` until generated.</ResponseField>
<ResponseField name="compliance_legal" type="object | null">Legal actions, enforcement actions, and data-privacy certifications. `null` until generated.</ResponseField>
<ResponseField name="security_posture" type="object | null">Trust center URL, security contact, and known security incidents. `null` until generated.</ResponseField>

<Note>
  Section objects are best-effort and their internal shape may evolve as Coverbase improves enrichment. Treat the presence of each field defensively; any individual section can be `null` even when `status` is `"completed"` if no data was found for it.
</Note>

#### Error responses

| Status | Body                                                                       | When                                                       |
| ------ | -------------------------------------------------------------------------- | ---------------------------------------------------------- |
| 404    | `{"detail": {"code": "vendor_not_found", "message": "Vendor not found."}}` | `vendor_id` does not exist or is not in the API key's org. |

### Refresh a vendor's fact sheet

<ParamField path="method" type="POST">
  `POST /v1/vendors/{vendor_id}/fact-sheet/refresh`
</ParamField>

Queues a (re)generation of the vendor's fact sheet and returns immediately with `202 Accepted` and `status: "pending"`. Existing fact sheet data is cleared at the start of the run, so a `GET` made right after a refresh reflects the `pending` state. Poll the [fact sheet endpoint](#retrieve-a-vendors-fact-sheet) until `status` is `"completed"`.

<Note>
  This endpoint is not idempotent — each call queues a fresh generation run.
</Note>

#### Path parameters

<ParamField path="vendor_id" type="string" required>
  The vendor ID (`cbvndr_...`).
</ParamField>

#### Example request

```bash cURL theme={null}
curl -X POST "https://sandbox.api.coverbase.app/v1/vendors/cbvndr_e448ba62882143f3ba0c140bb2e30162/fact-sheet/refresh" \
  -H "Authorization: Bearer ak_live_xxx"
```

#### Example response

`202 Accepted`:

```json theme={null}
{
  "vendor_id": "cbvndr_e448ba62882143f3ba0c140bb2e30162",
  "entity_id": "cbentity_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "status": "pending",
  "message": "Fact sheet regeneration has been queued."
}
```

<ResponseField name="vendor_id" type="string">The vendor ID (`cbvndr_...`).</ResponseField>
<ResponseField name="entity_id" type="string | null">ID of the underlying entity, or `null` when the vendor is being linked to one for the first time as part of this run.</ResponseField>
<ResponseField name="status" type="string">Always `"pending"` on a successful enqueue.</ResponseField>
<ResponseField name="message" type="string">Human-readable acknowledgement.</ResponseField>

#### Error responses

| Status | Body                                                                       | When                                                       |
| ------ | -------------------------------------------------------------------------- | ---------------------------------------------------------- |
| 404    | `{"detail": {"code": "vendor_not_found", "message": "Vendor not found."}}` | `vendor_id` does not exist or is not in the API key's org. |
