Developer Portal
Build on KeyBS Trust Intelligence
The KeyBS API lives on its own subdomain — api.trust.keybs.io, versioned under /v1. This portal is the canonical reference for endpoints, authentication, webhooks, the badge embed, errors, and rate limits.
Overview
- Production API base
- https://api.trust.keybs.io/v1
- Internal app mirror
- https://app.trust.keybs.io/api/public/v1/*
- Developer portal
- https://developers.trust.keybs.io
- Public verify URL
- https://verify.keybs.io/r/<verificationId>
- Marketing site
- https://trust.keybs.io
Every endpoint returns JSON with a stable error envelope and an X-Request-Id header on every response. Include the request ID in any support correspondence.
Authentication
Authenticate every request with a tenant-scoped API key issued from the client portal → API keys. Keys are hashed at rest and shown exactly once on creation — store them in your secrets manager.
Authorization: Bearer kbs_live_…(production)Authorization: Bearer kbs_test_…(sandbox)- Each key carries a fixed scope set; insufficient scope returns 403 invalid_scope.
- Revoked keys are rejected immediately — no caching window.
Supported scopes
- verification:create
- verification:read
- document:create
- report:read
- webhook:manage
Endpoints
https://api.trust.keybs.io/v1/verificationsverification:createCreate a new verification case.
Request
{
"supplier_name": "Acme Trading Co.",
"country_code": "IN",
"tier": "tier2",
"package": "standard",
"contact_email": "ops@acme.example",
"client_reference": "PO-2026-0042"
}Response
{
"id": "f7e9…",
"verification_id": "KBS-VR-7F3A",
"status": "submitted",
"supplier": { "name": "Acme Trading Co.", "country_code": "IN", "tier": "tier2" },
"created_at": "2026-05-31T10:21:00Z"
}https://api.trust.keybs.io/v1/verificationsverification:readList tenant-owned verification cases. Supports ?status= and ?limit= (max 200).
Response
{
"data": [
{ "id": "f7e9…", "verification_id": "KBS-VR-7F3A", "status": "in_review", "supplier": { … } }
]
}https://api.trust.keybs.io/v1/verifications/:idverification:readRetrieve a single verification — safe public-facing fields only.
Response
{
"id": "f7e9…",
"verification_id": "KBS-VR-7F3A",
"status": "approved",
"supplier": { "name": "Acme Trading Co.", "country_code": "IN", "trust_score": 78, "risk_band": "medium" },
"report": { "lifecycle_status": "published", "signed_at": "…", "valid_until": "…", "verify_url": "…" }
}https://api.trust.keybs.io/v1/verifications/:id/documentsdocument:createAttach a document reference (signed-URL upload performed separately).
Request
{
"kind": "trade_license",
"filename": "license.pdf",
"storage_path": "tenants/…/license.pdf",
"sha256": "9af…"
}Response
{ "id": "doc_…", "kind": "trade_license", "status": "received" }https://api.trust.keybs.io/v1/reportsreport:readList signed/published reports owned by the tenant.
Response
{
"data": [
{ "verification_id": "KBS-VR-7F3A", "lifecycle_status": "published", "signed_at": "…", "valid_until": "…", "verify_url": "…", "pdf_url": "…" }
]
}https://api.trust.keybs.io/v1/reports/:verificationIdreport:readRetrieve a single report — public-safe metadata only.
Response
{
"verification_id": "KBS-VR-7F3A",
"supplier_name": "Acme Trading Co.",
"country": "India",
"trust_score": 78,
"risk_band": "medium",
"report_status": "published",
"lifecycle_status": "published",
"signed_at": "…", "valid_until": "…", "verify_url": "…", "pdf_url": "…"
}https://api.trust.keybs.io/v1/webhooks/testwebhook:manageTrigger a signed webhook.test event to every active endpoint.
Request
{ "endpoint_id": "wh_…" }Response
{ "ok": true, "endpoint_id": "wh_…", "event_id": "evt_…" }Webhooks
KeyBS delivers signed JSON webhooks for every verification and report lifecycle change. Configure endpoints from the client portal → Webhooks. The signing secret is encrypted at rest with AES-256-GCM and never returned after creation.
Headers
- X-KeyBS-Event-Id: evt_…
- X-KeyBS-Timestamp: 1717150800 (unix seconds)
- X-KeyBS-Signature: t=<ts>,v1=<hex_hmac_sha256(secret, `$${ts}.${body}`)>
Verifying a signature
// Node example
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, header, rawBody) {
const [tPart, sigPart] = header.split(",");
const ts = tPart.split("=")[1];
const sig = sigPart.split("=")[1];
if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return false; // replay
const mac = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(mac), Buffer.from(sig));
}Event payload
{
"event_id": "evt_…",
"event_type": "report.signed",
"created_at": "2026-05-31T10:21:00Z",
"tenant_id": "tnt_…",
"verification_case_id": "f7e9…",
"verification_id": "KBS-VR-7F3A",
"status": "signed",
"supplier": { "name": "Acme Trading Co.", "country_code": "IN", "trust_score": 78, "risk_band": "medium" },
"report": { "lifecycle_status": "signed", "signed_at": "…", "valid_until": "…", "verify_url": "…" }
}Event types
verification.submitted— New verification case created (API or portal).verification.in_review— Case picked up by an analyst.verification.rfi_requested— Analyst requested more information from the client.verification.rfi_resolved— RFI items have been provided and the case is back in review.verification.approved— Case approved (with or without controls). Report not yet signed.verification.declined— Case declined. No report will be issued.report.signed— Senior Analyst signed the report. Cryptographic digest available.report.published— Signed report published — verify_url is live.report.revoked— Report revoked. Public verify page shows a revoked warning.report.expiring— Report is within 30 days of valid_until.report.expired— Report has passed valid_until — verify page marked expired.webhook.test— Sent only by manual test from /app/webhooks.
Retry & delivery log
Failed deliveries (non-2xx or timeout) are marked failed with next_retry_at populated; we do not retry forever. The delivery log includes attempt count, HTTP status, response body excerpt, and error message — visible in /app/webhooks. webhook.test events are tracked separately from lifecycle events.
Badge embed
Paste the snippet on any partner site. The script injects a hosted iframe pointing at the badge page — partners never see private data.
<script async src="https://app.trust.keybs.io/badge/<verificationId>/embed.js"></script>
<div id="keybs-badge"></div>Hosted badge
https://app.trust.keybs.io/badge/<verificationId>
Badge states
- Active — within validity window.
- Expiring — within 30 days of valid_until; warning band shown.
- Expired — past valid_until; verify page remains accessible but marked expired.
- Revoked — signing authority revoked the report; do not rely on it.
- Disputed — under client/regulator dispute; treat as not-current.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
| unauthorized | 401 | Missing or invalid Authorization header / API key. |
| forbidden | 403 | Key is valid but the tenant does not own this resource. |
| invalid_scope | 403 | Key lacks the scope required by this endpoint. |
| not_found | 404 | Resource not found within this tenant. |
| validation_error | 422 | Request body failed schema validation. See `message`. |
| rate_limited | 429 | Per-key rate limit exceeded. Retry shortly. |
| server_error | 500 | Unexpected internal error. Includes request_id for support. |
{ "error": { "code": "invalid_scope", "message": "Scope verification:create required", "request_id": "req_…" } }Rate limits
Each API key gets 120 requests / minute by default, enforced per key (and effectively per tenant). Exceeded calls return 429 rate_limited. Enterprise plans lift this — talk to sales.
CORS allowlist
- https://trust.keybs.io
- https://app.trust.keybs.io
- https://ops.trust.keybs.io
- https://developers.trust.keybs.io
Server-to-server calls (no Origin header) are not affected by CORS.
Sandbox / demo mode
Sandbox keys (kbs_test_…) hit the same endpoints and return the same shapes as production, but target the demo-data tenant. Use them to wire up integrations end-to-end without touching real supplier cases. Sandbox-issued reports carry a demo watermark on the public verify page.