Credential Register
Menu
Region
Change region — currently Canada
Appearance
Appearance — System

Appearance

Light Dark System

Remembered across the whole site.

Get started

API & MCP reference

Connect Credential Register to the systems you already run: read your vendors, documents and projects, keep vendor records in step with your accounting or ERP, hear about renewals as they happen, and let an AI assistant answer questions about your register. Included from the Growth plan.

Keys and permissions

A workspace owner creates keys on the API keys page of the workspace and ticks exactly what each one may do. A key is shown once, and we keep only a fingerprint of it. Send it as a bearer token on every call.
A key can be limited to the addresses your systems call from, and the owners are emailed when a key is used from an address it has never used before. Any owner or admin can revoke a key, and it stops working on the next call.

Base address

https://api.credentialregister.com/v1

curl https://api.credentialregister.com/v1/vendors?status=active \
  -H "Authorization: Bearer cr_…"

Permissions

Each endpoint needs one permission. The standard set is available to every workspace with API access. The rest are switched on for a workspace on request: choose "Request additional API functionality" on the API keys page and tell us what you need them for. A permission we switch on still does nothing until an owner ticks it on a key.

PermissionAllowsAvailableOver MCP
vendors:read Read vendors and their checklist status Standard Yes
vendors:write Create and update vendors Standard No
projects:read Read projects and who is on them Standard Yes
projects:write Create and update projects, and put vendors on them Standard No
documents:read Read documents: facts, dates, status and decisions Standard Yes
files:read Download the files attached to documents Standard No
invitations:send Send portal invitations to vendors Standard No
documents:submit Upload documents into the review queue On request No
subcontractors:write Record who works under whom On request No
activity:read Read the activity log On request No
records:trash Move vendors, projects and documents to Trash On request No
decisions:write Record review decisions on behalf of a named reviewer On request No

Responses, pages and errors

Every response is JSON, with the record or list in data. Lists come a page at a time: pass meta.next_cursor back as cursor until it is null. Dates are written YYYY-MM-DD and times in ISO 8601.
Every response carries the same disclaimer as your reports: a status is a fact, a date or a named person's decision, never our verdict on a document. An error is an HTTP status with a short code you can branch on and a sentence you can show.

{
  "data": {
    "id": 44,
    "name": "Northside Electrical",
    "status": "active",
    "external_ref": "V-1042",
    "works_under": null,
    "checklist": {
      "summary": "3/4 on file • 1 expiring • 1 missing",
      "required": 4,
      "on_file": 3,
      "expiring": 1,
      "expired": 0,
      "missing": 1,
      "items": [
        {
          "document_type": { "id": 7, "name": "General liability certificate" },
          "state": "expiring",
          "state_label": "On file — expiring soon",
          "detail": "Expires Mar 1, 2027",
          "required_for": null,
          "expires_on": "2027-03-01",
          "document_id": 381,
          "awaiting_review": false
        }
      ]
    }
  },
  "meta": { "disclaimer": "…" }
}
{
  "error": {
    "code": "forbidden_scope",
    "message": "This key does not have the \"files:read\" permission. A workspace owner can add it on the API page."
  }
}

{
  "error": {
    "code": "invalid",
    "message": "The name field is required.",
    "fields": { "name": ["The name field is required."] }
  }
}
StatusCodeMeaning
401 invalid_key The key is missing, wrong or revoked.
403 forbidden_scope The key does not have the permission this endpoint needs.
403 blocked_ip The key has an address list and this address is not on it.
403 plan The workspace's plan does not include API access.
403 workspace_unavailable The workspace is suspended or closed.
403 creator_left The owner who created the key no longer owns the workspace.
404 not_found No such endpoint, or no such record in this workspace.
422 invalid The request did not pass validation; fields says which values and why.
422 plan_limit Adding or reactivating this vendor would go over the plan's vendor limit.
422 no_email An invitation needs an address and the vendor has none.
422 not_a_reviewer reviewer_email is not a workspace member who may review.
422 not_awaiting_review Only a submitted document, or one marked as needing information, can be decided.
429 rate_limited Over 120 requests a minute for this key. Retry-After says when to try again.
429 invitation_cap The workspace's daily invitation allowance is used up.
429 too_many_invalid Too many calls with a bad key from this address.
405 method_not_allowed The path exists but not with that method.
500 server_error Something failed on our side. It is logged; try again later.

Endpoints

Every path is under the base address. A record that belongs to another workspace answers exactly as one that does not exist. Changes follow the same rules as the workspace itself, your plan's vendor limit included.

Vendors

GET /vendors vendors:read

List vendors, each with their checklist: what is required, on file, expiring, expired and missing.

status query
active or inactive.
project query
Only vendors on this project id.
updated_since query
A date or ISO 8601 time.
search query
Part of the vendor's name.
per_page query
Up to 100; 50 by default.
cursor query
meta.next_cursor from the previous page.
GET /vendors/{id} vendors:read

One vendor, with who they work under and their checklist item by item, including why each item is required.

POST /vendors vendors:write

Add a vendor. An active vendor counts toward your plan's vendor limit, exactly as in the workspace.

name body
Required.
category, primary_contact, email, phone body
Optional details.
status body
active (default) or inactive.
external_ref body
Your own system's id for this vendor.
notes body
Up to 5,000 characters.
PATCH /vendors/{id} vendors:write

Change any of the fields above. Making an inactive vendor active again takes a slot on your plan.

POST /vendors/{id}/invitations invitations:send

Invite someone to this vendor's portal. Capped at 50 a day per workspace.

email body
Defaults to the vendor's own address.
role body
vendor_owner (default) or vendor_member.
message body
A personal note, as on the invite form.

Documents

GET /documents documents:read

List documents with their dates, status, attestation and the decisions people recorded.

vendor query
Only this vendor id.
status query
draft, submitted, on_file, needs_info or rejected.
expiring_within_days query
Only documents expiring between today and this many days ahead.
updated_since query
A date or ISO 8601 time.
per_page query
Up to 100; 50 by default.
cursor query
meta.next_cursor from the previous page.
GET /vendors/{id}/documents documents:read

The same list for one vendor, workers' documents included.

per_page query
Up to 100; 50 by default.
cursor query
meta.next_cursor from the previous page.
GET /documents/{id} documents:read

One document in full, with its files listed by name, size and SHA-256 fingerprint.

GET /documents/{id}/files/{file} files:read

Download one file. Its download_url appears on the document only for a key with this permission.

Projects

GET /projects projects:read

List projects with their status and how many vendors are on each.

status query
active, completed or archived.
per_page query
Up to 100; 50 by default.
cursor query
meta.next_cursor from the previous page.
GET /projects/{id} projects:read

One project: its own checklist, and every vendor on it with their trade and their status for this job.

POST /projects projects:write

Create a project.

name body
Required.
reference body
Your own job number.
site_address, notes body
Optional.
starts_on, ends_on body
Dates; for information only.
status body
active (default), completed or archived. Only an active project's checklist counts toward its vendors.
PATCH /projects/{id} projects:write

Change any of the fields above.

PUT /projects/{id}/vendors/{vendor} projects:write

Put a vendor on a project, or change their trade there. Calling it again is safe.

trade body
Optional, e.g. Electrical.
DELETE /projects/{id}/vendors/{vendor} projects:write

Take a vendor off a project. Neither record is deleted.

On request

POST /vendors/{id}/documents documents:submit

Upload a document for a vendor, as multipart form data. It always lands in your review queue — your system cannot attest to it, so a person decides.

document_type_id body
Required. One of your workspace's document types.
files[] body
1 to 10 files: PDF, PNG, JPEG, WebP or HEIC, 20 MB each.
worker_id body
To file it against one of the vendor's workers.
issuer, identifier, issued_on, expires_on body
Optional facts.
POST /documents/{id}/decisions decisions:write

Record a review decision on a document awaiting review, on behalf of a named member of your workspace who may review. It reads as theirs, with their note, and shows it came through the API.

decision body
approved, rejected or needs_info.
reviewer_email body
The workspace member making the decision.
note body
Required, as it is in the workspace.
DELETE /vendors/{id} records:trash

Move a vendor to Trash, with their documents. Restorable by an owner or admin.

DELETE /projects/{id} records:trash

Move a project to Trash. Its vendors and documents are untouched.

DELETE /documents/{id} records:trash

Move a document to Trash, decision history and all.

PUT /vendors/{id}/parent subcontractors:write

Record who a vendor works under, or pass null to make them a direct vendor again.

parent_vendor_id body
Required: a vendor id, or null.
GET /activity activity:read

The workspace's activity log: who changed what, how (workspace, portal, API, MCP…), and each field's old and new value.

since query
A date or ISO 8601 time.
channel query
One of: workspace, vendor_portal, management, api, mcp, platform_support, platform, system, web.
per_page query
Up to 100; 50 by default.
cursor query
meta.next_cursor from the previous page.

Webhooks

An owner adds an https address on the Webhooks page and picks its events. Each delivery is a POST of JSON carrying ids and facts, never files, signed with the endpoint's own secret so your system can tell it came from us.
A delivery that fails is tried again seven times over about a day. After 20 deliveries in a row fail, the endpoint is switched off and the owners are told.

Events

document.submitted
A document was submitted for review
document.decided
A reviewer recorded a decision on a document
document.expiring
A document reached a reminder step before its expiry date
document.expired
A document expired
vendor.created
A vendor was added
vendor.updated
A vendor's details or status changed
{
  "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
  "event": "document.submitted",
  "created_at": "2027-02-10T14:05:00+00:00",
  "workspace": { "id": 12, "name": "Acme Builders" },
  "data": {
    "document": {
      "id": 381,
      "vendor": { "id": 44, "name": "Northside Electrical" },
      "document_type": "General liability certificate",
      "status": "submitted",
      "expires_on": "2028-03-01"
    }
  },
  "disclaimer": "…"
}

Checking a signature

The X-Signature header holds a Unix time t and a signature v1: an HMAC-SHA256 of the time, a full stop and the raw body, keyed with the secret. Compute it yourself, compare in constant time, and refuse a delivery more than five minutes old.

// Node.js — rawBody is the request body exactly as received.
import crypto from 'node:crypto';

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')));
  const sentAt = Number(parts.t);
  if (!sentAt || Math.abs(Date.now() / 1000 - sentAt) > 300 || !parts.v1) return false;

  const expected = crypto.createHmac('sha256', secret).update(`${sentAt}.${rawBody}`).digest('hex');
  return expected.length === parts.v1.length
    && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

AI assistants (MCP)

Our MCP server lets an assistant such as Claude look up your vendors, documents and projects and answer questions about them. It is read-only, always, whatever the key it uses may do, and it serves no files.
In Claude, add a custom connector with the address below and sign in: an owner picks the workspace and what the assistant may read. Other assistants can send a key instead.

Server address

https://api.credentialregister.com/mcp

claude mcp add --transport http credential-register https://api.credentialregister.com/mcp \
  --header "Authorization: Bearer cr_…"

Every call on the record

Every call is in the workspace's request log for 90 days: the key, the address it came from, and what it read or changed. Every change also appears on the Activity page marked API or MCP, with each field's old and new value, and an owner or admin can undo most of them there.

What no key can do

No key can set a document's status or attestation directly, delete anything permanently, change your team or their roles, touch billing or your plan, change your workspace's address or domain, or create or widen another key. Those stay with the people in your workspace.

Ready to connect?

Create your first key from the API keys page in your workspace, or start a free trial to try it.

Start your free 14-day trial

API questions in the help centre →