Find the insights and best practices about our product.
Vendors

These endpoints let you list, look up, create, update, and retire the vendors recorded for your tenant. Use them to keep vendor records in step with a procurement or third-party risk management system. They read and write the same vendor records shown in My Vendors.

Vendor Record

Every vendor endpoint returns the same vendor record. Create returns the record inside a wrapper that also lists possible duplicates.

Response Fields

Field

Type

Description

vendorId

GUID

Cranium's identifier for the vendor. Record it against your own identifier so later updates need no search.

name

string

Display name.

legalName

string, nullable

Legal entity name.

primaryDomain

string, nullable

The vendor's main domain. Stored as sent and not validated as a domain. Used for duplicate matching.

website

string, nullable

Website URL.

description

string, nullable

Free text.

status

string

Always Active. Retired vendors are never returned.

createdAt

ISO 8601, UTC

When the vendor was created.

updatedAt

ISO 8601, UTC

When the vendor was last changed. The sync cursor tracks this value. Retiring or editing a custom field definition doesn't change it, so an updatedAfter sync doesn't report that change.

customAttributes

object

The vendor's values for active custom vendor fields, keyed by field key. {} when the vendor has no values.

publisherNames

string array

Not populated yet. Always [].

sharedAiCardCount

integer

Not populated yet. Always 0.

Every timestamp is UTC. Timestamps read back from storage omit the Z designator, while timestamps set during a create or update include it, so a single response can contain both forms. Parse every timestamp as UTC, especially before sending one back as updatedAfter.

List Vendors

Returns a cursor-paginated feed of your tenant's vendors in updatedAt order. Retired vendors never appear.

Request

GET /api/public/vendors

Authentication

Bearer token. See Authentication & Generating Credentials.

Required Permission

Api_Vendors_Read

Query Parameters

Parameter

Type

Description

limit

integer

Results per page. Maximum 200. A higher value returns 400 rather than being reduced.

cursor

string

Omit to start. Pass nextCursor from the previous response to continue.

updatedAfter

ISO 8601

Start the feed from a point in time. Can't be combined with cursor, and cursor must be left out of the query string entirely.

The feed has no search or filter parameters. A cursor can't safely combine with a filter, because changing the filter partway through would return a page from a different result set. Walk the feed once and build a local map from your own identifiers to vendorId. Keep the map current with incremental sync, and upsert on vendorId rather than appending, since an updated vendor returns at its new position in the feed.

Sample Response

json

{
"data": [
{
"vendorId": "fbd57db8-159a-4983-a6a2-c6b061843549",
"name": "Acme Labs",
"legalName": null,
"primaryDomain": null,
"website": null,
"description": null,
"status": "Active",
"createdAt": "2026-07-14T16:08:42.409328",
"updatedAt": "2026-07-14T16:08:42.409328",
"customAttributes": {},
"publisherNames": [],
"sharedAiCardCount": 0
},
{
"vendorId": "823cb8a0-cba8-4f3f-8028-011743973b16",
"name": "Globex",
"legalName": null,
"primaryDomain": null,
"website": null,
"description": null,
"status": "Active",
"createdAt": "2026-07-14T16:08:42.887375",
"updatedAt": "2026-07-14T16:08:42.887375",
"customAttributes": {},
"publisherNames": [],
"sharedAiCardCount": 0
}
],
"pagination": {
"limit": 2,
"nextCursor": "eyJ1cGRhdGVkQXQiOiIyMDI2LTA3LTE0VDE2OjA4OjQyLjg4NzM3NTAiLCJpZCI6IjgyM2NiOGEwLWNiYTgtNGYzZi04MDI4LTAxMTc0Mzk3M2IxNiJ9",
"hasMore": true
},
"error": null,
"meta": {
"requestId": "0f330001-bb91-4ff4-87a5-06ad73447769",
"timestamp": "2026-09-28T12:24:10.9092250Z"
}
}

Pass nextCursor as cursor on the next call. When hasMore is false, keep the last nextCursor and send it on your next sync to receive only vendors created or changed since.

Error Responses

  • 400 BAD_REQUEST: limit is above 200, page is supplied, the cursor is malformed, or cursor and updatedAfter are supplied together. To use updatedAfter, leave cursor out of the query string entirely; an empty cursor also returns 400.
  • 401 UNAUTHORIZED: the request is missing a token, or the token lacks Api_Vendors_Read.
  • 500 INTERNAL_ERROR: an unexpected server error.

Retrieve a Vendor

Returns a single vendor in the same record shape as the list.

Request

GET /api/public/vendors/{vendorId}

Authentication

Bearer token. See Authentication & Generating Credentials.

Required Permission

Api_Vendors_Read

Sample Response

json

{
"data": {
"vendorId": "fbd57db8-159a-4983-a6a2-c6b061843549",
"name": "Acme Labs",
"legalName": null,
"primaryDomain": null,
"website": null,
"description": null,
"status": "Active",
"createdAt": "2026-07-14T16:08:42.409328",
"updatedAt": "2026-07-14T16:08:42.409328",
"customAttributes": {},
"publisherNames": [],
"sharedAiCardCount": 0
},
"pagination": null,
"error": null,
"meta": {
"requestId": "9547acf1-7df7-4997-a2be-e343456036d6",
"timestamp": "2026-09-28T12:25:20.5763820Z"
}
}

Error Responses

  • 404 NOT_FOUND: the vendor doesn't exist, has been retired, or belongs to another tenant. The three cases return the same response, and the id is never echoed back, so a response can't confirm that a vendor exists in another tenant.
  • 401 UNAUTHORIZED: the request is missing a token, or the token lacks Api_Vendors_Read.
  • 500 INTERNAL_ERROR: an unexpected server error.

json

{
"data": null,
"pagination": null,
"error": {
"code": "NOT_FOUND",
"message": "Resource not found.",
"traceId": "0HNOTB94989C2:00000001"
},
"meta": {
"requestId": "d54d82c5-359a-4b6a-9547-79e85adb2a10",
"timestamp": "2026-09-28T12:25:20.9892740Z"
}
}

Create a Vendor

Creates a vendor in your tenant and returns its new identifier, so you can record the mapping to your own identifier without a follow-up lookup.

Request

POST /api/public/vendors

Authentication

Bearer token. See Authentication & Generating Credentials.

Required Permission

Api_Vendors_Create. Read or update access doesn't grant create.

Request Body

Field

Type

Rules

name

string

Required. Not blank. Up to 256 characters.

legalName

string

Optional. Up to 256 characters.

primaryDomain

string

Optional. Up to 253 characters.

website

string

Optional. Up to 512 characters.

description

string

Optional. Up to 1024 characters.

customAttributes

object

Optional. Custom field values keyed by field key. Values are JSON strings up to 400 characters. Create must include every required field. Update merges values instead of replacing them.

json

{
"name": "Contoso Analytics",
"legalName": "Contoso Analytics Ltd",
"primaryDomain": "contoso-analytics.example",
"website": "https://contoso-analytics.example",
"description": "Model hosting provider",
"customAttributes": {
"supplier_number": "12345"
}
}

customAttributes sets custom field values, keyed by field key. Keys must match exactly, including case. Values must be JSON strings, so send 12345 as "12345". The request must include every required custom field. Custom fields are defined in My Vendors, not through the API.

Response

Returns 201 Created with vendor (the new vendor record) and possibleDuplicates. There is no Location header; read the new id from vendor.vendorId.

The vendor is always created when the request is valid. If it resembles vendors you already have, those vendors appear in possibleDuplicates for review, and nothing is blocked. Each entry contains the existing vendor record, including its custom values, and a matchType of Name or PrimaryDomain. An empty list means no look-alikes were found, not that the vendor is unique. An integration that creates vendors in bulk should treat a non-empty list as a prompt to reconcile, since nothing else prevents near-duplicates.

Sample Response

json

{
"data": {
"vendor": {
"vendorId": "bf421a62-2c72-4568-9047-da67f054ef24",
"name": "Contoso Cloud Services",
"legalName": null,
"primaryDomain": "contoso-analytics.example",
"website": null,
"description": null,
"status": "Active",
"createdAt": "2026-09-28T12:25:22.226603Z",
"updatedAt": "2026-09-28T12:25:22.226603Z",
"customAttributes": {},
"publisherNames": [],
"sharedAiCardCount": 0
},
"possibleDuplicates": [
{
"vendor": {
"vendorId": "03a8d602-846a-46e8-9cbc-99d021accf73",
"name": "Contoso Analytics",
"legalName": "Contoso Analytics Ltd",
"primaryDomain": "contoso-analytics.example",
"website": "https://contoso-analytics.example",
"description": "Model hosting provider",
"status": "Active",
"createdAt": "2026-09-28T12:25:21.306379",
"updatedAt": "2026-09-28T12:25:21.306379",
"customAttributes": {},
"publisherNames": [],
"sharedAiCardCount": 0
},
"matchType": "PrimaryDomain"
}
]
},
"pagination": null,
"error": null,
"meta": {
"requestId": "804cca14-50d7-4a72-8c30-4aac90145509",
"timestamp": "2026-09-28T12:25:22.3611190Z"
}
}

This sample shows a possible duplicate matched on primary domain. The new vendor was still created.

Create isn't idempotent and has no deduplication key. Retrying a create after a timeout can produce a second vendor, which possibleDuplicates on the retry usually shows.

Error Responses

  • 400: a custom attribute value is sent as a number or boolean instead of a string. This response doesn't use the standard error format.
  • 409 CONFLICT: a value is already held by another vendor in a unique custom field. The response names the field where it can be identified. Nothing is saved.
  • 422 VALIDATION_FAILED: the body fails validation. error.details lists each failing field. Custom field problems, such as an unknown, retired, or over-long key or a missing required field, use a field of customAttributes.{key}.
  • 401 UNAUTHORIZED: the request is missing a token, or the token lacks Api_Vendors_Create.
  • 500 INTERNAL_ERROR: an unexpected server error.

json

{
"data": null,
"pagination": null,
"error": {
"code": "VALIDATION_FAILED",
"message": "Validation failed.",
"traceId": "0HNOTB94989C4:00000001",
"details": [
{
"field": "name",
"message": "Name is required."
}
]
},
"meta": {
"requestId": "68414f4d-d079-4994-95a2-d3320ab6ff5a",
"timestamp": "2026-09-28T12:25:21.8732140Z"
}
}

Update a Vendor

Updates a vendor's details, addressed by its Cranium vendor identifier.

Request

PUT /api/public/vendors/{vendorId}

Authentication

Bearer token. See Authentication & Generating Credentials.

Required Permission

Api_Vendors_Update. Create access doesn't grant update.

Request Body

Same fields and rules as Create a Vendor.

An update is a full replacement for every standard field, so a field you leave out is cleared. Send the vendor's complete intended state, and retrieve it first if you don't hold it. customAttributes works differently and merges: leave it out to keep every stored value, send a key to set it, or send a key as null or "" to clear it.

Renaming a vendor keeps the same vendorId. Update runs no duplicate detection. status can't be set through update; use Retire a Vendor instead.

Response

Returns 200 OK with the vendor record as stored.

Sample Response

json

{
"data": {
"vendorId": "03a8d602-846a-46e8-9cbc-99d021accf73",
"name": "Contoso Analytics Group",
"legalName": "Contoso Analytics Group Ltd",
"primaryDomain": "contoso-analytics.example",
"website": "https://contoso-analytics.example",
"description": "Model hosting and evaluation provider",
"status": "Active",
"createdAt": "2026-09-28T12:25:21.306379",
"updatedAt": "2026-09-28T12:25:23.152886Z",
"customAttributes": {},
"publisherNames": [],
"sharedAiCardCount": 0
},
"pagination": null,
"error": null,
"meta": {
"requestId": "cd8c213c-bac0-4f93-bc27-8c01775bd96a",
"timestamp": "2026-09-28T12:25:23.2262930Z"
}
}

Error Responses

  • 400: a custom attribute value is sent as a number or boolean instead of a string. This response doesn't use the standard error format.
  • 404 NOT_FOUND: the vendor doesn't exist, has been retired, or belongs to another tenant.
  • 409 CONFLICT: a value is already held by another vendor in a unique custom field. Nothing is saved.
  • 422 VALIDATION_FAILED: the body fails validation. Validation runs before the lookup, so a bad body returns 422 even for an id that doesn't exist. Once a custom field is marked Required, an update that includes customAttributes for a vendor without a value for that field returns 422 until the value is supplied.
  • 401 UNAUTHORIZED: the request is missing a token, or the token lacks Api_Vendors_Update.
  • 500 INTERNAL_ERROR: an unexpected server error.

Retire a Vendor

Retires a vendor your organization no longer works with, addressed by its Cranium vendor identifier.

Request

DELETE /api/public/vendors/{vendorId}

Authentication

Bearer token. See Authentication & Generating Credentials.

Required Permission

Api_Vendors_Delete. Update access doesn't grant retire.

Response

Returns 204 No Content with an empty body. The X-Request-Id header is still set; quote it to support if needed.

Retiring is a soft retire. The vendor record is kept, but it no longer appears in the list or lookup, and update returns 404 for it. There is no API route to un-retire a vendor. AI Cards, the BOMs inside them, and vulnerability data received from the vendor are unaffected.

Error Responses

  • 404 NOT_FOUND: the vendor doesn't exist, belongs to another tenant, or is already retired. A repeat retire returns 404, so after a timeout, read a 404 on retry as already retired.
  • 401 UNAUTHORIZED: the request is missing a token, or the token lacks Api_Vendors_Delete.
  • 500 INTERNAL_ERROR: the retire couldn't be confirmed, and the vendor may still be active. It is never reported as success. Retry, then contact support with the traceId.

Permission and Routing Errors

A missing permission returns 401 with an empty body, not 403. A 401 with a token you know is valid usually means the API role lacks the operation's Api_Vendors_* permission.

A mistyped path or unsupported method also returns 401. For example, a vendorId that isn't a GUID, or a PATCH request, matches no endpoint, and the permission check rejects it before routing. Check the path and method before the token.