Developer docs
Check any company from your own code.
One GET returns the witnessed record for any company on the Network, no key, no account. Every example below runs against the live API at api.trooth.co.
In one lineOne witnessed record, four ways to read it: a single API call, the CLI, the GitHub Action, and the embeddable badge.
# No key, no account. The public profile is open.
curl -s https://api.trooth.co/public/trust/your-co
{ "profile": { "displayName": "Your Co", "witnessed": true } }
# Or install the CLI and scan a plan.
npm install -g trooth
trooth scan plan.json
Reference
Everything on this page
| Resource | Endpoint | Access |
|---|---|---|
| Agents & MCP | POST /public/mcp | Public |
| Fill your profile with your own AI | WebMCP · 4 tools | Signed in |
| Verifiable evidence | tlt2 · VC crosswalk | Public |
| Agentic commerce | position | Public |
| Public Trust Profile API | GET /public/trust/:slug | Public |
| Trust Badge | trooth.co/badge.js | Public embed |
| CLI (Terraform pre-flight) | POST /v1/preflight | Public, rate limited |
| GitHub Action | POST /v1/preflight | Public, rate limited |
| Webhooks | POST · outbound | Signed |
Start here
Quickstart
The public Trust Profile API needs no key or account. It answers what is true about a company, checked: the signed, witnessed record, fetched by slug. Authenticated endpoints (your dashboard, webhooks) use a Bearer token you mint in your dashboard. Trooth automates. Trooth never signs.
curl https://api.trooth.co/public/trust/your-coReady for authenticated calls? Grab a key and see rate limits on the API reference.
How it fits
Architecture
Every value the API returns came through this path. Select a node to see what it receives, what it emits, and what it is unable to do.
Topology
↺ The whole path repeats on a schedule, which is why a profile does not go stale.
Your stack
Cloud accounts, identity providers, repositories and endpoints, connected with read-only grants you approve one at a time.
Cannot
Cannot be written to. No write scope is requested, so Trooth is unable to change the systems it reports on.
Payload shape
{ "connector": "aws", "grant": "read-only", "scopes": ["describe*", "list*", "get*"], "write_scopes": []}Shapes are real; the values are an illustrative sample, not anyone's account.
Public API
Public Trust Profile API
/public/trust/:slugPublicGET /public/trust/:slug returns a company's public, signed Trust Profile as JSON: framework coverage, the pillar summary, the chain evidence and the last witnessed time. A company Trooth has not witnessed returns a typed absence rather than a guess, with the reason and a claim link. Public and unauthenticated.
curl -s https://api.trooth.co/public/trust/your-co \
-H "Accept: application/json"Press Run. Nothing is shown here until a real response comes back, so this pane never displays a payload the API did not send.
Calls api.trooth.co directly from your browser. Public, unauthenticated, and rate limited for each IP address.
Client embed
Trust Badge
Drop a live, self-updating Trust Badge anywhere. Paste the embed where you want it to appear. It reads your public profile and re-renders automatically; no redeploy needed.
<div id="trooth-trust-badge" data-slug="your-co"></div>
<script src="https://trooth.co/badge.js"></script>This loads the real badge.js against Trooth's own slug, so what you see is a live witnessed record. Swap in your own slug and the same script renders your standing.
The badge on the right is rendered by the real badge.js against Trooth's own slug, so it shows a real witnessed record. Your embed uses your slug.
Full install steps and placement tips are on the badge install page.
Command line
CLI
/v1/preflightPublic, rate limitedCheck a plan against any framework straight from your terminal or CI, report-only. It never applies changes and never signs on your behalf. The scan posts your rendered Terraform plan to POST /v1/preflight and prints the verdict and findings that come back. It sends no API key: this is the endpoint the published trooth binary calls.
# Published to npm as `trooth`. Zero dependencies, Node 18+.
npm install -g trooth
trooth --version
0.2.0
# No install. Advisory and report-only; nothing is ever applied.
terraform show -json plan.tfplan > plan.json
npx trooth scan plan.jsonFlags, CI wiring and the full command reference are on the CLI page.
Continuous integration
GitHub Action
/v1/preflightAPI keyRun the same advisory preflight on every pull request. It posts the plan to /v1/preflight and is report-only by default, so it never blocks a merge. The endpoint itself takes no credential; the action accepts a key so a future authenticated mode does not change its inputs.
# .github/workflows/trooth.yml
- name: Trooth compliance scan
uses: troothllc/trooth-action@v1
with:
api-key: ${{ secrets.TROOTH_API_KEY }}
plan-file: plan.json
fail-on: none # report-only; set critical/high/medium/low to gateEvents
Webhooks
Subscribe to state changes and Trooth will POST a JSON event to your endpoint. Manage endpoints from your dashboard.
| Event | Fires when |
|---|---|
| witness.changed | Witnessed coverage moved: how many checks pass, out of how many ran, with the previous pair. trust.score.changed is accepted as a legacy name at registration and delivered as witness.changed. |
| control.witnessed | A control was (re)witnessed by a scan. |
| profile.viewed | A buyer opened your public Trust Profile. |
| profile.requested | A buyer asked you to publish a profile. |
| monitoring.drift | Monitoring recorded a regression or a lost connection on a connected source. One event per workspace per re-witness run; data carries count, changes[] and a url. |
Each event shares the outer envelope of id, type, created and data. The shape inside data varies by type, and so does its nesting: read the sample for the event you are handling rather than assuming a common wrapper.
{
"id": "evt_a1b2c3d4e5f6",
"type": "witness.changed",
"created": 1785340800000,
"data": {
"coverage": { "passed": 41, "run": 44 },
"previous": { "passed": 40, "run": 44 },
"change": { "passed": 1, "run": 0 },
"source": "capability",
"changed_at": 1785340800000
}
}The legacy name control.verified is accepted as an alias when registering webhooks.
Every delivery carries two headers. x-trooth-signature is the HMAC-SHA256 of the raw request body, keyed with your endpoint's signing secret, as 64 lowercase hex characters with no prefix. x-trooth-event repeats the event type. There is no timestamp header; the signed bytes are the body and nothing else.
Compute the same HMAC over the exact bytes you received, before any JSON parsing, and compare in constant time. Key order, whitespace and Unicode are all part of the signed bytes: a body your framework parsed and re-serialised will not verify. Deduplicate on the event id; with no timestamp signed, that is what makes a replayed delivery harmless.
import crypto from "node:crypto";
// rawBody: the exact bytes of the request as a Buffer, read BEFORE any JSON
// parsing. A body a framework parsed and re-serialised is not what was signed.
// headers: the request headers with lower-case names.
// secret: the endpoint signing secret shown once at registration.
export function verifyTrooth(rawBody, headers, secret) {
const signature = String(headers["x-trooth-signature"] || "");
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(signature, "utf8");
// Compare the length first: timingSafeEqual throws on unequal lengths, and
// a missing or malformed header must be a clean false, not a crash.
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}Use of the Trooth API is subject to the Terms of Service.