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 |
|---|---|---|
| GUID | Cranium's identifier for the vendor. Record it against your own identifier so later updates need no search. |
| string | Display name. |
| string, nullable | Legal entity name. |
| string, nullable | The vendor's main domain. Stored as sent and not validated as a domain. Used for duplicate matching. |
| string, nullable | Website URL. |
| string, nullable | Free text. |
| string | Always |
| ISO 8601, UTC | When the vendor was created. |
| 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 |
| object | The vendor's values for active custom vendor fields, keyed by field key. |
| string array | Not populated yet. Always |
| integer | Not populated yet. Always |
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 |
|---|---|---|
| integer | Results per page. Maximum 200. A higher value returns 400 rather than being reduced. |
| string | Omit to start. Pass |
| ISO 8601 | Start the feed from a point in time. Can't be combined with |
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:limitis above 200,pageis supplied, the cursor is malformed, orcursorandupdatedAfterare supplied together. To useupdatedAfter, leavecursorout of the query string entirely; an emptycursoralso returns 400.401 UNAUTHORIZED: the request is missing a token, or the token lacksApi_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 lacksApi_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 |
|---|---|---|
| string | Required. Not blank. Up to 256 characters. |
| string | Optional. Up to 256 characters. |
| string | Optional. Up to 253 characters. |
| string | Optional. Up to 512 characters. |
| string | Optional. Up to 1024 characters. |
| 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.detailslists each failing field. Custom field problems, such as an unknown, retired, or over-long key or a missing required field, use afieldofcustomAttributes.{key}.401 UNAUTHORIZED: the request is missing a token, or the token lacksApi_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 includescustomAttributesfor 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 lacksApi_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 lacksApi_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 thetraceId.
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.





