Skip to main content

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.

bash

# 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

ResourceEndpointAccess
Agents & MCPPOST /public/mcpPublic
Fill your profile with your own AIWebMCP · 4 toolsSigned in
Verifiable evidencetlt2 · VC crosswalkPublic
Agentic commercepositionPublic
Public Trust Profile APIGET /public/trust/:slugPublic
Trust Badgetrooth.co/badge.jsPublic embed
CLI (Terraform pre-flight)POST /v1/preflightPublic, rate limited
GitHub ActionPOST /v1/preflightPublic, rate limited
WebhooksPOST · outboundSigned

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-co

Ready 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.

Inspector01 / 6

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

GET/public/trust/:slug

GET /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.

Live requestPublic · 60/min
Get/public/trust/
curl -s https://api.trooth.co/public/trust/your-co \
  -H "Accept: application/json"
Response

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.

See a live witnessed profile

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>
Rendered badgeInteractive
data-slug="trooth"

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

POST/v1/preflight

Check 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.

bash

# 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.json

Flags, CI wiring and the full command reference are on the CLI page.

Continuous integration

GitHub Action

POST/v1/preflight

Run 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.

yaml
# .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 gate

Events

Webhooks

Subscribe to state changes and Trooth will POST a JSON event to your endpoint. Manage endpoints from your dashboard.

EventFires when
witness.changedWitnessed 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.witnessedA control was (re)witnessed by a scan.
profile.viewedA buyer opened your public Trust Profile.
profile.requestedA buyer asked you to publish a profile.
monitoring.driftMonitoring 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.