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.
| Permission | Allows | Available | Over 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."] }
}
}
| Status | Code | Meaning |
|---|---|---|
| 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
/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.
/vendors/{id}
vendors:read
One vendor, with who they work under and their checklist item by item, including why each item is required.
/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.
/vendors/{id}
vendors:write
Change any of the fields above. Making an inactive vendor active again takes a slot on your plan.
/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
/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.
/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.
/documents/{id}
documents:read
One document in full, with its files listed by name, size and SHA-256 fingerprint.
/documents/{id}/files/{file}
files:read
Download one file. Its download_url appears on the document only for a key with this permission.
Projects
/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.
/projects/{id}
projects:read
One project: its own checklist, and every vendor on it with their trade and their status for this job.
/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.
/projects/{id}
projects:write
Change any of the fields above.
/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.
/projects/{id}/vendors/{vendor}
projects:write
Take a vendor off a project. Neither record is deleted.
On request
/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.
/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.
/vendors/{id}
records:trash
Move a vendor to Trash, with their documents. Restorable by an owner or admin.
/projects/{id}
records:trash
Move a project to Trash. Its vendors and documents are untouched.
/documents/{id}
records:trash
Move a document to Trash, decision history and all.
/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.
/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