Developers
API reference
Read your vendor compliance status from your own systems, so your procurement and payment tools can check VendMetric before they act.
Base URL and authentication
All endpoints are under https://app.vendmetric.com/api/v1. An organization admin creates keys in Settings; each key is shown once. Send it as a Bearer token. Every response is scoped to the key's organization.
The API and webhooks are included on Professional and Enterprise, not on Starter. Keys can be given an expiry when you create them, and the plan is checked on every request, so a key stops working if the plan no longer includes API access.
curl https://app.vendmetric.com/api/v1/vendors \
-H "Authorization: Bearer vm_live_your_key_here"Rate limits: 60 requests per minute and 10,000 per day per key. Exceeding a limit returns HTTP 429 with a Retry-After header. Keys can be revoked at any time in Settings.
Key expiry and rotation
When you create a key you choose how long it lives: 24 hours, 30 days, 90 days, 1 year, or never. New keys default to 90 days. A key that never expires stays valid until someone revokes it, including if it leaks into a log or a repository, so prefer a dated key.
We notify your admins seven days before a key expires. Requests with an expired key return HTTP 401 with the code key_expired.
To rotate without downtime: create the replacement key, deploy it, confirm traffic has moved, then revoke the old key.
Versioning
Each key is pinned to a dated release, returned on every response in the VendMetric-API-Version header. Your integration keeps the behavior it was built against; a new key picks up the current release.
We add endpoints and response fields without changing your version, so your client must ignore fields it does not recognize. Anything that could break a working integration, such as removing a field or tightening a default, ships as a new dated release instead. When a release is retired you will see Deprecation and Sunset headers for at least 90 days first.
Errors
Errors return a human message and a stable code to branch on.
{ "error": "API key expired. Create a new key in Settings, Developer.",
"code": "key_expired" }- invalid_key (401): missing, malformed, or revoked.
- key_expired (401): the key passed its expiry date.
- plan_required (403): your plan does not include this.
- rate_limited (429): see Retry-After.
- not_found (404): no such record in your organization.
- invalid_request (400): a parameter was rejected; the message names it.
- internal_error (500): failed on our side, and we are alerted. Safe to retry.
List vendors
GET/vendors
Active vendors with compliance status and score, 50 per page by default, sorted by name.
{
"data": [
{
"id": "cmr...",
"name": "Apex Electrical",
"category": "Construction",
"status": "ACTIVE",
"compliance": "COMPLIANT",
"vis_score": 96
}
],
"has_more": true,
"next_cursor": "cmr..."
}Pass next_cursor back as cursor to walk pages until has_more is false.
- limit: 1 to 200, default 50.
- cursor: the previous page's next_cursor.
- compliance: COMPLIANT, AT_RISK, NON_COMPLIANT, or PENDING.
- status: ACTIVE, ONBOARDING, INVITED, SUSPENDED, or ARCHIVED. Archived vendors are excluded unless you ask for them.
- updated_since: ISO 8601; only vendors changed since then.
curl "https://app.vendmetric.com/api/v1/vendors?compliance=NON_COMPLIANT&limit=100" \
-H "Authorization: Bearer vm_live_your_key_here"Vendor compliance detail
GET/vendors/{id}/compliance
The endpoint to call before paying a vendor: whether they are compliant, plus each requirement still needing action.
{
"data": {
"id": "cmr...",
"name": "Apex Electrical",
"compliance": "AT_RISK",
"vis_score": 74,
"is_compliant": false,
"requirements": [
{
"document_type": "COI",
"document_type_name": "Certificate of Insurance",
"state": "EXPIRING_SOON",
"days_until_expiration": 12,
"expiration_date": "2026-07-30T00:00:00.000Z",
"waived": false,
"verification": null
},
{
"document_type": "W9",
"document_type_name": "W-9 Tax Form",
"state": "SATISFIED",
"days_until_expiration": null,
"expiration_date": null,
"waived": false,
"verification": {
"status": "MATCH",
"checked_at": "2026-07-18T14:22:00.000Z"
}
}
],
"action_needed": [ /* the requirements not yet satisfied */ ]
}
}The verification field
Each requirement carries a verification object when its satisfying document has been checked against an authoritative source (W-9 tax IDs first), or null when no such check has run. It is evidence for your decision, never a gate: a requirement can be satisfied without verification, and a verification result never changes a document's status on its own.
- MATCH: the name and tax ID were confirmed against the source.
- MISMATCH: the source reports the values do not match. Only a confirmed non-match returns this; treat it as a signal to review the file, not an automatic rejection.
- REVIEW: the source returned a partial or ambiguous result (for example a near-miss on the name, or a fuzzy watchlist candidate) that a person should confirm. Not a pass and not a confirmed failure.
- PENDING: an asynchronous check is in progress (for example a state registry lookup). Poll again, or subscribe to the document.approved webhook; the field settles to one of the states above.
- UNAVAILABLE: the check could not be completed (no tax ID present, or the source could not be reached). A provider error is never reported as a mismatch.
checked_at is the timestamp of the most recent check. If you enforce source-verified tax IDs in your payment gate, require verification.status === "MATCH" on the relevant requirement in addition to is_compliant.
Vendor warranties
GET/vendors/{id}/warranties
The warranties your organization holds with one vendor, soonest expiration first. Deliberately separate from the compliance endpoint: a warranty is coverage you own, so it never affects is_compliant or the vendor's score. Use it to feed asset, maintenance, or procurement systems that need to know when coverage ends.
{
"data": {
"vendor_id": "cmr...",
"vendor_name": "Apex Electrical",
"warranties": [
{
"reference": "V#-2107",
"covered_item": "Rooftop HVAC units, Building A",
"provider": "Trane",
"start_date": "2025-09-02T00:00:00.000Z",
"expiration_date": "2027-09-01T00:00:00.000Z",
"days_until_expiration": 410,
"status": "ACTIVE",
"coverage_value": 85000,
"terms": "Parts and labor, quarterly maintenance required."
}
]
}
}status is one of ACTIVE, EXPIRING_SOON, EXPIRED, CLAIMED, or VOID. A vendor with no warranties returns an empty list. Warranty tracking is a Professional-plan feature: on plans without it the endpoint returns HTTP 403 with an explanation rather than an empty result.
Organization summary
GET/organization/summary
Portfolio counts and average score, for a dashboard tile.
{
"data": {
"vendors_total": 42,
"by_compliance": { "COMPLIANT": 33, "AT_RISK": 6, "NON_COMPLIANT": 2, "PENDING": 1 },
"average_vis": 88
}
}Contracts
GET/contracts
Your organization's contracts with their vendor, value, dates and renewal terms, soonest end date first. Drafts are left out unless you ask for them with status=DRAFT.
{
"data": [
{
"id": "cms...",
"reference": "CT-2026-014",
"title": "HVAC preventive maintenance, Building A",
"vendor_id": "cmr...",
"vendor_name": "Apex Electrical",
"status": "ACTIVE",
"value": 48000,
"start_date": "2026-04-01",
"end_date": "2027-03-31",
"renewal_type": "AUTO",
"renewal_notice_days": 60,
"notice_deadline": "2027-01-30",
"days_until_end": 167
}
],
"has_more": false,
"next_cursor": null
}notice_deadline is the last day to give notice: the end date less renewal_notice_days. It is null when either is missing, never a guess. Amendments update end_date in place, so it is always the date in force. Dates are YYYY-MM-DD in UTC; value is in dollars, or null.
- limit: 1 to 200, default 50.
- cursor: the previous page's next_cursor.
- status: one of DRAFT, ACTIVE, EXPIRING_SOON, EXPIRED, TERMINATED, RENEWED (default: everything but DRAFT)
- vendor_id: only that vendor's contracts
- ends_after / ends_before: YYYY-MM-DD; ends on or after, and before, those days
Monthly spend
GET/spend/monthly
Cash paid to your vendors, by closed calendar month: payments as sent plus retainage later released, each counted in the month it was paid.
{
"data": [
{
"month": "2026-09",
"total": 61250.3,
"payments": 58750.3,
"retainage_released": 2500,
"payment_count": 14,
"release_count": 1
}
],
"from": "2025-10",
"to": "2026-09",
"currency": "USD",
"basis": "cash_paid"
}Months are UTC and closed: the month still running is never reported, because a partial month would read as a whole one. A month with nothing paid is left out rather than reported as zero, so a missing month means no payment was recorded, not that one was zero. Amounts are dollars.
- from / to: YYYY-MM, closed months only, at most 24 (default: the last twelve closed months)
Webhooks
Instead of polling, register an https endpoint in Settings and get a signed POST the moment something changes. Available events:
vendor.compliance_changeddocument.approveddocument.expiredvendor.joined
Each delivery carries an X-VendMetric-Event header and an X-VendMetric-Signature header of the form sha256=<hex>, an HMAC-SHA256 of the exact request body using your endpoint's signing secret (shown once when you add the endpoint). Verify it before trusting a payload:
// Node.js
import crypto from "crypto";
function verify(rawBody, signatureHeader, secret) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected),
);
}Failed deliveries are retried; an endpoint that fails repeatedly is disabled and your team is notified. Use "Send test" in Settings to confirm your receiver works.
